Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit52f12af

Browse files
authored
feat(api_core): make the last retry happen at deadline (#9873)
* feat(api_core): allow setting Retry deadline as strictIf a deadline is set as strict, Retry will shorten the last sleepperiod to end at the given deadline, and not possibly stretch beyondit.* feat(core): make the last retry happen at deadlineThis commit changes Retry instances in a way that the last sleep periodis shortened so that its end matches at the given deadline. Thisprevents the last sleep period to last way beyond the deadline.
1 parentb6cea3c commit52f12af

File tree

2 files changed

+136
-20
lines changed

2 files changed

+136
-20
lines changed

‎google/api_core/retry.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,12 @@ def retry_target(target, predicate, sleep_generator, deadline, on_error=None):
155155
It should return True to retry or False otherwise.
156156
sleep_generator (Iterable[float]): An infinite iterator that determines
157157
how long to sleep between retries.
158-
deadline (float): How long to keep retrying the target.
159-
on_error (Callable): A function to call while processing a retryable
160-
exception. Any error raised by this function will *not* be
161-
caught.
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).
161+
on_error (Callable[Exception]): A function to call while processing a
162+
retryable exception. Any error raised by this function will *not*
163+
be caught.
162164
163165
Returns:
164166
Any: the return value of the target function.
@@ -191,16 +193,21 @@ def retry_target(target, predicate, sleep_generator, deadline, on_error=None):
191193
on_error(exc)
192194

193195
now=datetime_helpers.utcnow()
194-
ifdeadline_datetimeisnotNoneanddeadline_datetime<now:
195-
six.raise_from(
196-
exceptions.RetryError(
197-
"Deadline of {:.1f}s exceeded while calling {}".format(
198-
deadline,target
196+
197+
ifdeadline_datetimeisnotNone:
198+
ifdeadline_datetime<=now:
199+
six.raise_from(
200+
exceptions.RetryError(
201+
"Deadline of {:.1f}s exceeded while calling {}".format(
202+
deadline,target
203+
),
204+
last_exc,
199205
),
200206
last_exc,
201-
),
202-
last_exc,
203-
)
207+
)
208+
else:
209+
time_to_deadline= (deadline_datetime-now).total_seconds()
210+
sleep=min(time_to_deadline,sleep)
204211

205212
_LOGGER.debug(
206213
"Retrying due to {}, sleeping {:.1f}s ...".format(last_exc,sleep)
@@ -227,7 +234,9 @@ class Retry(object):
227234
must be greater than 0.
228235
maximum (float): The maximum amout of time to delay in seconds.
229236
multiplier (float): The multiplier applied to the delay.
230-
deadline (float): How long to keep retrying in seconds.
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).
231240
"""
232241

233242
def__init__(
@@ -251,8 +260,8 @@ def __call__(self, func, on_error=None):
251260
252261
Args:
253262
func (Callable): The callable to add retry behavior to.
254-
on_error (Callable): A function to call while processing a
255-
retryable exception. Any error raised by this function will
263+
on_error (Callable[Exception]): A function to call while processing
264+
aretryable exception. Any error raised by this function will
256265
*not* be caught.
257266
258267
Returns:

‎tests/unit/test_retry.py

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,35 +182,94 @@ def test_constructor_options(self):
182182
assertretry_._on_erroris_some_function
183183

184184
deftest_with_deadline(self):
185-
retry_=retry.Retry()
185+
retry_=retry.Retry(
186+
predicate=mock.sentinel.predicate,
187+
initial=1,
188+
maximum=2,
189+
multiplier=3,
190+
deadline=4,
191+
on_error=mock.sentinel.on_error,
192+
)
186193
new_retry=retry_.with_deadline(42)
187194
assertretry_isnotnew_retry
188195
assertnew_retry._deadline==42
189196

197+
# the rest of the attributes should remain the same
198+
assertnew_retry._predicateisretry_._predicate
199+
assertnew_retry._initial==retry_._initial
200+
assertnew_retry._maximum==retry_._maximum
201+
assertnew_retry._multiplier==retry_._multiplier
202+
assertnew_retry._on_errorisretry_._on_error
203+
190204
deftest_with_predicate(self):
191-
retry_=retry.Retry()
205+
retry_=retry.Retry(
206+
predicate=mock.sentinel.predicate,
207+
initial=1,
208+
maximum=2,
209+
multiplier=3,
210+
deadline=4,
211+
on_error=mock.sentinel.on_error,
212+
)
192213
new_retry=retry_.with_predicate(mock.sentinel.predicate)
193214
assertretry_isnotnew_retry
194215
assertnew_retry._predicate==mock.sentinel.predicate
195216

217+
# the rest of the attributes should remain the same
218+
assertnew_retry._deadline==retry_._deadline
219+
assertnew_retry._initial==retry_._initial
220+
assertnew_retry._maximum==retry_._maximum
221+
assertnew_retry._multiplier==retry_._multiplier
222+
assertnew_retry._on_errorisretry_._on_error
223+
196224
deftest_with_delay_noop(self):
197-
retry_=retry.Retry()
225+
retry_=retry.Retry(
226+
predicate=mock.sentinel.predicate,
227+
initial=1,
228+
maximum=2,
229+
multiplier=3,
230+
deadline=4,
231+
on_error=mock.sentinel.on_error,
232+
)
198233
new_retry=retry_.with_delay()
199234
assertretry_isnotnew_retry
200235
assertnew_retry._initial==retry_._initial
201236
assertnew_retry._maximum==retry_._maximum
202237
assertnew_retry._multiplier==retry_._multiplier
203238

204239
deftest_with_delay(self):
205-
retry_=retry.Retry()
240+
retry_=retry.Retry(
241+
predicate=mock.sentinel.predicate,
242+
initial=1,
243+
maximum=2,
244+
multiplier=3,
245+
deadline=4,
246+
on_error=mock.sentinel.on_error,
247+
)
206248
new_retry=retry_.with_delay(initial=1,maximum=2,multiplier=3)
207249
assertretry_isnotnew_retry
208250
assertnew_retry._initial==1
209251
assertnew_retry._maximum==2
210252
assertnew_retry._multiplier==3
211253

254+
# the rest of the attributes should remain the same
255+
assertnew_retry._deadline==retry_._deadline
256+
assertnew_retry._predicateisretry_._predicate
257+
assertnew_retry._on_errorisretry_._on_error
258+
212259
deftest___str__(self):
213-
retry_=retry.Retry()
260+
defif_exception_type(exc):
261+
returnbool(exc)# pragma: NO COVER
262+
263+
# Explicitly set all attributes as changed Retry defaults should not
264+
# cause this test to start failing.
265+
retry_=retry.Retry(
266+
predicate=if_exception_type,
267+
initial=1.0,
268+
maximum=60.0,
269+
multiplier=2.0,
270+
deadline=120.0,
271+
on_error=None,
272+
)
214273
assertre.match(
215274
(
216275
r"<Retry predicate=<function.*?if_exception_type.*?>, "
@@ -259,6 +318,54 @@ def test___call___and_execute_retry(self, sleep, uniform):
259318
sleep.assert_called_once_with(retry_._initial)
260319
asserton_error.call_count==1
261320

321+
# Make uniform return half of its maximum, which is the calculated sleep time.
322+
@mock.patch("random.uniform",autospec=True,side_effect=lambdam,n:n/2.0)
323+
@mock.patch("time.sleep",autospec=True)
324+
deftest___call___and_execute_retry_hitting_deadline(self,sleep,uniform):
325+
326+
on_error=mock.Mock(spec=["__call__"],side_effect=[None]*10)
327+
retry_=retry.Retry(
328+
predicate=retry.if_exception_type(ValueError),
329+
initial=1.0,
330+
maximum=1024.0,
331+
multiplier=2.0,
332+
deadline=9.9,
333+
)
334+
335+
utcnow=datetime.datetime.utcnow()
336+
utcnow_patcher=mock.patch(
337+
"google.api_core.datetime_helpers.utcnow",return_value=utcnow
338+
)
339+
340+
target=mock.Mock(spec=["__call__"],side_effect=[ValueError()]*10)
341+
# __name__ is needed by functools.partial.
342+
target.__name__="target"
343+
344+
decorated=retry_(target,on_error=on_error)
345+
target.assert_not_called()
346+
347+
withutcnow_patcheraspatched_utcnow:
348+
# Make sure that calls to fake time.sleep() also advance the mocked
349+
# time clock.
350+
defincrease_time(sleep_delay):
351+
patched_utcnow.return_value+=datetime.timedelta(seconds=sleep_delay)
352+
sleep.side_effect=increase_time
353+
354+
withpytest.raises(exceptions.RetryError):
355+
decorated("meep")
356+
357+
asserttarget.call_count==5
358+
target.assert_has_calls([mock.call("meep")]*5)
359+
asserton_error.call_count==5
360+
361+
# check the delays
362+
assertsleep.call_count==4# once between each successive target calls
363+
last_wait=sleep.call_args.args[0]
364+
total_wait=sum(call_args.args[0]forcall_argsinsleep.call_args_list)
365+
366+
assertlast_wait==2.9# and not 8.0, because the last delay was shortened
367+
asserttotal_wait==9.9# the same as the deadline
368+
262369
@mock.patch("time.sleep",autospec=True)
263370
deftest___init___without_retry_executed(self,sleep):
264371
_some_function=mock.Mock()

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp