Skip to content

Commit 9c965ad

Browse files
committed
feat(core): make the last retry happen at deadline
This commit changes Retry instances in a way that the last sleep period is shortened so that its end matches at the given deadline. This prevents the last sleep period to last way beyond the deadline.
1 parent 4a5b3ee commit 9c965ad

2 files changed

Lines changed: 14 additions & 41 deletions

File tree

api_core/google/api_core/retry.py

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,7 @@ def exponential_sleep_generator(initial, maximum, multiplier=_DEFAULT_DELAY_MULT
141141
delay = delay * multiplier
142142

143143

144-
def retry_target(
145-
target, predicate, sleep_generator, deadline, on_error=None, strict_deadline=False
146-
):
144+
def retry_target(target, predicate, sleep_generator, deadline, on_error=None):
147145
"""Call a function and retry if it fails.
148146
149147
This is the lowest-level retry helper. Generally, you'll use the
@@ -157,13 +155,12 @@ def retry_target(
157155
It should return True to retry or False otherwise.
158156
sleep_generator (Iterable[float]): An infinite iterator that determines
159157
how long to sleep between retries.
160-
deadline (float): How long to keep retrying the target.
158+
deadline (float): How long to keep retrying the target. The last sleep
159+
period is shortened as necessary, so that the last retry runs at
160+
``deadline`` (and not considerably beyond it).
161161
on_error (Callable[Exception]): A function to call while processing a
162162
retryable exception. Any error raised by this function will *not*
163163
be caught.
164-
strict_deadline (bool): If :data:`True`, the last retry will run at
165-
``deadline``, shortening the last sleep interval as necessary.
166-
Defaults to :data:`False`.
167164
168165
Returns:
169166
Any: the return value of the target function.
@@ -208,7 +205,7 @@ def retry_target(
208205
),
209206
last_exc,
210207
)
211-
elif strict_deadline:
208+
else:
212209
time_to_deadline = (deadline_datetime - now).total_seconds()
213210
sleep = min(time_to_deadline, sleep)
214211

@@ -237,10 +234,9 @@ class Retry(object):
237234
must be greater than 0.
238235
maximum (float): The maximum amout of time to delay in seconds.
239236
multiplier (float): The multiplier applied to the delay.
240-
deadline (float): How long to keep retrying in seconds.
241-
strict_deadline (bool): If :data:`True`, the last retry will run at
242-
``deadline``, shortening the last sleep interval as necessary.
243-
Defaults to :data:`False`.
237+
deadline (float): How long to keep retrying in seconds. The last sleep
238+
period is shortened as necessary, so that the last retry runs at
239+
``deadline`` (and not considerably beyond it).
244240
"""
245241

246242
def __init__(
@@ -251,15 +247,13 @@ def __init__(
251247
multiplier=_DEFAULT_DELAY_MULTIPLIER,
252248
deadline=_DEFAULT_DEADLINE,
253249
on_error=None,
254-
strict_deadline=False,
255250
):
256251
self._predicate = predicate
257252
self._initial = initial
258253
self._multiplier = multiplier
259254
self._maximum = maximum
260255
self._deadline = deadline
261256
self._on_error = on_error
262-
self._strict_deadline = strict_deadline
263257

264258
def __call__(self, func, on_error=None):
265259
"""Wrap a callable with retry behavior.
@@ -290,19 +284,15 @@ def retry_wrapped_func(*args, **kwargs):
290284
sleep_generator,
291285
self._deadline,
292286
on_error=on_error,
293-
strict_deadline=self._strict_deadline
294287
)
295288

296289
return retry_wrapped_func
297290

298-
def with_deadline(self, deadline, strict_deadline=False):
291+
def with_deadline(self, deadline):
299292
"""Return a copy of this retry with the given deadline.
300293
301294
Args:
302295
deadline (float): How long to keep retrying.
303-
strict_deadline (bool): If :data:`True`, the last retry will run at
304-
``deadline``, shortening the last sleep interval as necessary.
305-
Defaults to :data:`False`.
306296
307297
Returns:
308298
Retry: A new retry instance with the given deadline.
@@ -314,7 +304,6 @@ def with_deadline(self, deadline, strict_deadline=False):
314304
multiplier=self._multiplier,
315305
deadline=deadline,
316306
on_error=self._on_error,
317-
strict_deadline=strict_deadline,
318307
)
319308

320309
def with_predicate(self, predicate):
@@ -334,7 +323,6 @@ def with_predicate(self, predicate):
334323
multiplier=self._multiplier,
335324
deadline=self._deadline,
336325
on_error=self._on_error,
337-
strict_deadline=self._strict_deadline,
338326
)
339327

340328
def with_delay(self, initial=None, maximum=None, multiplier=None):
@@ -356,20 +344,17 @@ def with_delay(self, initial=None, maximum=None, multiplier=None):
356344
multiplier=multiplier if maximum is not None else self._multiplier,
357345
deadline=self._deadline,
358346
on_error=self._on_error,
359-
strict_deadline=self._strict_deadline,
360347
)
361348

