fix: Adding ConnectionError to retry mechanism (#822)
This commit fixes issue googleapis#558
Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com>
diff --git a/googleapiclient/http.py b/googleapiclient/http.py
index cf9a509..cd2775a 100644
--- a/googleapiclient/http.py
+++ b/googleapiclient/http.py
@@ -81,6 +81,11 @@
_LEGACY_BATCH_URI = "https://www.googleapis.com/batch"
+if six.PY2:
+ # That's a builtin python3 exception, nonexistent in python2.
+ # Defined to None to avoid NameError while trying to catch it
+ ConnectionError = None
+
def _should_retry_response(resp_status, content):
"""Determines whether a response should be retried.
@@ -177,6 +182,10 @@
# It's important that this be before socket.error as it's a subclass
# socket.timeout has no errorcode
exception = socket_timeout
+ except ConnectionError as connection_error:
+ # Needs to be before socket.error as it's a subclass of
+ # OSError (socket.error)
+ exception = connection_error
except socket.error as socket_error:
# errno's contents differ by platform, so we have to match by name.
if socket.errno.errorcode.get(socket_error.errno) not in {
diff --git a/tests/test_http.py b/tests/test_http.py
index ce27e2e..2c0756e 100644
--- a/tests/test_http.py
+++ b/tests/test_http.py
@@ -132,32 +132,31 @@
def request(self, *args, **kwargs):
if not self.num_errors:
return httplib2.Response(self.success_json), self.success_data
+ elif self.num_errors == 5 and PY3:
+ ex = ConnectionResetError # noqa: F821
+ elif self.num_errors == 4:
+ ex = httplib2.ServerNotFoundError()
+ elif self.num_errors == 3:
+ ex = socket.error()
+ ex.errno = socket.errno.EPIPE
+ elif self.num_errors == 2:
+ ex = ssl.SSLError()
else:
- self.num_errors -= 1
- if self.num_errors == 1: # initial == 2
- raise ssl.SSLError()
- if self.num_errors == 3: # initial == 4
- raise httplib2.ServerNotFoundError()
- else: # initial != 2,4
- if self.num_errors == 2:
- # first try a broken pipe error (#218)
- ex = socket.error()
- ex.errno = socket.errno.EPIPE
+ # Initialize the timeout error code to the platform's error code.
+ try:
+ # For Windows:
+ ex = socket.error()
+ ex.errno = socket.errno.WSAETIMEDOUT
+ except AttributeError:
+ # For Linux/Mac:
+ if PY3:
+ ex = socket.timeout()
else:
- # Initialize the timeout error code to the platform's error code.
- try:
- # For Windows:
- ex = socket.error()
- ex.errno = socket.errno.WSAETIMEDOUT
- except AttributeError:
- # For Linux/Mac:
- if PY3:
- ex = socket.timeout()
- else:
- ex = socket.error()
- ex.errno = socket.errno.ETIMEDOUT
- # Now raise the correct error.
- raise ex
+ ex = socket.error()
+ ex.errno = socket.errno.ETIMEDOUT
+
+ self.num_errors -= 1
+ raise ex
class HttpMockWithNonRetriableErrors(object):
@@ -562,14 +561,14 @@
def test_media_io_base_download_retries_connection_errors(self):
self.request.http = HttpMockWithErrors(
- 4, {"status": "200", "content-range": "0-2/3"}, b"123"
+ 5, {"status": "200", "content-range": "0-2/3"}, b"123"
)
download = MediaIoBaseDownload(fd=self.fd, request=self.request, chunksize=3)
download._sleep = lambda _x: 0 # do nothing
download._rand = lambda: 10
- status, done = download.next_chunk(num_retries=4)
+ status, done = download.next_chunk(num_retries=5)
self.assertEqual(self.fd.getvalue(), b"123")
self.assertEqual(True, done)
@@ -899,13 +898,13 @@
def test_retry_connection_errors_non_resumable(self):
model = JsonModel()
request = HttpRequest(
- HttpMockWithErrors(4, {"status": "200"}, '{"foo": "bar"}'),
+ HttpMockWithErrors(5, {"status": "200"}, '{"foo": "bar"}'),
model.response,
u"https://www.example.com/json_api_endpoint",
)
request._sleep = lambda _x: 0 # do nothing
request._rand = lambda: 10
- response = request.execute(num_retries=4)
+ response = request.execute(num_retries=5)
self.assertEqual({u"foo": u"bar"}, response)
def test_retry_connection_errors_resumable(self):
@@ -918,7 +917,7 @@
request = HttpRequest(
HttpMockWithErrors(
- 4, {"status": "200", "location": "location"}, '{"foo": "bar"}'
+ 5, {"status": "200", "location": "location"}, '{"foo": "bar"}'
),
model.response,
u"https://www.example.com/file_upload",
@@ -927,7 +926,7 @@
)
request._sleep = lambda _x: 0 # do nothing
request._rand = lambda: 10
- response = request.execute(num_retries=4)
+ response = request.execute(num_retries=5)
self.assertEqual({u"foo": u"bar"}, response)
def test_retry(self):