Skip to content

Inconsistent behavior of asyncio.Server.wait_closed in Python 3.12 versus earlier releases#120866

@tovrstra

Description

@tovrstra

Bug report

Bug description:

The issue fixed in #111424 is only available in Python 3.12. As a result, the example below prematurely closes a connection when using an older Python version. The old behavior may lead to bugs that are hard to catch, and the difference in behavior implies that applications must use workarounds to support multiple Python versions.

If backporting the fix is not an option, it would be helpful to add a note to the documentation of asyncio.Server.wait_closed that it changed in Python 3.12.

Related issues: #104344 (same cause, going from 3.11 to 3.12), #79033

minimal example

server.py

importasynciofromfunctoolsimportpartialasyncdefhandler(stop_event, reader, writer): print("Client connected") msg= (awaitreader.readline()).decode() print(f"Received: {msg.strip()}") awaitasyncio.sleep(1) if"STOP"inmsg: print("Client requests stop. Setting stop event") stop_event.set() awaitasyncio.sleep(1) print("----> Respond with same message <----") writer.write(msg.encode()) awaitwriter.drain() writer.close() awaitwriter.wait_closed() print("----> Closing connection <----") asyncdefmain(): print("Starting server") stop_event=asyncio.Event() socket_path="socket"server=awaitasyncio.start_unix_server(partial(handler, stop_event), socket_path) print(f"Server listening at: {socket_path}") asyncwithserver: awaitstop_event.wait() print("Server stopping") print("Server stopped") if__name__=="__main__": asyncio.run(main())

client.py

importasyncioasyncdeftalk(msg): socket_path="socket"print(f"Connecting to: {socket_path}") reader, writer=awaitasyncio.open_unix_connection(socket_path) print("Connected") print(f"Sending message: {msg}") writer.write(f"{msg}\n".encode()) awaitwriter.drain() res= (awaitreader.readline()).decode() print(f"Received response: {res[:-1]}") print("Closing connection") writer.close() awaitwriter.wait_closed() asyncdefmain(): print("Starting client") awaittalk("Hello") awaittalk("STOP") print("Stopping client") if__name__=="__main__": asyncio.run(main())

This is a minimal working example distilled from a more complex use case where the stop_event is set in a function that receives the request and creates the response.

One possible workaround is to postpone setting the stop_event to the end of the handler function. That would be easy in this example, but more involved in the case where I ran into this issue.

Python 3.12 and 3.13 output

server.py

Starting server Server listening at: socket Client connected Received: Hello ----> Respond with same message <---- ----> Closing connection <---- Client connected Received: STOP Client requests stop. Setting stop event Server stopping ----> Respond with same message <---- ----> Closing connection <---- Server stopped 

client.py

Starting client Connecting to: socket Connected Sending message: Hello Received response: Hello Closing connection Connecting to: socket Connected Sending message: STOP Received response: STOP Closing connection Stopping client 

Python 3.9, 3.10 and 3.11 output

Note that the handler does not complete when it receives STOP.

server.py

Starting server Server listening at: socket Client connected Received: Hello ----> Respond with same message <---- ----> Closing connection <---- Client connected Received: STOP Client requests stop. Setting stop event Server stopping Server stopped 

client.py

Starting client Connecting to: socket Connected Sending message: Hello Received response: Hello Closing connection Connecting to: socket Connected Sending message: STOP Received response: Closing connection Stopping client 

CPython versions tested on:

3.9, 3.10, 3.11, 3.12, 3.13

Operating systems tested on:

Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions