websockets
websockets copied to clipboard
Enabling SO_REUSEADDR and/or REUSEPORT on server connections
By default, asyncio.loop.create_server should be setting SO_REUSEADDR (if reuse_address is not set, it will be set to True on Unix, which I'm on). But it seems not to be active. The server is doing this:
async with websockets.serve(handler, host, port, ssl=None) as ws_server:
await ws_server.serve_forever()
Cancelling this task while there is a connected client causes some socket (I think the client socket, rather than the master listening socket) to be left hanging. Setting REUSEADDR may solve this, or if not, having an option to set REUSEPORT would do the same.
Is this already possible? I couldn't find anything in the documentation explaining how to do it.
From the documentation of serve():
Any other keyword arguments are passed the event loop’s create_server() method.
So you can pass reuse_address=True or reuse_port=True to websockets.server.serve() and they will be passed down to asyncio.loop.create_server().
websockets isn't doing anything with reuse_address by default. I recommend you experiment with plain TCP sockets, with asyncio.loop.create_server(), to understand the behavior. Then I am highly confident that you will see the same behavior when adding websockets on top.
Yeah, that's the part that surprises me. I'm familiar with the normal behaviour with and without REUSEADDR, and it's generally fine with the default of None. That's what I expected from the docs.
(I missed the part about other kwargs though, thanks. That might at least let me set REUSEPORT.)
I see a problem with your code though: I think it runs two servers.
serve() starts a server and stops it when exiting the context manager.
serve_forever() starts another server and stops it when it's cancelled.
Perhaps this could explain the weird behavior you're seeing?
Reopening (at least) as a documentation issue to clarify that serve() and serve_forever() are mutually incompatible.
Interesting. That definitely could be a part of the problem. The code previously waited on a different signal (waiting on an FD), but I changed that a little while back to no longer need that, and used serve_forever since it seemed plausible. Currently, the websockets docs say that serve_forever is just "see asyncio's serve_forever", and asyncio's docs say that serve_forever can be called if the server is already listening for connections, so if that is the wrong way to do things, it would definitely be worth a docs patch.
If it's done with await asyncio.Future() as per the example in the docs, is the task cancellable, and will it correctly close the server? I believe I ran into problems doing it that way, but they may have been caused by unrelated issues (which have now been resolved).
asyncio's docs say that serve_forever can be called if the server is already listening for connections
Indeed I was wrong; this doesn't create a duplicate server.
If it's done with await asyncio.Future() as per the example in the docs, is the task cancellable, and will it correctly close the server?
Yes, the server is closed by the serve() context manager, with the same code path as serve_forever():
Exiting the serve() context manager:
https://github.com/aaugustin/websockets/blob/57a1325795cc7b9166a5b4599c34c4869d8b2ab6/src/websockets/legacy/server.py#L1078-L1079
Canceling the serve_forever() coroutine:
https://github.com/python/cpython/blob/ea5ed0ba51c10cfdde7651a475438551964dfdfc/Lib/asyncio/base_events.py#L372-L373
Cool. This technically isn't my project, so I'll leave an issue on the tracker. Hopefully removing serve_forever will help.