uvicorn
uvicorn copied to clipboard
ConnectionClosedError raised from websockets library
Checklist
- [x] The bug is reproducible against the latest release and/or
master. - [x] There are no similar issues or pull requests to fix it yet.
Describe the bug
I'm running a Django project with channels and uvicorn (as a gunicorn worker).
Occasionally, when the server tries to send a message and the client has disconnected improperly, the following exception is raised from the websockets library:
...
File "channels/consumer.py", line 81, in send
await self.base_send(message)
File "channels/sessions.py", line 236, in send
return await self.real_send(message)
File "uvicorn/protocols/websockets/websockets_impl.py", line 212, in asgi_send
await self.send(data)
File "websockets/protocol.py", line 555, in send
await self.ensure_open()
File "websockets/protocol.py", line 812, in ensure_open
raise self.connection_closed_exc()
ConnectionClosedError
(full traceback under debugging below)
Expected behavior
As a user of channels, I expect not to be required to catch these lower-level exceptions in my message sending code (right? I'm a bit unsure about this). I would expect uvicorn to handle this exception from websockets.
Actual behavior
I have to guard my websocket sending code against exceptions thrown by an implementation detail of the underlying ASGI server. I'm running the daphne ASGI server in development, so this exception handling is unexpected.
Debugging material
Full traceback
CancelledError: null
File "websockets/protocol.py", line 827, in transfer_data
message = await self.read_message()
File "websockets/protocol.py", line 895, in read_message
frame = await self.read_data_frame(max_size=self.max_size)
File "websockets/protocol.py", line 971, in read_data_frame
frame = await self.read_frame(max_size)
File "websockets/protocol.py", line 1051, in read_frame
extensions=self.extensions,
File "websockets/framing.py", line 105, in read
data = await reader(2)
File "asyncio/streams.py", line 674, in readexactly
yield from self._wait_for_data('readexactly')
File "asyncio/streams.py", line 464, in _wait_for_data
yield from self._waiter
ConnectionClosedError: code = 1006 (connection closed abnormally [internal]), no reason
File "uvicorn/protocols/websockets/websockets_impl.py", line 154, in run_asgi
result = await self.app(self.scope, self.asgi_receive, self.asgi_send)
File "uvicorn/middleware/proxy_headers.py", line 45, in __call__
return await self.app(scope, receive, send)
File "uvicorn/middleware/asgi2.py", line 7, in __call__
await instance(receive, send)
File "channels/sessions.py", line 183, in __call__
return await self.inner(receive, self.send)
File "channels/middleware.py", line 41, in coroutine_call
await inner_instance(receive, send)
File "channels/consumer.py", line 59, in __call__
[receive, self.channel_receive], self.dispatch
File "channels/utils.py", line 51, in await_many_dispatch
await dispatch(result)
File "channels/consumer.py", line 73, in dispatch
await handler(message)
File ".../consumers.py", line 63, in userlist
'user': event['user']
File "channels/generic/websocket.py", line 273, in send_json
await super().send(text_data=await self.encode_json(content), close=close)
File "channels/generic/websocket.py", line 211, in send
await super().send({"type": "websocket.send", "text": text_data})
File "channels/consumer.py", line 81, in send
await self.base_send(message)
File "channels/sessions.py", line 236, in send
return await self.real_send(message)
File "uvicorn/protocols/websockets/websockets_impl.py", line 212, in asgi_send
await self.send(data)
File "websockets/protocol.py", line 555, in send
await self.ensure_open()
File "websockets/protocol.py", line 812, in ensure_open
raise self.connection_closed_exc()
Environment
- Running uvicorn 0.11.8 with CPython 3.6.6 on Linux
- Running as a gunicorn worker via
uvicorn.workers.UvicornWorker
Thanks for the detailed report @stefanw .
What would help would be to know if this happens without gunicorn, just to make sure we throw that out of the equation.
Then if you had a minimal example that would be amazing : I'm not that familiar with channels and I'm like missing ideas on how to "mimic" what you describe as a client disconnecting improperly,
Hey @euri10, I added a passing test in PR #758 that asserts a raised exception under the described condition.
The error only appears with the WebSocketProtocol (based on websockets) and not WSProtocol (based on wsproto), that's why the test only tests that protocol.
In the test I'm calling connection_lost on the protocol to simulate a connection close from the transport without sending a close frame and then try to send some data after. I'm not entirely sure this is the proper way to simulate this, but it does reproduce the exception – and only on the WebSocketProtocol implementation!
What would help would be to know if this happens without gunicorn, just to make sure we throw that out of the equation.
This issue is occurring for me without gunicorn (I am using Django channels also).
FYI If I catch & ignore this exception in the send method of my Django channels consumer (see exception above), I run into this issue: https://github.com/encode/uvicorn/issues/244
Closing this as stale. Please feel free to reopen with an MRE if this still happens.