Skip to content

Commit f57db58

Browse files
committed
Avoid http-level retries during upload requests
This PR fixes the b2http logic to avoid any retries during the upload requests. Previously, the http layer would still proceed with retries during upload failures, preventing the upload manager from issuing a new upload token. There was a workaround only for timeout errors, but it didn't quite work as expected.
1 parent c159384 commit f57db58

File tree

3 files changed

+47
-11
lines changed

3 files changed

+47
-11
lines changed

b2sdk/_internal/b2http.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
B2ConnectionError,
3737
B2Error,
3838
B2RequestTimeout,
39-
B2RequestTimeoutDuringUpload,
4039
BadDateFormat,
4140
BrokenPipe,
4241
ClockSkew,
@@ -281,7 +280,7 @@ def do_request():
281280
self._run_post_request_hooks(method, url, request_headers, response)
282281
return response
283282

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

286285
def request_content_return_json(
287286
self,
@@ -355,14 +354,9 @@ def post_content_return_json(
355354
:param data: a file-like object to send
356355
:return: a dict that is the decoded JSON
357356
"""
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()
357+
return self.request_content_return_json(
358+
'POST', url, headers, data, try_count, post_params, _timeout=_timeout
359+
)
366360

367361
def post_json_return_json(self, url, headers, params, try_count: int = TRY_COUNT_OTHER):
368362
"""
@@ -579,7 +573,12 @@ def _translate_errors(cls, fcn, post_params=None):
579573

580574
@classmethod
581575
def _translate_and_retry(
582-
cls, fcn: Callable, try_count: int, post_params: dict[str, Any] | None = None
576+
cls,
577+
fcn: Callable,
578+
try_count: int,
579+
method: str,
580+
headers: dict[str, Any],
581+
post_params: dict[str, Any] | None = None,
583582
):
584583
"""
585584
Try calling fcn try_count times, retrying only if
@@ -598,6 +597,11 @@ def _translate_and_retry(
598597
except B2Error as e:
599598
if not e.should_retry_http():
600599
raise
600+
if method == 'POST' and 'X-Bz-Content-Sha1' in headers:
601+
# This is an upload operation, so we avoid http-level retries
602+
# here to force an upload token refresh
603+
raise
604+
601605
logger.debug(str(e), exc_info=True)
602606
if e.retry_after_seconds is not None:
603607
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)