Skip to content

Commit 147e845

Browse files
authored
feat: add support to retry known connection errors (#375)
1 parent 1aff914 commit 147e845

File tree

2 files changed

+49
-38
lines changed

2 files changed

+49
-38
lines changed

google/resumable_media/requests/_request_helpers.py

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
This utilities are explicitly catered to ``requests``-like transports.
1818
"""
1919

20+
import http.client
2021
import requests.exceptions
2122
import urllib3.exceptions # type: ignore
2223

@@ -35,10 +36,16 @@
3536
_DEFAULT_READ_TIMEOUT = 60
3637

3738
_CONNECTION_ERROR_CLASSES = (
39+
http.client.BadStatusLine,
40+
http.client.IncompleteRead,
41+
http.client.ResponseNotReady,
3842
requests.exceptions.ConnectionError,
3943
requests.exceptions.ChunkedEncodingError,
4044
requests.exceptions.Timeout,
45+
urllib3.exceptions.PoolError,
4146
urllib3.exceptions.ProtocolError,
47+
urllib3.exceptions.SSLError,
48+
urllib3.exceptions.TimeoutError,
4249
ConnectionError, # Python 3.x only, superclass of ConnectionResetError.
4350
)
4451

tests/unit/requests/test__helpers.py

+42-38
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,18 @@ def raise_response():
165165

166166
@mock.patch("time.sleep")
167167
@mock.patch("random.randint")
168-
def test_success_with_retry_connection_error(self, randint_mock, sleep_mock):
169-
randint_mock.side_effect = [125, 625, 375]
168+
def test_retry_success_http_standard_lib_connection_errors(
169+
self, randint_mock, sleep_mock
170+
):
171+
randint_mock.side_effect = [125, 625, 500, 875, 375]
170172

171-
response = _make_response(http.client.NOT_FOUND)
173+
status_code = int(http.client.OK)
174+
response = _make_response(status_code)
172175
responses = [
173-
ConnectionResetError, # Subclass of ConnectionError
174-
urllib3.exceptions.ConnectionError,
175-
requests.exceptions.ConnectionError,
176+
http.client.BadStatusLine(""),
177+
http.client.IncompleteRead(""),
178+
http.client.ResponseNotReady,
179+
ConnectionError,
176180
response,
177181
]
178182
func = mock.Mock(side_effect=responses, spec=[])
@@ -183,28 +187,29 @@ def test_success_with_retry_connection_error(self, randint_mock, sleep_mock):
183187
)
184188

185189
assert ret_val == responses[-1]
186-
187-
assert func.call_count == 4
188-
assert func.mock_calls == [mock.call()] * 4
189-
190-
assert randint_mock.call_count == 3
191-
assert randint_mock.mock_calls == [mock.call(0, 1000)] * 3
192-
193-
assert sleep_mock.call_count == 3
190+
assert func.call_count == 5
191+
assert func.mock_calls == [mock.call()] * 5
192+
assert randint_mock.call_count == 4
193+
assert randint_mock.mock_calls == [mock.call(0, 1000)] * 4
194+
assert sleep_mock.call_count == 4
194195
sleep_mock.assert_any_call(1.125)
195196
sleep_mock.assert_any_call(2.625)
196-
sleep_mock.assert_any_call(4.375)
197+
sleep_mock.assert_any_call(4.500)
198+
sleep_mock.assert_any_call(8.875)
197199

198200
@mock.patch("time.sleep")
199201
@mock.patch("random.randint")
200-
def test_success_with_retry_chunked_encoding_error(self, randint_mock, sleep_mock):
201-
randint_mock.side_effect = [125, 625, 375]
202+
def test_retry_success_requests_lib_connection_errors(
203+
self, randint_mock, sleep_mock
204+
):
205+
randint_mock.side_effect = [125, 625, 500, 875]
202206

203207
status_code = int(http.client.OK)
204208
response = _make_response(status_code)
205209
responses = [
210+
requests.exceptions.ConnectionError,
206211
requests.exceptions.ChunkedEncodingError,
207-
requests.exceptions.ChunkedEncodingError,
212+
requests.exceptions.Timeout,
208213
response,
209214
]
210215
func = mock.Mock(side_effect=responses, spec=[])
@@ -215,27 +220,27 @@ def test_success_with_retry_chunked_encoding_error(self, randint_mock, sleep_moc
215220
)
216221

217222
assert ret_val == responses[-1]
218-
219-
assert func.call_count == 3
220-
assert func.mock_calls == [mock.call()] * 3
221-
222-
assert randint_mock.call_count == 2
223-
assert randint_mock.mock_calls == [mock.call(0, 1000)] * 2
224-
225-
assert sleep_mock.call_count == 2
223+
assert func.call_count == 4
224+
assert func.mock_calls == [mock.call()] * 4
225+
assert randint_mock.call_count == 3
226+
assert randint_mock.mock_calls == [mock.call(0, 1000)] * 3
227+
assert sleep_mock.call_count == 3
226228
sleep_mock.assert_any_call(1.125)
227229
sleep_mock.assert_any_call(2.625)
230+
sleep_mock.assert_any_call(4.500)
228231

229232
@mock.patch("time.sleep")
230233
@mock.patch("random.randint")
231-
def test_success_with_retry_client_timeout(self, randint_mock, sleep_mock):
232-
randint_mock.side_effect = [125, 625, 375]
234+
def test_retry_success_urllib3_connection_errors(self, randint_mock, sleep_mock):
235+
randint_mock.side_effect = [125, 625, 500, 875, 375]
233236

234237
status_code = int(http.client.OK)
235238
response = _make_response(status_code)
236239
responses = [
237-
requests.exceptions.Timeout,
238-
requests.exceptions.Timeout,
240+
urllib3.exceptions.PoolError(None, ""),
241+
urllib3.exceptions.ProtocolError,
242+
urllib3.exceptions.SSLError,
243+
urllib3.exceptions.TimeoutError,
239244
response,
240245
]
241246
func = mock.Mock(side_effect=responses, spec=[])
@@ -246,16 +251,15 @@ def test_success_with_retry_client_timeout(self, randint_mock, sleep_mock):
246251
)
247252

248253
assert ret_val == responses[-1]
249-
250-
assert func.call_count == 3
251-
assert func.mock_calls == [mock.call()] * 3
252-
253-
assert randint_mock.call_count == 2
254-
assert randint_mock.mock_calls == [mock.call(0, 1000)] * 2
255-
256-
assert sleep_mock.call_count == 2
254+
assert func.call_count == 5
255+
assert func.mock_calls == [mock.call()] * 5
256+
assert randint_mock.call_count == 4
257+
assert randint_mock.mock_calls == [mock.call(0, 1000)] * 4
258+
assert sleep_mock.call_count == 4
257259
sleep_mock.assert_any_call(1.125)
258260
sleep_mock.assert_any_call(2.625)
261+
sleep_mock.assert_any_call(4.500)
262+
sleep_mock.assert_any_call(8.875)
259263

260264
@mock.patch("time.sleep")
261265
@mock.patch("random.randint")

0 commit comments

Comments
 (0)