Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Adding ConnectionError to retry mechanism #822

Merged
merged 2 commits into from Mar 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions googleapiclient/http.py
Expand Up @@ -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.
Expand Down Expand Up @@ -177,6 +182,10 @@ def _retry_request(
# 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 {
Expand Down
59 changes: 29 additions & 30 deletions tests/test_http.py
Expand Up @@ -132,32 +132,31 @@ def __init__(self, num_errors, success_json, success_data):
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):
Expand Down Expand Up @@ -562,14 +561,14 @@ def test_media_io_base_download_handle_4xx(self):

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)
Expand Down Expand Up @@ -899,13 +898,13 @@ def test_no_retry_connection_errors(self):
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):
Expand All @@ -918,7 +917,7 @@ def test_retry_connection_errors_resumable(self):

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",
Expand All @@ -927,7 +926,7 @@ def test_retry_connection_errors_resumable(self):
)
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):
Expand Down