-
Notifications
You must be signed in to change notification settings - Fork 104
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
disconnect detection broken when running with trio #210
Comments
@davidbrochart Given that it seems to be a trio/asyncio problem I found #208 and subsequently tried out your fork https://github.com/davidbrochart/anycorn (the fix-tests branch). The repro fails there as well, both with trio and asyncio - so whatever magic that currently is making it work on asyncio seems to have been lost. |
After a lot of digging I'm pretty sure this is actually a bug in starlette and will open an issue there instead. |
Starlette has not escalated the discussion about this problem to a proper issue, but I have made several comments in there about my research around the problem: encode/starlette#2094 Hypercorn could work around it by creating a wrapper object for receiving that sidesteps trio checkpointing at the beginning of class ReceiveWrapper:
def __init__(self, receive_channel: trio.MemoryReceiveChannel[ASGIReceiveEvent]) -> None:
self._receive_channel = receive_channel
# starlette requires that a receiver does not checkpoint before attempting
# to return the first event, which `trio.MemoryreceiveChannel.receive` does.
async def receive(self) -> ASGIReceiveEvent:
try:
return self._receive_channel.receive_nowait()
except trio.WouldBlock:
pass
await trio.lowlevel.checkpoint()
return await self._receive_channel.receive()
class TaskGroup:
def __init__(self) -> None:
self._nursery: Optional[trio._core._run.Nursery] = None
self._nursery_manager: Optional[trio._core._run.NurseryManager] = None
async def spawn_app(
self,
app: AppWrapper,
config: Config,
scope: Scope,
send: Callable[[Optional[ASGISendEvent]], Awaitable[None]],
) -> Callable[[ASGIReceiveEvent], Awaitable[None]]:
app_send_channel, app_receive_channel = trio.open_memory_channel[ASGIReceiveEvent](
config.max_app_queue_size
)
self._nursery.start_soon(
_handle,
app,
config,
scope,
ReceiveWrapper(app_receive_channel).receive,
send,
trio.to_thread.run_sync,
trio.from_thread.run,
)
return app_send_channel.send But the ASGI spec certainly doesn't require that |
@jakkdl I opened davidbrochart/anycorn#8, if using |
thanks! I'll let the-people-that-makes-those-decisions know :) |
I'm thinking I need to alter the documentation to discourage trying to detect disconnections and rather ensure that the right context managers/finally clauses are used to cleanup. Quart will cancel the request handling task on disconnect so the |
yeah after attempting to write different potential fixes for starlette I mostly settled on the current interface being very bad, and there being better ways of handling it. If the disconnect occurs while receiving data with |
The problem
abort/disconnect events when a request is aborted before it is finished does not appear to propagate to the running application, when running with trio as backend. It does work on asyncio.
I'm currently trying to dig into the codebase and figure out what could be causing this, and can write a PR if/when I figure out a fix.
Repro (trio)
Setup server
On a different terminal:
or even with http2
Wait for a few secs, and abort the connection (CTRL + C). Notice that on server side, it us not able to detect the disconnection, and keeps on looping:
Code
main.py
requirements.txt
working example with asyncio
Setup server
On a different terminal:
Wait for a few secs, and abort the connection (CTRL + C). On the server side it does detect the disconnect and kills the loop.
Code
main.py (identical, except replacing trio with asyncio)
requirements.txt
system info
tested on linux, in clean virtualenvs and tox, on python 3.9, 3.11 and 3.12
The text was updated successfully, but these errors were encountered: