Hi,
in my main loop I'm trying to capture
except (GracyException, GracyParseFailed)
hoping that this will catch any GracyError caused by problems interfacing with
a API.
Alas every week or so a httpx.ReadTimeout causes my main loop to crash.
Also it seems that no retry is attempted.
The lowest error is a
"SSLWantReadError" - anyio/streams/tls.py in _call_sslobject_method at line 130
this bubbles up as
"CancelledError" - asyncio/locks.py in wait at line 213
"TimeoutError" - anyio/_core/_tasks.py in exit at line 118
"ReadTimeout" - httpcore/_exceptions.py in map_exceptions at line 14
before it hits my code in the function defined similar to the code in the
"# 2. Define your Graceful API" example.
I think it would be better if any uncaught httpx errors would turn into GracyException so that trying to capture GracyException is enough to notice that Gracy has given up.
Some more info: I've defined a REQUEST_TIMEOUT = 61.0 in my API, from the Sentry error log it looks like
there's 63 seconds between the start of the request and the error happening.
I have a Graceful retry defined but according to the log, no retry was attempted.
Slightly cleaned up code with some names changed / parameters removed:
class MyApiEndpoint(BaseEndpoint):
GET_MYSTUFF = "...remove..."
# 2. Define your Graceful API
class MyAPI(Gracy[str]):
class Config: # type: ignore
BASE_URL = "https://<REMOVED>/"
REQUEST_TIMEOUT = 61.0
SETTINGS = GracyConfig(
log_request=LogEvent(LogLevel.DEBUG),
log_response=LogEvent(LogLevel.INFO, "{URL} took {ELAPSED}"),
parser={
"default": lambda r: r.json()
},
throttling=GracefulThrottle(
rules=ThrottleRule(url_pattern=r".*",
max_requests=1,
per_time=datetime.timedelta(seconds=2)),
),
retry=GracefulRetry(
delay=30,
max_attempts=5,
delay_modifier=1.5,
retry_on=None,
log_before=LogEvent(LogLevel.INFO),
log_after=LogEvent(LogLevel.WARNING),
log_exhausted=LogEvent(LogLevel.CRITICAL),
behavior="break",
)
)
async def get_mystuff(self,
... removed ...
) -> Awaitable[dict]:
params = {... removed ... }
# the line below is where the httpx.timeout arrives
return await self.get(MyApiEndpoint.GET_MYSTUFF, params)
The version of Gracy used is 1.11.2 - I updated today to 1.12 and will report if this happens in version 1.12 as well.
Stack trace (with a few things removed)
SSLWantReadError: The operation did not complete (read) (_ssl.c:2546)
File "anyio/streams/tls.py", line 130, in _call_sslobject_method
result = func(*args)
File "ssl.py", line 921, in read
v = self._sslobj.read(len)
CancelledError: null
File "httpcore/backends/asyncio.py", line 34, in read
return await self._stream.receive(max_bytes=max_bytes)
File "anyio/streams/tls.py", line 195, in receive
data = await self._call_sslobject_method(self._ssl_object.read, max_bytes)
File "anyio/streams/tls.py", line 137, in _call_sslobject_method
data = await self.transport_stream.receive()
File "anyio/_backends/_asyncio.py", line 1265, in receive
await self._protocol.read_event.wait()
File "asyncio/locks.py", line 213, in wait
await fut
TimeoutError: null
File "httpcore/_exceptions.py", line 10, in map_exceptions
yield
File "httpcore/backends/asyncio.py", line 32, in read
with anyio.fail_after(timeout):
File "anyio/_core/_tasks.py", line 118, in exit
raise TimeoutError
ReadTimeout: null
File "httpx/_transports/default.py", line 60, in map_httpcore_exceptions
yield
File "httpx/_transports/default.py", line 353, in handle_async_request
resp = await self._pool.handle_async_request(req)
File "httpcore/_async/connection_pool.py", line 253, in handle_async_request
raise exc
File "httpcore/_async/connection_pool.py", line 237, in handle_async_request
response = await connection.handle_async_request(request)
File "httpcore/_async/connection.py", line 90, in handle_async_request
return await self._connection.handle_async_request(request)
File "httpcore/_async/http11.py", line 112, in handle_async_request
raise exc
File "httpcore/_async/http11.py", line 91, in handle_async_request
) = await self._receive_response_headers(**kwargs)
File "httpcore/_async/http11.py", line 155, in _receive_response_headers
event = await self._receive_event(timeout=timeout)
File "httpcore/_async/http11.py", line 191, in _receive_event
data = await self._network_stream.read(
File "httpcore/backends/asyncio.py", line 31, in read
with map_exceptions(exc_map):
File "contextlib.py", line 155, in exit
self.gen.throw(typ, value, traceback)
File "httpcore/_exceptions.py", line 14, in map_exceptions
raise to_exc(exc)
ReadTimeout: null
File "REMOVED.py", line 203, in
asyncio.run(main(days=7))
File "asyncio/runners.py", line 190, in run
return runner.run(main)
File "asyncio/runners.py", line 118, in run
return self._loop.run_until_complete(task)
File "asyncio/base_events.py", line 653, in run_until_complete
return future.result()
File "REMOVED.py", line 184, in main
pl = await REMOVED.REMOVED(REMOVED)
File "REMOVED.py", line 150, in get_playlist
return await self.get(REMOVED.REMOVED, params)
File "gracy/_core.py", line 322, in get
return await self._request("GET", endpoint, endpoint_args, *args, **kwargs)
File "gracy/_core.py", line 313, in _request
return await graceful_request
File "gracy/_core.py", line 186, in _gracify
result = await request()
File "httpx/_client.py", line 1533, in request
return await self.send(request, auth=auth, follow_redirects=follow_redirects)
File "httpx/_client.py", line 1620, in send
response = await self._send_handling_auth(
File "httpx/_client.py", line 1648, in _send_handling_auth
response = await self._send_handling_redirects(
File "httpx/_client.py", line 1685, in _send_handling_redirects
response = await self._send_single_request(request)
File "httpx/_client.py", line 1722, in _send_single_request
response = await transport.handle_async_request(request)
File "httpx/_transports/default.py", line 352, in handle_async_request
with map_httpcore_exceptions():
File "contextlib.py", line 155, in exit
self.gen.throw(typ, value, traceback)
File "httpx/_transports/default.py", line 77, in map_httpcore_exceptions
raise mapped_exc(message) from exc