python-websockets / websockets

Library for building WebSocket servers and clients in Python
https://websockets.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
5.16k stars 513 forks source link

Client doesn't acknowledge when server closes the connection #73

Closed Martiusweb closed 9 years ago

Martiusweb commented 9 years ago

As far as I understand, when WebsocketClientProtocol.close() is called, the closing handshake starts (client sends the FIN packet), and waits until the peer acknowledges (also sends FIN) and closes the TCP connection.

It seems that client's close_connection() will hang because the connection_closed future is never set (Protocol's connection_lost() callback isn't called). In that case, timeout will eventually cancel the future, and things will get cleaned.

The issue is that client.close() will take as long as the timeout value, while it can probably return quickly.

I don't observe this behavior with websockets 1.0.

Here is a simple way to reproduce:

server = yield from websockets.server.serve(
    lambda *a: asyncio.sleep(5), port=8080)

# client
client = yield from websockets.client.connect('ws://localhost:8080/service')
client.timeout = 2

start = loop.time()
yield from client.close()
assert loop.time() - start < 2

try:
    server.close()
    yield from server.wait_closed()
except Exception:
    pass
aaugustin commented 9 years ago

As far as I understand, when WebsocketClientProtocol.close() is called, the closing handshake starts (client sends the FIN packet), and waits until the peer acknowledges (also sends FIN) and closes the TCP connection.

That's incorrect.

When WebsocketClientProtocol.close() is called, the client:

Relevant excerpt from RFC 6455:

The underlying TCP connection, in most normal cases, SHOULD be closed first by the server, so that it holds the TIME_WAIT state and not the client (as this would prevent it from re-opening the connection for 2 maximum segment lifetimes (2MSL), while there is no corresponding server impact as a TIME_WAIT connection is immediately reopened upon a new SYN with a higher seq number).

Martiusweb commented 9 years ago

Sorry, my description wasn't clear enough: we meant the same thing (the server acknowledges the websocket's FIN and close the TCP socket, the client waits for this).

In fact, I investigated this issue further, and it appears that the bug is not in the websockets library but in recent versions of asyncio (the bug exists with python 3.5rc3 but not 3.4.3): the protocol's connection_lost() callback isn't scheduled. If you run the code snippet I attached, the client will timeout after 2 seconds, but it will work fine with python 3.4.

aaugustin commented 9 years ago

Thanks for your investigations! I assume you filed a bug against Python?

aaugustin commented 8 years ago

There's a good chance this was reported again in #76 and fixed.