diff --git a/swift/common/utils.py b/swift/common/utils.py index bb0853f388..2b7a75b7a1 100644 --- a/swift/common/utils.py +++ b/swift/common/utils.py @@ -3484,21 +3484,31 @@ class AbstractRateLimiter(object): # 1,000 milliseconds = 1 second clock_accuracy = 1000.0 - def __init__(self, max_rate, rate_buffer=5, running_time=0): + def __init__(self, max_rate, rate_buffer=5, burst_after_idle=False, + running_time=0): """ :param max_rate: The maximum rate per second allowed for the process. Must be > 0 to engage rate-limiting behavior. :param rate_buffer: Number of seconds the rate counter can drop and be allowed to catch up (at a faster than listed rate). A larger number will result in larger spikes in rate but better average accuracy. + :param burst_after_idle: If False (the default) then the rate_buffer + allowance is lost after the rate limiter has not been called for + more than rate_buffer seconds. If True then the rate_buffer + allowance is preserved during idle periods which means that a burst + of requests may be granted immediately after the idle period. :param running_time: The running time in milliseconds of the next allowable request. Setting this to any time in the past will cause the rate limiter to immediately allow requests; setting this to a future time will cause the rate limiter to deny requests until that - time. + time. If ``burst_after_idle`` is True then this can + be set to current time (ms) to avoid an initial burst, or set to + running_time < (current time - rate_buffer ms) to allow an initial + burst. """ self.max_rate = max_rate self.rate_buffer_ms = rate_buffer * self.clock_accuracy + self.burst_after_idle = burst_after_idle self.running_time = running_time self.time_per_incr = (self.clock_accuracy / self.max_rate if self.max_rate else 0) @@ -3535,6 +3545,8 @@ class AbstractRateLimiter(object): # Convert rate_buffer to milliseconds and compare if now - self.running_time > self.rate_buffer_ms: self.running_time = now + if self.burst_after_idle: + self.running_time -= self.rate_buffer_ms if now >= self.running_time: self.running_time += time_per_request @@ -3557,9 +3569,11 @@ class AbstractRateLimiter(object): class EventletRateLimiter(AbstractRateLimiter): - def __init__(self, max_rate, rate_buffer=5, running_time=0): + def __init__(self, max_rate, rate_buffer=5, running_time=0, + burst_after_idle=False): super(EventletRateLimiter, self).__init__( - max_rate, rate_buffer, running_time) + max_rate, rate_buffer=rate_buffer, running_time=running_time, + burst_after_idle=burst_after_idle) def _sleep(self, seconds): eventlet.sleep(seconds) diff --git a/test/unit/common/test_utils.py b/test/unit/common/test_utils.py index e30e97030b..40a34fe8d8 100644 --- a/test/unit/common/test_utils.py +++ b/test/unit/common/test_utils.py @@ -5900,11 +5900,12 @@ class TestEventletRateLimiter(unittest.TestCase): mock_sleep.assert_not_called() def _do_test(self, max_rate, running_time, start_time, rate_buffer, - incr_by=1.0): + burst_after_idle=False, incr_by=1.0): rate_limiter = utils.EventletRateLimiter( max_rate, running_time=1000 * running_time, # msecs - rate_buffer=rate_buffer) + rate_buffer=rate_buffer, + burst_after_idle=burst_after_idle) grant_times = [] current_time = [start_time] @@ -5972,6 +5973,20 @@ class TestEventletRateLimiter(unittest.TestCase): grant_times = self._do_test(1, 0, 4, 3) self.assertEqual([4, 5, 6, 7, 8], grant_times) + def test_burst_after_idle(self): + grant_times = self._do_test(1, 1, 4, 1, burst_after_idle=True) + self.assertEqual([4, 4, 5, 6, 7], grant_times) + + grant_times = self._do_test(1, 1, 4, 2, burst_after_idle=True) + self.assertEqual([4, 4, 4, 5, 6], grant_times) + + grant_times = self._do_test(1, 0, 4, 3, burst_after_idle=True) + self.assertEqual([4, 4, 4, 4, 5], grant_times) + + # running_time = start_time prevents burst on start-up + grant_times = self._do_test(1, 4, 4, 3, burst_after_idle=True) + self.assertEqual([4, 5, 6, 7, 8], grant_times) + class TestRateLimitedIterator(unittest.TestCase):