uvicorn: TypeError when connection is closed in connect()
I have the same problem then #185 but I think, I can reproduce it.
When you close a connection in the connect() method of a WebsocketConsumer, it works fine with daphne but uvicorn raises a TypeError
Traceback (most recent call last):
File "/home/ossi/src/channels-examples/.venv/lib/python3.7/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 140, in run_asgi
result = await asgi(self.asgi_receive, self.asgi_send)
File "/home/ossi/src/channels-examples/.venv/lib/python3.7/site-packages/channels/sessions.py", line 179, in __call__
return await self.inner(receive, self.send)
File "/home/ossi/src/channels-examples/.venv/lib/python3.7/site-packages/channels/middleware.py", line 41, in coroutine_call
await inner_instance(receive, send)
File "/home/ossi/src/channels-examples/.venv/lib/python3.7/site-packages/channels/consumer.py", line 59, in __call__
[receive, self.channel_receive], self.dispatch
File "/home/ossi/src/channels-examples/.venv/lib/python3.7/site-packages/channels/utils.py", line 59, in await_many_dispatch
await task
File "/home/ossi/src/channels-examples/.venv/lib/python3.7/site-packages/channels/utils.py", line 51, in await_many_dispatch
result = task.result()
File "/home/ossi/src/channels-examples/.venv/lib/python3.7/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 222, in asgi_receive
data = await self.recv()
File "/home/ossi/src/channels-examples/.venv/lib/python3.7/site-packages/websockets/protocol.py", line 419, in recv
return_when=asyncio.FIRST_COMPLETED,
File "/usr/lib/python3.7/asyncio/tasks.py", line 361, in wait
fs = {ensure_future(f, loop=loop) for f in set(fs)}
File "/usr/lib/python3.7/asyncio/tasks.py", line 361, in <setcomp>
fs = {ensure_future(f, loop=loop) for f in set(fs)}
File "/usr/lib/python3.7/asyncio/tasks.py", line 592, in ensure_future
raise TypeError('An asyncio.Future, a coroutine or an awaitable is '
TypeError: An asyncio.Future, a coroutine or an awaitable is required
I created this branch on the django-channels-example repository: https://github.com/ostcar/channels-examples/tree/uvicorn-test
All I changed was, that the websocket connection is always closed: https://github.com/andrewgodwin/channels-examples/compare/master...ostcar:uvicorn-test
I think the problem is, that you call websockets.WebSocketServerProtocol.recv() before the websocket connection is open so websockets.WebSocketServerProtocol.connection_open() was not called. In this case the attribute transfer_data_task is still None. So when websockets.WebSocketServerProtocol.recv() calls
yield from asyncio.wait(
[pop_message_waiter, self.transfer_data_task],
loop=self.loop,
return_when=asyncio.FIRST_COMPLETED,
)
it tries to await None what causes the exception.
I don’t know, if this is a but in the websockets package or if you should check self.closed before calling self.recv()
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 7
- Comments: 18 (8 by maintainers)
Commits related to this issue
- Fix recv() errors when closing during handshake Fixes #244. The unit test already existed, but it logged exceptions that weren't being noticed. * websockets_impl: `transfer_data_task` is unset if th... — committed to adamhooper/uvicorn by adamhooper 4 years ago
- Fix recv() errors when closing during handshake Fixes #244. The unit test already existed, but it logged exceptions that weren't being noticed. * websockets_impl: `transfer_data_task` is unset if th... — committed to adamhooper/uvicorn by adamhooper 4 years ago
- Fix recv() errors when closed during handshake [closes #244] Previously, if the application tried to close the connection before the handshake completed, uvicorn would log: TypeError: An asynci... — committed to adamhooper/uvicorn by adamhooper 4 years ago
- Fix recv() errors when closed during handshake (#704) [closes #244] Previously, if the application tried to close the connection before the handshake completed, uvicorn would log: TypeErro... — committed to encode/uvicorn by adamhooper 4 years ago
Here is a minimal example without using django:
To test it, you have to start it with an asgi server an open a websocket connection.
With daphne:
With uvicorn and websockets:
With uvicorn and wsproto:
The error seems to be, that the
while True:loop does not break. But when I look into the django channels code, it seems, that django channels also does not close the loop.The loop in django channels is here: https://github.com/django/channels/blob/03d221792e6881477911728615a6d51ec9fab73b/channels/utils.py#L45
The
closemessage is send here: https://github.com/django/channels/blob/03d221792e6881477911728615a6d51ec9fab73b/channels/generic/websocket.py#L226An exception, that could break the loop is raised when
websocket.disconnectis handled: https://github.com/django/channels/blob/03d221792e6881477911728615a6d51ec9fab73b/channels/generic/websocket.py#L241@tomchristie ping? I have reduced the issue by enabling logging in Uvicorn’s own test suite. I also submitted pull request #704 with a sketch of a fix. Are you still keen to dig into it? 😃
My workaround is to open the connection and close it immediately.
https://github.com/encode/uvicorn/issues/244#issuecomment-482893968
Is there any way at all I can be of any more help? I’ve helped diagnose, I’ve authored a pull request, I’ve written helpful notes on all issues I could find that seemed relevant. I’m doing my best, and I’m prepared to do better. Please direct me.
Our project experiences this stack trace >35,000 times per day. I worry that real, as-yet unknown errors are hiding among all those duplicates.
Turns out this error was being logged – and ignored – in the unit-test suite already.
I’m not 100% confident in my reading of the ASGI-to-websockets spec. The Websockets spec (as cited in the
websocketscodebase) suggests it’s wrong to callrecv()before a successful handshake – which is why this stack trace is logged. But the ASGI spec doesn’t say anything.For all I know, all real-world ASGI applications call
recv()before the handshakes completes, whether that’s ideal or not. So my pull request [#704] makesrecv()return an error message – the same one that would be returned after unexpected disconnect.