362349
def __str__(self):
363350
return (
364351
"<Retry predicate={}, initial={:.1f}, maximum={:.1f}, "
365-
"multiplier={:.1f}, deadline={:.1f}, on_error={}, "
366-
"strict_deadline={}>".format(
352+
"multiplier={:.1f}, deadline={:.1f}, on_error={}>".format(
367353
self._predicate,
368354
self._initial,
369355
self._maximum,
370356
self._multiplier,
371357
self._deadline,
372358
self._on_error,
373-
self._strict_deadline,
374359
)
375360
)

api_core/tests/unit/test_retry.py

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ def test_constructor_defaults(self):
162162
assert retry_._multiplier == 2
163163
assert retry_._deadline == 120
164164
assert retry_._on_error is None
165-
assert retry_._strict_deadline is False
166165

167166
def test_constructor_options(self):
168167
_some_function = mock.Mock()
@@ -174,15 +173,13 @@ def test_constructor_options(self):
174173
multiplier=3,
175174
deadline=4,
176175
on_error=_some_function,
177-
strict_deadline=True,
178176
)
179177
assert retry_._predicate == mock.sentinel.predicate
180178
assert retry_._initial == 1
181179
assert retry_._maximum == 2
182180
assert retry_._multiplier == 3
183181
assert retry_._deadline == 4
184182
assert retry_._on_error is _some_function
185-
assert retry_._strict_deadline is True
186183

187184
def test_with_deadline(self):
188185
retry_ = retry.Retry(
@@ -192,12 +189,10 @@ def test_with_deadline(self):
192189
multiplier=3,
193190
deadline=4,
194191
on_error=mock.sentinel.on_error,
195-
strict_deadline=True,
196192
)
197-
new_retry = retry_.with_deadline(42, strict_deadline=True)
193+
new_retry = retry_.with_deadline(42)
198194
assert retry_ is not new_retry
199195
assert new_retry._deadline == 42
200-
assert new_retry._strict_deadline is True
201196

202197
# the rest of the attributes should remain the same
203198
assert new_retry._predicate is retry_._predicate
@@ -214,15 +209,13 @@ def test_with_predicate(self):
214209
multiplier=3,
215210
deadline=4,
216211
on_error=mock.sentinel.on_error,
217-
strict_deadline=True,
218212
)
219213
new_retry = retry_.with_predicate(mock.sentinel.predicate)
220214
assert retry_ is not new_retry
221215
assert new_retry._predicate == mock.sentinel.predicate
222216

223217
# the rest of the attributes should remain the same
224218
assert new_retry._deadline == retry_._deadline
225-
assert new_retry._strict_deadline == retry_._strict_deadline
226219
assert new_retry._initial == retry_._initial
227220
assert new_retry._maximum == retry_._maximum
228221
assert new_retry._multiplier == retry_._multiplier
@@ -236,7 +229,6 @@ def test_with_delay_noop(self):
236229
multiplier=3,
237230
deadline=4,
238231
on_error=mock.sentinel.on_error,
239-
strict_deadline=True,
240232
)
241233
new_retry = retry_.with_delay()
242234
assert retry_ is not new_retry
@@ -252,7 +244,6 @@ def test_with_delay(self):
252244
multiplier=3,
253245
deadline=4,
254246
on_error=mock.sentinel.on_error,
255-
strict_deadline=True,
256247
)
257248
new_retry = retry_.with_delay(initial=1, maximum=2, multiplier=3)
258249
assert retry_ is not new_retry
@@ -262,7 +253,6 @@ def test_with_delay(self):
262253

263254
# the rest of the attributes should remain the same
264255
assert new_retry._deadline == retry_._deadline
265-
assert new_retry._strict_deadline == retry_._strict_deadline
266256
assert new_retry._predicate is retry_._predicate
267257
assert new_retry._on_error is retry_._on_error
268258

@@ -279,13 +269,12 @@ def if_exception_type(exc):
279269
multiplier=2.0,
280270
deadline=120.0,
281271
on_error=None,
282-
strict_deadline=False,
283272
)
284273
assert re.match(
285274
(
286275
r"<Retry predicate=<function.*?if_exception_type.*?>, "
287276
r"initial=1.0, maximum=60.0, multiplier=2.0, deadline=120.0, "
288-
r"on_error=None, strict_deadline=False>"
277+
r"on_error=None>"
289278
),
290279
str(retry_),
291280
)
@@ -332,7 +321,7 @@ def test___call___and_execute_retry(self, sleep, uniform):
332321
# Make uniform return half of its maximum, which is the calculated sleep time.
333322
@mock.patch("random.uniform", autospec=True, side_effect=lambda m, n: n / 2.0)
334323
@mock.patch("time.sleep", autospec=True)
335-
def test___call___and_execute_retry_strict_deadline(self, sleep, uniform):
324+
def test___call___and_execute_retry_hitting_deadline(self, sleep, uniform):
336325

337326
on_error = mock.Mock(spec=["__call__"], side_effect=[None] * 10)
338327
retry_ = retry.Retry(
@@ -341,7 +330,6 @@ def test___call___and_execute_retry_strict_deadline(self, sleep, uniform):
341330
maximum=1024.0,
342331
multiplier=2.0,
343332
deadline=9.9,
344-
strict_deadline=True,
345333
)
346334

347335
utcnow = datetime.datetime.utcnow()
@@ -376,7 +364,7 @@ def increase_time(sleep_delay):
376364
total_wait = sum(call_args.args[0] for call_args in sleep.call_args_list)
377365

378366
assert last_wait == 2.9 # and not 8.0, because the last delay was shortened
379-
assert total_wait == 9.9 # the same as the (strict) deadline
367+
assert total_wait == 9.9 # the same as the deadline
380368

381369
@mock.patch("time.sleep", autospec=True)
382370
def test___init___without_retry_executed(self, sleep):

0 commit comments

Comments
 (0)