In the example below:
loop = asyncio.get_event_loop()
loop.create_task(client()) # Does no start until run_server ends
await server()
# await f()
... the call to server causes client to be blocked. In particular it get stuck in the call to
await loop.sock_accept(sock)
and the client() will not start until server() exits. Why?
Replacing await server()
with a difference async functions:
#await server()
await f()
allows client to run()
The behavior is the same for Python 3.7 .. 3.10
Similarly, we can flip which is added as as a task and which is immediately await. The task fails to run in both:
if 1:
loop.create_task(server()) # Does no start until server ends
await client()
else:
loop.create_task(client()) # Does no start until client ends
await server()
Full example:
import asyncio
import socket
host, port = ('localhost', 15555)
# host, port = ('127.0.0.1', 15555)
ACCEPT_TIMEOUT = 2
async def server():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("Creating server")
sock.bind((host, port))
sock.listen(8)
sock.setblocking(False) # sock_accept asks for non-blockin
sock.settimeout(ACCEPT_TIMEOUT)
loop = asyncio.get_event_loop()
while True:
print("ACCEPTING CONNECTIONS")
try:
client, _ = await loop.sock_accept(sock)
except socket.timeout:
print("ACCEPT TIMEOUT")
return
print("123 5")
loop.create_task(handle_client(client))
print("123 6")
async def client():
print("Sending .. ")
while True:
try:
tcp_reader, tcp_writer = await asyncio.open_connection(host, port)
break
except ConnectionRefusedError:
print("Refused")
await asyncio.sleep(1)
print("Sending .. OK")
msg = b"Message"
while True:
tcp_writer.write(msg)
tcp_writer.drain()
r = tcp_reader.read()
assert r == msg
async def handle_client(client): # Never reached
print("Handle client...")
loop = asyncio.get_event_loop()
request = None
while request != 'quit':
request = (await loop.sock_recv(client, 255))
await loop.sock_sendall(client, request)
client.close()
async def f():
print("f..")
asyncio.sleep(1)
print("f..done")
async def main():
loop = asyncio.get_event_loop()
if 1:
loop.create_task(client()) # Does no start until client ends
await server()
# await f()
else:
loop.create_task(server()) # Does no start until server ends
await client()
if __name__=='__main__':
asyncio.run(main())
CodePudding user response:
It seems that on Linux sock.settimeout()
overrules sock.setblocking(false)
.
A couple of changes that made your code work for me:
- Remove
sock.settimeout(...)
and use asyncio's timeout functionality instead - Use context managers for closing the socket properly, to avoid leaving an unclosed socket behind
- Add the
REUSEADDR
flag to keep the socket from being stuck inTIME_WAIT
state when running the example repeatedly
async def server():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
print("Creating server")
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((host, port))
sock.listen(8)
sock.setblocking(False) # sock_accept asks for non-blockin
loop = asyncio.get_event_loop()
while True:
print("ACCEPTING CONNECTIONS")
try:
client, _ = await asyncio.wait_for(loop.sock_accept(sock), timeout=ACCEPT_TIMEOUT)
except asyncio.TimeoutError:
print("ACCEPT TIMEOUT")
return
print("123 5")
loop.create_task(handle_client(client))
print("123 6")