Skip to content

Commit d49bc44

Browse files
authored
Improve retrying failed API server connections (#71)
When the API server got terminated in the middle of a connection (perhaps because of a new deployment), it didn't finish the response properly, and the connection errored out with a `"Connection broken: InvalidChunkLength(got length b'', 0 bytes read)"` error. This change extends the error-retrying logic to retry the request in case of those errors as well.
1 parent d27cafc commit d49bc44

File tree

4 files changed

+26
-6
lines changed

4 files changed

+26
-6
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
Changelog
22
=========
33

4+
[0.5.0](../../releases/tag/v0.5.0) - 2021-09-16
5+
-----------------------------------------------
6+
7+
### Changed
8+
9+
- improved retrying broken API server connections
10+
411
[0.4.0](../../releases/tag/v0.4.0) - 2021-09-07
512
-----------------------------------------------
613

src/apify_client/_errors.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Optional
22

33
import requests
4+
from requests.exceptions import ChunkedEncodingError, ConnectionError, Timeout
45

56

67
class ApifyClientError(Exception):
@@ -69,3 +70,15 @@ def __init__(self, response: requests.models.Response) -> None:
6970
self.name = 'InvalidResponseBodyError'
7071
self.code = 'invalid-response-body'
7172
self.response = response
73+
74+
75+
def _is_retryable_error(e: Exception) -> bool:
76+
if isinstance(e, (InvalidResponseBodyError, ConnectionError, Timeout)):
77+
return True
78+
79+
# This can happen if an API server pod restarts while handling a long-running request
80+
if isinstance(e, ChunkedEncodingError):
81+
if str(e).startswith('("Connection broken: InvalidChunkLength(got length b\'\', 0 bytes read)"'):
82+
return True
83+
84+
return False

src/apify_client/_http_client.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66
from typing import Any, Callable, Dict, Optional
77

88
import requests
9-
from requests.exceptions import ConnectionError, Timeout
109

11-
from ._errors import ApifyApiError, InvalidResponseBodyError
10+
from ._errors import ApifyApiError, InvalidResponseBodyError, _is_retryable_error
1211
from ._types import JSONSerializable
1312
from ._utils import _is_content_type_json, _is_content_type_text, _is_content_type_xml, _retry_with_exp_backoff
1413
from ._version import __version__
@@ -82,10 +81,11 @@ def _make_request(bail: Callable, attempt: int) -> requests.models.Response: #
8281
setattr(response, '_maybe_parsed_body', _maybe_parsed_body)
8382
return response
8483

85-
except (ConnectionError, Timeout, InvalidResponseBodyError) as e:
86-
raise e
8784
except Exception as e:
88-
bail(e)
85+
if _is_retryable_error(e):
86+
raise e
87+
else:
88+
bail(e)
8989

9090
api_error = ApifyApiError(response, attempt)
9191
if response.status_code == HTTPStatus.TOO_MANY_REQUESTS or response.status_code >= 500:

src/apify_client/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.4.0'
1+
__version__ = '0.5.0'

0 commit comments

Comments
 (0)