Reputation: 65
So I have two scripts that use asyncio's servers' for communication, the script's work by the server opening an asyncio server and listening for connections, the client script connecting to that server, the server script stopping listening for new connections and assigning the reader and the writer to global variables so data sending and receiving would be possible.
Server.py:
import asyncio
import sys
class Server:
def __init__(self):
self.reader, self.writer = None, None
self.connected = False
async def listen(self, ip: str, port: int) -> None:
"""
Listens for incoming connections and handles the first connection.
After accepting the first connection, it stops the server from accepting further connections.
:param ip: IP address to listen on.
:param port: Port number to listen on.
"""
async def handle_connection(reader, writer):
print("Client connected!")
# Assign the reader and writer to instance variables for later use
self.reader, self.writer = reader, writer
self.connected = True
print("Shutting down server from accepting new connections")
server.close()
await server.wait_closed()
print(f"Listening on {ip}:{port}")
server = await asyncio.start_server(handle_connection, ip, port)
try:
async with server:
await server.serve_forever()
except KeyboardInterrupt:
sys.exit(1)
except asyncio.CancelledError:
print("Connection canceled")
except Exception as e:
print(f"Unexpected error while trying to listen, Error: {e}")
sys.exit(1)
if __name__ == '__main__':
server = Server()
asyncio.run(server.listen('192.168.0.35', 9090))
Client.py:
import asyncio
class Client:
def __init__(self):
self.reader, self.writer = None, None
self.connected = False
async def connect(self, ip: str, port: int) -> None:
"""
Connects to a server at the specified IP address and port.
:param ip: IP address of the server.
:param port: Port number of the server.
"""
while not self.connected:
try:
self.reader, self.writer = await asyncio.wait_for(
asyncio.open_connection(ip, port), 5
)
print(f"Connecting to {ip}:{port}")
self.connected = True
break
except Exception as e:
print(
f"Failed to connect to {ip}:{port} retrying in 10 seconds."
)
print(e)
await asyncio.sleep(10)
continue
if __name__ == '__main__':
Client = Client()
asyncio.run(Client.connect('192.168.0.35', 9090))
In python 3.11 the execution process was as follows; the Client script is connecting to the listening server script, the server script is calling the handle_connection function and the function is raising asyncio.CancelledError
which exits the listening method and keeps the reader and writer alive.
However in python 3.12; the Client script is connecting to the listening server script, the server script is calling the handle_connection and is being stuck at await server.wait_closed()
.
I did some debugging and discovered that the await server.wait_closed()
line is not returning unless the writer is closed using writer.close()
, which we do not want because as I said the script will be using the reader and writer for communication.
My intended action was for the server script to listen to a single connection, when a connection is established for it to stop listening for any further connection attempts but still maintain a connection between it and the original connected client.
EDIT: I upgraded from python3.11.9 to python3.12.6
Upvotes: 2
Views: 70
Reputation: 17342
To stop serving server.close
does the job. The wait_closed
has a broader meaning. Let me quote directly from the asyncio code, it explains everything, also why it was working on 3.11:
async def wait_closed(self):
"""Wait until server is closed and all connections are dropped.
- If the server is not closed, wait.
- If it is closed, but there are still active connections, wait.
Anyone waiting here will be unblocked once both conditions
(server is closed and all connections have been dropped)
have become true, in either order.
Historical note: In 3.11 and before, this was broken, returning
immediately if the server was already closed, even if there
were still active connections. An attempted fix in 3.12.0 was
still broken, returning immediately if the server was still
open and there were no active connections. Hopefully in 3.12.1
we have it right.
"""
Upvotes: 3