Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

The web socket handler is stuck in an undefined state if the client did not close properly #5301

Open
mykola-mokhnach opened this issue Nov 30, 2020 · 11 comments
Labels

Comments

@mykola-mokhnach
Copy link
Contributor

馃悶 Describe the bug

The server socket listener is forever stuck in web socket messages loop if the client does not close the socket properly

馃挕 To Reproduce

  1. Create a new web socket handler and set the heartbeat timeout to 30 seconds
  2. Connect to the created web socket with the websocat tool (brew install websockat on macOS)
  3. Close the previously created websocat client by pressing Ctrl+C
  4. Wait until the heartbeat timeout expires

An exception is written to the log. The web socket does never get closed and is practically stuck in messages loop.

馃挕 Expected behavior
The server should properly close the client connection after STR and exit the messages loop

馃搵 Logs/tracebacks

ERROR:asyncio:Task exception was never retrieved
future: <Task finished name='Task-675' coro=<WebSocketWriter.ping() done, defined at /root/.local/share/virtualenvs/live-session-manager-rV8PaGpt/lib/python3.8/site-packages/aiohttp/http_websocket.py:669> exception=ConnectionResetError('Cannot write to closing transport')>
Traceback (most recent call last):
  File "/root/.local/share/virtualenvs/live-session-manager-rV8PaGpt/lib/python3.8/site-packages/aiohttp/http_websocket.py", line 673, in ping
    await self._send_frame(message, WSMsgType.PING)
  File "/root/.local/share/virtualenvs/live-session-manager-rV8PaGpt/lib/python3.8/site-packages/aiohttp/http_websocket.py", line 650, in _send_frame
    self._write(header + message)
  File "/root/.local/share/virtualenvs/live-session-manager-rV8PaGpt/lib/python3.8/site-packages/aiohttp/http_websocket.py", line 660, in _write
    raise ConnectionResetError("Cannot write to closing transport")
ConnectionResetError: Cannot write to closing transport

馃搵 Your version of the Python

$ python --version
3.8.6

馃搵 Your version of the aiohttp/yarl/multidict distributions

$ python -m pip show aiohttp
Name: aiohttp
Version: 3.7.2
Summary: Async http client/server framework (asyncio)
Home-page: https://github.com/aio-libs/aiohttp
Author: Nikolay Kim
Author-email: fafhrd91@gmail.com
License: Apache 2
Location: /root/.local/share/virtualenvs/live-session-manager-rV8PaGpt/lib/python3.8/site-packages
Requires: chardet, attrs, async-timeout, yarl, multidict, typing-extensions
Required-by: aiohttp-swagger3
$ python -m pip show multidict
Name: multidict
Version: 5.0.2
Summary: multidict implementation
Home-page: https://github.com/aio-libs/multidict
Author: Andrew Svetlov
Author-email: andrew.svetlov@gmail.com
License: Apache 2
Location: /root/.local/share/virtualenvs/live-session-manager-rV8PaGpt/lib/python3.8/site-packages
Requires: 
Required-by: yarl, aiohttp
$ python -m pip show yarl
Name: yarl
Version: 1.6.3
Summary: Yet another URL library
Home-page: https://github.com/aio-libs/yarl/
Author: Andrew Svetlov
Author-email: andrew.svetlov@gmail.com
License: Apache 2
Location: /root/.local/share/virtualenvs/live-session-manager-rV8PaGpt/lib/python3.8/site-packages
Requires: idna, multidict
Required-by: aiohttp

馃搵 Additional context

    async def handle_establish_channel(self: T, request: web.Request) -> web.WebSocketResponse:
        ws = web.WebSocketResponse(heartbeat=heartbeat_interval_sec / 1.3)
        await ws.prepare(request)

        async for ws_message in ws:
            # the loop is stuck here
            # and never reaches the `return` statement
        return ws
@asvetlov
Copy link
Member

Thanks for the report. I have a plan for working on it; I hope the fix will be a part of aiohttp 3.8

@mykola-mokhnach
Copy link
Contributor Author

Thanks @asvetlov

@eino
Copy link

eino commented Oct 11, 2022

Hi, thanks for this awesome project.
Is there perchance any update on the issue? I have met it as well randomly, where the server doesn't notice a client has disconnected (probably because of an improper disconnection), and keeps trying to send data.
It's not systematic, though.
I run python 3.9 and aiohttp 3.8.1

