Skip to content

Commit 91c9145

Browse files
authored
fix: add REQUEST_TIMEOUT 408 as retryable code (#270)
* fix: add code 408 as retryable code * revert 2 test changes * revise test_success_with_retry * address review comments
1 parent 0f9628e commit 91c9145

2 files changed

Lines changed: 69 additions & 68 deletions

File tree

packages/google-resumable-media/google/resumable_media/common.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363

6464
RETRYABLE = (
6565
TOO_MANY_REQUESTS, # 429
66+
http.client.REQUEST_TIMEOUT, # 408
6667
http.client.INTERNAL_SERVER_ERROR, # 500
6768
http.client.BAD_GATEWAY, # 502
6869
http.client.SERVICE_UNAVAILABLE, # 503

packages/google-resumable-media/tests/unit/test__helpers.py

Lines changed: 68 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -198,15 +198,9 @@ def test_success_no_retry(self):
198198
@mock.patch("time.sleep")
199199
@mock.patch("random.randint")
200200
def test_success_with_retry(self, randint_mock, sleep_mock):
201-
randint_mock.side_effect = [125, 625, 375]
202-
203-
status_codes = (
204-
http.client.INTERNAL_SERVER_ERROR,
205-
http.client.BAD_GATEWAY,
206-
http.client.SERVICE_UNAVAILABLE,
207-
http.client.NOT_FOUND,
208-
)
201+
status_codes = common.RETRYABLE + (http.client.NOT_FOUND,)
209202
responses = [_make_response(status_code) for status_code in status_codes]
203+
randint_mock.side_effect = [75 * i for i in range(len(status_codes) - 1)]
210204

211205
def raise_response():
212206
raise common.InvalidResponse(responses.pop(0))
@@ -222,36 +216,42 @@ def raise_response():
222216
assert ret_val.status_code == status_codes[-1]
223217
assert status_codes[-1] not in common.RETRYABLE
224218

225-
assert func.call_count == 4
226-
assert func.mock_calls == [mock.call()] * 4
219+
assert func.call_count == len(status_codes)
220+
assert func.mock_calls == [mock.call()] * len(status_codes)
227221

228-
assert randint_mock.call_count == 3
229-
assert randint_mock.mock_calls == [mock.call(0, 1000)] * 3
222+
assert randint_mock.call_count == len(status_codes) - 1
223+
assert randint_mock.mock_calls == [mock.call(0, 1000)] * (len(status_codes) - 1)
230224

231-
assert sleep_mock.call_count == 3
232-
sleep_mock.assert_any_call(1.125)
233-
sleep_mock.assert_any_call(2.625)
234-
sleep_mock.assert_any_call(4.375)
225+
assert sleep_mock.call_count == len(status_codes) - 1
226+
wait = 1
227+
multiplier = 2
228+
for i in range(len(status_codes) - 1):
229+
randint = i * 75 / 1000
230+
sleep_mock.assert_any_call(wait + randint)
231+
wait = wait * multiplier
235232

236233
@mock.patch("time.sleep")
237234
@mock.patch("random.randint")
238235
def test_success_with_retry_custom_delay(self, randint_mock, sleep_mock):
239-
randint_mock.side_effect = [125, 625, 375]
240-
241236
status_codes = (
242237
http.client.INTERNAL_SERVER_ERROR,
243238
http.client.BAD_GATEWAY,
244239
http.client.SERVICE_UNAVAILABLE,
245240
http.client.NOT_FOUND,
246241
)
247242
responses = [_make_response(status_code) for status_code in status_codes]
243+
randint_mock.side_effect = [75 * i for i in range(len(status_codes) - 1)]
248244

249245
def raise_response():
250246
raise common.InvalidResponse(responses.pop(0))
251247

252248
func = mock.Mock(side_effect=raise_response)
253249

254-
retry_strategy = common.RetryStrategy(initial_delay=3.0, multiplier=4)
250+
initial_delay = 3.0
251+
multiplier = 4
252+
retry_strategy = common.RetryStrategy(
253+
initial_delay=initial_delay, multiplier=multiplier
254+
)
255255
try:
256256
_helpers.wait_and_retry(func, _get_status_code, retry_strategy)
257257
except common.InvalidResponse as e:
@@ -260,26 +260,21 @@ def raise_response():
260260
assert ret_val.status_code == status_codes[-1]
261261
assert status_codes[-1] not in common.RETRYABLE
262262

263-
assert func.call_count == 4
264-
assert func.mock_calls == [mock.call()] * 4
263+
assert func.call_count == len(status_codes)
264+
assert func.mock_calls == [mock.call()] * len(status_codes)
265265

266-
assert randint_mock.call_count == 3
267-
assert randint_mock.mock_calls == [mock.call(0, 1000)] * 3
266+
assert randint_mock.call_count == len(status_codes) - 1
267+
assert randint_mock.mock_calls == [mock.call(0, 1000)] * (len(status_codes) - 1)
268268

269-
assert sleep_mock.call_count == 3
270-
sleep_mock.assert_any_call(3.125) # initial delay 3 + jitter 0.125
271-
sleep_mock.assert_any_call(
272-
12.625
273-
) # previous delay 3 * multiplier 4 + jitter 0.625
274-
sleep_mock.assert_any_call(
275-
48.375
276-
) # previous delay 12 * multiplier 4 + jitter 0.375
269+
assert sleep_mock.call_count == len(status_codes) - 1
270+
for i in range(len(status_codes) - 1):
271+
randint = i * 75 / 1000
272+
sleep_mock.assert_any_call(initial_delay + randint)
273+
initial_delay = initial_delay * multiplier
277274

278275
@mock.patch("time.sleep")
279276
@mock.patch("random.randint")
280277
def test_success_with_retry_connection_error(self, randint_mock, sleep_mock):
281-
randint_mock.side_effect = [125, 625, 375]
282-
283278
response = _make_response(http.client.NOT_FOUND)
284279
responses = [
285280
requests.exceptions.ConnectionError,
@@ -288,63 +283,69 @@ def test_success_with_retry_connection_error(self, randint_mock, sleep_mock):
288283
response,
289284
]
290285
func = mock.Mock(side_effect=responses, spec=[])
286+
randint_mock.side_effect = [125 * i for i in range(len(responses) - 1)]
291287

292288
retry_strategy = common.RetryStrategy()
293289
ret_val = _helpers.wait_and_retry(func, _get_status_code, retry_strategy)
294290

295291
assert ret_val == responses[-1]
296292

297-
assert func.call_count == 4
298-
assert func.mock_calls == [mock.call()] * 4
293+
assert func.call_count == len(responses)
294+
assert func.mock_calls == [mock.call()] * len(responses)
299295

300-
assert randint_mock.call_count == 3
301-
assert randint_mock.mock_calls == [mock.call(0, 1000)] * 3
296+
assert randint_mock.call_count == len(responses) - 1
297+
assert randint_mock.mock_calls == [mock.call(0, 1000)] * (len(responses) - 1)
302298

303-
assert sleep_mock.call_count == 3
304-
sleep_mock.assert_any_call(1.125)
305-
sleep_mock.assert_any_call(2.625)
306-
sleep_mock.assert_any_call(4.375)
299+
assert sleep_mock.call_count == len(responses) - 1
300+
wait = 1
301+
multiplier = 2
302+
for i in range(len(responses) - 1):
303+
randint = i * 125 / 1000
304+
sleep_mock.assert_any_call(wait + randint)
305+
wait = wait * multiplier
307306

308307
@mock.patch(u"time.sleep")
309308
@mock.patch(u"random.randint")
310309
def test_success_with_retry_chunked_encoding_error(self, randint_mock, sleep_mock):
311-
randint_mock.side_effect = [125, 625, 375]
312-
313310
response = _make_response(http.client.NOT_FOUND)
314311
responses = [
315312
requests.exceptions.ChunkedEncodingError,
316313
requests.exceptions.ChunkedEncodingError,
317314
response,
318315
]
319316
func = mock.Mock(side_effect=responses, spec=[])
317+
randint_mock.side_effect = [125 * i for i in range(len(responses) - 1)]
320318

321319
retry_strategy = common.RetryStrategy()
322320
ret_val = _helpers.wait_and_retry(func, _get_status_code, retry_strategy)
323321

324322
assert ret_val == responses[-1]
325323

326-
assert func.call_count == 3
327-
assert func.mock_calls == [mock.call()] * 3
324+
assert func.call_count == len(responses)
325+
assert func.mock_calls == [mock.call()] * len(responses)
328326

329-
assert randint_mock.call_count == 2
330-
assert randint_mock.mock_calls == [mock.call(0, 1000)] * 2
327+
assert randint_mock.call_count == len(responses) - 1
328+
assert randint_mock.mock_calls == [mock.call(0, 1000)] * (len(responses) - 1)
331329

332-
assert sleep_mock.call_count == 2
333-
sleep_mock.assert_any_call(1.125)
334-
sleep_mock.assert_any_call(2.625)
330+
assert sleep_mock.call_count == len(responses) - 1
331+
wait = 1
332+
multiplier = 2
333+
for i in range(len(responses) - 1):
334+
randint = i * 125 / 1000
335+
sleep_mock.assert_any_call(wait + randint)
336+
wait = wait * multiplier
335337

336338
@mock.patch(u"time.sleep")
337339
@mock.patch(u"random.randint")
338340
def test_connection_import_error_failure(self, randint_mock, sleep_mock):
339-
randint_mock.side_effect = [125, 625, 375]
340-
341341
response = _make_response(http.client.NOT_FOUND)
342342
responses = [
343343
requests.exceptions.ConnectionError,
344344
requests.exceptions.ConnectionError,
345345
requests.exceptions.ConnectionError,
346346
response,
347347
]
348+
randint_mock.side_effect = [125 * i for i in range(len(responses) - 1)]
348349

349350
with mock.patch(
350351
"google.resumable_media._helpers._get_connection_error_classes",
@@ -403,8 +404,6 @@ def raise_response():
403404
@mock.patch("time.sleep")
404405
@mock.patch("random.randint")
405406
def test_retry_exceeds_max_retries(self, randint_mock, sleep_mock):
406-
randint_mock.side_effect = [875, 0, 375, 500, 500, 250, 125]
407-
408407
status_codes = (
409408
http.client.SERVICE_UNAVAILABLE,
410409
http.client.GATEWAY_TIMEOUT,
@@ -415,13 +414,15 @@ def test_retry_exceeds_max_retries(self, randint_mock, sleep_mock):
415414
common.TOO_MANY_REQUESTS,
416415
)
417416
responses = [_make_response(status_code) for status_code in status_codes]
417+
randint_mock.side_effect = [75 * i for i in range(len(responses))]
418418

419419
def raise_response():
420420
raise common.InvalidResponse(responses.pop(0))
421421

422422
func = mock.Mock(side_effect=raise_response)
423423

424-
retry_strategy = common.RetryStrategy(max_retries=6)
424+
max_retries = 6
425+
retry_strategy = common.RetryStrategy(max_retries=max_retries)
425426
try:
426427
_helpers.wait_and_retry(func, _get_status_code, retry_strategy)
427428
except common.InvalidResponse as e:
@@ -430,31 +431,30 @@ def raise_response():
430431
assert ret_val.status_code == status_codes[-1]
431432
assert status_codes[-1] in common.RETRYABLE
432433

433-
assert func.call_count == 7
434-
assert func.mock_calls == [mock.call()] * 7
434+
assert func.call_count == max_retries + 1
435+
assert func.mock_calls == [mock.call()] * (max_retries + 1)
435436

436-
assert randint_mock.call_count == 7
437-
assert randint_mock.mock_calls == [mock.call(0, 1000)] * 7
437+
assert randint_mock.call_count == max_retries + 1
438+
assert randint_mock.mock_calls == [mock.call(0, 1000)] * (max_retries + 1)
438439

439-
assert sleep_mock.call_count == 6
440-
sleep_mock.assert_any_call(1.875)
441-
sleep_mock.assert_any_call(2.0)
442-
sleep_mock.assert_any_call(4.375)
443-
sleep_mock.assert_any_call(8.5)
444-
sleep_mock.assert_any_call(16.5)
445-
sleep_mock.assert_any_call(32.25)
440+
assert sleep_mock.call_count == max_retries
441+
wait = 1
442+
multiplier = 2
443+
for i in range(max_retries - 1):
444+
randint = i * 75 / 1000
445+
sleep_mock.assert_any_call(wait + randint)
446+
wait = wait * multiplier
446447

447448
@mock.patch("time.sleep")
448449
@mock.patch("random.randint")
449450
def test_retry_zero_max_retries(self, randint_mock, sleep_mock):
450-
randint_mock.side_effect = [875, 0, 375]
451-
452451
status_codes = (
453452
http.client.SERVICE_UNAVAILABLE,
454453
http.client.GATEWAY_TIMEOUT,
455454
common.TOO_MANY_REQUESTS,
456455
)
457456
responses = [_make_response(status_code) for status_code in status_codes]
457+
randint_mock.side_effect = [125 * i for i in range(len(status_codes))]
458458

459459
def raise_response():
460460
raise common.InvalidResponse(responses.pop(0))

0 commit comments

Comments
 (0)