Skip to content

Commit a030d6b

Browse files
Merge pull request #412 from reef-technologies/5xx-token-refresh
Avoid b2http retries for upload requests
2 parents c884f6f + 9a72f42 commit a030d6b

3 files changed

Lines changed: 52 additions & 11 deletions

File tree

b2sdk/_internal/b2http.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@
3636
B2ConnectionError,
3737
B2Error,
3838
B2RequestTimeout,
39-
B2RequestTimeoutDuringUpload,
4039
BadDateFormat,
4140
BrokenPipe,
4241
ClockSkew,
4342
ConnectionReset,
4443
PotentialS3EndpointPassedAsRealm,
44+
ServiceError,
4545
UnknownError,
4646
UnknownHost,
4747
interpret_b2_error,
@@ -281,7 +281,7 @@ def do_request():
281281
self._run_post_request_hooks(method, url, request_headers, response)
282282
return response
283283

284-
return self._translate_and_retry(do_request, try_count, params)
284+
return self._translate_and_retry(do_request, try_count, method, request_headers, params)
285285

286286
def request_content_return_json(
287287
self,
@@ -355,14 +355,9 @@ def post_content_return_json(
355355
:param data: a file-like object to send
356356
:return: a dict that is the decoded JSON
357357
"""
358-
try:
359-
return self.request_content_return_json(
360-
'POST', url, headers, data, try_count, post_params, _timeout=_timeout
361-
)
362-
except B2RequestTimeout:
363-
# this forces a token refresh, which is necessary if request is still alive
364-
# on the server but has terminated for some reason on the client. See #79
365-
raise B2RequestTimeoutDuringUpload()
358+
return self.request_content_return_json(
359+
'POST', url, headers, data, try_count, post_params, _timeout=_timeout
360+
)
366361

367362
def post_json_return_json(self, url, headers, params, try_count: int = TRY_COUNT_OTHER):
368363
"""
@@ -579,7 +574,12 @@ def _translate_errors(cls, fcn, post_params=None):
579574

580575
@classmethod
581576
def _translate_and_retry(
582-
cls, fcn: Callable, try_count: int, post_params: dict[str, Any] | None = None
577+
cls,
578+
fcn: Callable,
579+
try_count: int,
580+
method: str,
581+
headers: dict[str, Any],
582+
post_params: dict[str, Any] | None = None,
583583
):
584584
"""
585585
Try calling fcn try_count times, retrying only if
@@ -598,6 +598,15 @@ def _translate_and_retry(
598598
except B2Error as e:
599599
if not e.should_retry_http():
600600
raise
601+
if (
602+
method == 'POST'
603+
and 'X-Bz-Content-Sha1' in headers
604+
and isinstance(e, (ServiceError, B2RequestTimeout))
605+
):
606+
# This is an upload operation, so we avoid http-level retries
607+
# here to force an upload token refresh
608+
raise
609+
601610
logger.debug(str(e), exc_info=True)
602611
if e.retry_after_seconds is not None:
603612
sleep_duration = e.retry_after_seconds
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Avoid http-level retries during upload requests.

test/unit/b2http/test_b2http.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,37 @@ def test_too_many_requests_retry_header_combination_two(
387387
b2_http.request(responses.GET, self.URL, {}, try_count=4)
388388
assert mock_time.mock_calls == [call(1.0), call(5), call(2.25)]
389389

390+
@responses.activate
391+
def test_service_error_during_upload_no_retries(self, b2_http: B2Http, mock_time: MagicMock):
392+
_mock_error_response(self.URL, method=responses.POST, status=503)
393+
responses.post(self.URL)
394+
395+
headers = {'X-Bz-Content-Sha1': '1234'}
396+
397+
with pytest.raises(ServiceError):
398+
b2_http.request(responses.POST, self.URL, headers)
399+
400+
mock_time.assert_not_called()
401+
402+
@responses.activate
403+
def test_request_timeout_during_upload_no_retries(self, b2_http: B2Http, mock_time: MagicMock):
404+
responses.post(
405+
self.URL,
406+
body=requests.ConnectionError(
407+
requests.packages.urllib3.exceptions.ProtocolError(
408+
'dummy', TimeoutError('The write operation timed out')
409+
)
410+
),
411+
)
412+
responses.post(self.URL)
413+
414+
headers = {'X-Bz-Content-Sha1': '1234'}
415+
416+
with pytest.raises(B2RequestTimeout):
417+
b2_http.request(responses.POST, self.URL, headers)
418+
419+
mock_time.assert_not_called()
420+
390421

391422
class TestB2Http:
392423
URL = 'http://example.com'

0 commit comments

Comments
 (0)