ConnectionResetError: Cannot write to closing transport
ERROR:asyncio:Task exception was never retrieved
future: <Task finished name='Task-5179' coro=<WslinkHandler.sendWrappedMessage() done, defined at /opt/app/.venv/lib/python3.9/site-packages/wslink/backends/aiohttp/__init__.py:542> exception=ConnectionResetError('Cannot write to closing transport')>
Traceback (most recent call last):
  File (...calling code )
    await self._writer.send(data, binary=False, compress=compress)
  File "/opt/app/.venv/lib/python3.9/site-packages/aiohttp/http_websocket.py", line 690, in send
    await self._send_frame(message, WSMsgType.TEXT, compress)
  File "/opt/app/.venv/lib/python3.9/site-packages/aiohttp/http_websocket.py", line 653, in _send_frame
    self._write(header + message)

@Dreamsorcerer
Copy link
Member

I would assume that @asvetlov hasn't had a chance to look at it yet. If anyone else wants to propose a solution, we're happy to look at a PR.

@eino
Copy link

eino commented Oct 12, 2022

I investigated the issue some more, and I may have found the original reason.
It seems in case of WebSocketError (also any other Exception), the Websocket is closed, but iteration on it isn't stopped

This is because a msg with type ERROR is returned,

aiohttp/aiohttp/web_ws.py

Lines 458 to 461 in 50fec95

except WebSocketError as exc:
self._close_code = exc.code
await self.close(code=exc.code)
return WSMessage(WSMsgType.ERROR, exc, None)

but this type doesn't raise a StopAsyncIteration:

aiohttp/aiohttp/web_ws.py

Lines 512 to 516 in 50fec95

async def __anext__(self) -> WSMessage:
msg = await self.receive()
if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.CLOSED):
raise StopAsyncIteration
return msg

Thus a client will continue waiting forever in the closed websocket iterator. And, (in our case), as it considers the socket opens, also tries to write to it (hence the "ConnectionResetError: Cannot write to closing transport" error).

As the WSMsgType.ERROR is only raised in 2 cases where the socket is closed, this msg type should cause the iteration to stop.
It would be this change: eino@60ce76a
I can open the PR if it looks good, what do you think?

@Dreamsorcerer
Copy link
Member

Potentially promising. Do you think you can add a test case that reproduces the issue now?

@eino
Copy link

eino commented Oct 12, 2022

Actually that's not the cause of the issue. On next call, as connection is closed, receive returns WS_CLOSED_MESSAGE (line 404) thus terminate the iteration.

@anpr
Copy link

anpr commented Feb 13, 2024

What's the current status of this? This issue is hurting us with an implementation of a websocket server.

@bdraco
Copy link
Member

bdraco commented Feb 13, 2024

This might already be fixed in 3.9.x. Can you verify the issue still persists?

@bdraco
Copy link
Member

bdraco commented Feb 13, 2024

This looks fixed

Reproducer on older aiohttp

from aiohttp import web


heartbeat_interval_sec = 30


async def websocket_handler(request):
    ws = web.WebSocketResponse(heartbeat=heartbeat_interval_sec / 1.3)
    await ws.prepare(request)
    print ("WebSocket connection opened")
    async for ws_message in ws:
        pass
        # the loop is stuck here
        # and never reaches the `return` statement
    print("WebSocket connection closed")
    return ws


app = web.Application()
app.add_routes(
    [
        web.get("/land/websocket_test", websocket_handler),
    ]
)

if __name__ == "__main__":
    web.run_app(app, host="0.0.0.0", port=8000)
% python3 5301.py
======== Running on http://0.0.0.0:8000 ========
(Press CTRL+C to quit)
WebSocket connection opened
WebSocket connection closed
WebSocket connection opened
WebSocket connection closed
(venv) bdraco@MacBook-Pro aiohttp % websocat ws://0.0.0.0:8000/land/websocket_test
^C
(venv) bdraco@MacBook-Pro aiohttp % websocat ws://0.0.0.0:8000/land/websocket_test
^C

@bdraco
Copy link
Member

bdraco commented Feb 13, 2024

@mykola-mokhnach Can you still reproduce this after #7978 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants