I'm converting a Flask app to Quart and trying not to change too much, so for now I'm making requests on the server using the requests
library, and just wrapping them in run_sync
.
So I converted:
response: Response = session.request(
method,
url,
params=params,
data=data,
headers=headers,
json=json,
files=files,
cookies=cookies,
timeout=(connect_timeout, timeout),
)
to
response: Response = await run_sync(lambda: session.request(
method,
url,
params=params,
data=data,
headers=headers,
json=json,
files=files,
cookies=cookies,
timeout=(connect_timeout, timeout),
))()
This all works fine except I've experienced a small but significant uptick in errors that I'm unable to reproduce locally. I can't tell right now if the errors come from some kind of timeout or some other edge case when working with requests
+ async + Quart's run_sync
. If I use Quart, but just revert the session.request(...)
to a synchronous call, the uptick in errors goes away.
I'm using gunicorn with UvicornWorker
to run the app, using uvloop
for the async framework.
It may line up with a CancelledError
that I see in logs, but I'm not sure, and not sure why the session.request
coroutine would be getting cancelled.
What might be causing this?
Aside from other smaller issues, I realized what I was missing is documented here.
Quart throws a CancelledError
whenever a client disconnects early. For my particular application this happens fairly regularly and I was misunderstanding how that works. An await
ing task far down in the stack will throw a CancelledError
when its distant parent signals cancellation. So for example in a proxy application that waits for a call to a downstream, a CancelledError
may often be raised when waiting for response headers from the downstream since that's where the bulk of the waiting time happens. This doesn't have anything to do with the downstream request, it's just a signal that the upstream client to the server is no longer connected.
In my case I plan to handle these CancelledError
s at the top of the application and send back simple http 408 responses.
This differs from Flask / other non-async frameworks.