-
-
Notifications
You must be signed in to change notification settings - Fork 474
feat(core): Cancel handler functions / responses when client disconnects #3984
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
base: main
Are you sure you want to change the base?
Conversation
52612cc
to
002ceb4
Compare
async with anyio.create_task_group() as tg: | ||
tg.start_soon( | ||
partial( | ||
self._handle_response_cycle, | ||
scope=scope, | ||
send=send, | ||
receive=receive, | ||
request=request, | ||
route_handler=route_handler, | ||
parameter_model=parameter_model, | ||
cancel_scope=tg.cancel_scope, | ||
), | ||
) | ||
tg.start_soon(self._listen_for_disconnect, request, tg.cancel_scope) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a more performant, but we can't really use it for reasons of anyio/trio compatibility.
async with anyio.create_task_group() as tg: | |
tg.start_soon( | |
partial( | |
self._handle_response_cycle, | |
scope=scope, | |
send=send, | |
receive=receive, | |
request=request, | |
route_handler=route_handler, | |
parameter_model=parameter_model, | |
cancel_scope=tg.cancel_scope, | |
), | |
) | |
tg.start_soon(self._listen_for_disconnect, request, tg.cancel_scope) | |
tasks = [ | |
asyncio.create_task( | |
self._handle_response_cycle( | |
scope=scope, | |
send=send, | |
receive=receive, | |
request=request, | |
route_handler=route_handler, | |
parameter_model=parameter_model, | |
) | |
), | |
asyncio.create_task(request._listen_for_disconnect()), | |
] | |
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) | |
done.pop().result() | |
if done: | |
done.pop().result() | |
if pending: | |
pending = pending.pop() | |
pending.cancel() | |
await pending | |
pending.result() |
What I could see is maybe having this be an opt-in feature. That would introduce additional complexity, but leave it up to the user to decide whether the overhead is worth it. I'd suspect though, that in most cases, a simple timeout on the request would suffice |
Make sense. For most of use cases, the action is not so heavy that we should cancel the handler when use disconnected. User disconnected should be considered as an exception, not a common situation. The idempotency of API should be handled by backend logic, according to the HTTP method. |
poc for listening for early client disconnects and cancelling handler functions / responses in progress.
I currently consider this not viable, since it imposes a 50% performance penalty when looking at throughput of a simple plaintext benchmark. This is due to the overhead introduced by running the response cycle in an
anyio.TaskGroup
, which is considerable. There is a pure asyncio solution, which is a bit more performant, but overhead is still in the same ballpark.This also surfaces some flaws in how we're handling static files, which is what's causing most of the test failures, and would need to be addressed.
Open questions