-
-
Notifications
You must be signed in to change notification settings - Fork 30.4k
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’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Race condition in asyncio.Server.close #109564
Comments
Hi, I would like to take that issue! |
@Yaro1 Okay, first step, can you reproduce the issue locally using the |
@gvanrossum Yes I reproduced |
Okay go ahead and work on a fix then. Come back here if you are stuck. |
okay, thank you |
Well, I'm not sure about creating the transport synchronously in _accept_connection. I tried to understand what is the reason of dividing accepting socket and creating a transport to _accept_connection and _accept_connection2. I found commit which include creating _accept_connection2 2934262 But there is a problem - I guess there is not enough information in commit message:
And the link for now indicates to another issue. Could you help me please with an understanding of purpose _accept_connection and _accept_connection2? @gvanrossum |
I'm sorry, I recommend that you try a simpler issue. |
okay I will take simpler |
Hello @bmerry 👋 I was able to reproduce this issue on latest; repro codeimport asyncio
import sys
import socket
async def server():
server = await asyncio.start_server(lambda *_: None, host="127.0.0.1", port=4445)
await asyncio.sleep(0)
server.close()
await server.wait_closed()
def client():
while True:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
try:
sock.connect(("127.0.0.1", 4445))
except IOError:
pass
finally:
sock.close()
if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == 'server':
asyncio.run(server())
else:
client() As noted in this issue, the reader callback for the listening socket ( However, I don't believe a code change is needed to address this, since the transport will be closed on error and throw a
Yup, I see 3 separate clean-up issues:
|
Multiple |
Thanks @asvetlov; should I put this change in a different PR to #126660, since the |
Bug report
Bug description:
There is a race condition when closing an asyncio.Server. Accepting a connection takes several iterations of the event loop to complete, and a call to
Server.close
in the middle will lead to exceptions.First, some demonstration code. Run both of these concurrently, running the server with
python -X dev
:Client:
Server:
It will repeatly output errors like this:
Additionally, stopping the server (with Ctrl-C) prints lots of ResourceWarnings about unclosed transports and sockets.
When a connection arrives on a socket (with selector_events), it goes through the following steps before reaching
Server._attach
:_accept_connection2
is scheduled (will only run on next event loop iteration): hereServer._attach
is called: hereIt's possible for
server.close()
to be executed between steps 2 and 3, in which caseServer._sockets
has already been set to None, and the assertion is triggered. The client will presumably experience this as a connection that either closes immediately (if the garbage collector closes the socket) or is unresponsive.This particular bug might be fixable by creating the transport synchronously in
_accept_connection
. I think there may be further potential race conditions becauseconnection_made
is also called asynchronously, which means it is possible for it to be called afterServer.close
has already returned. That might not break anything in asyncio itself but it will cause software that does something like the following to hang in 3.12 (related to #79033 / #104344):CPython versions tested on:
3.11, 3.12
Operating systems tested on:
Linux
Linked PRs
None
check forasyncio.Server._wakeup
#126660The text was updated successfully, but these errors were encountered: