extmod/uasyncio: Get addr and bind server socket before creating task.

Currently when using uasyncio.start_server() the socket configuration is
done inside a uasyncio.create_task() background function.  If the address
and port are already in use however this throws an OSError which cannot be
cleanly caught behind the create_task().

This commit moves the getaddrinfo and socket binding to the start_server()
function, and only creates the task if that succeeds.  This means that any
OSError from the initial socket configuration is propagated directly up the
call stack, compatible with CPython behaviour.

See #7444.

Signed-off-by: Damien George <damien@micropython.org>
This commit is contained in:
Damien George 2021-06-25 16:53:20 +10:00
parent cbc9a591a4
commit 7ec95c2768
3 changed files with 51 additions and 12 deletions

View File

@ -107,15 +107,7 @@ class Server:
async def wait_closed(self):
await self.task
async def _serve(self, cb, host, port, backlog):
import usocket as socket
ai = socket.getaddrinfo(host, port)[0] # TODO this is blocking!
s = socket.socket()
s.setblocking(False)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(ai[-1])
s.listen(backlog)
async def _serve(self, s, cb):
# Accept incoming connections
while True:
try:
@ -137,9 +129,20 @@ class Server:
# Helper function to start a TCP stream server, running as a new task
# TODO could use an accept-callback on socket read activity instead of creating a task
async def start_server(cb, host, port, backlog=5):
s = Server()
s.task = core.create_task(s._serve(cb, host, port, backlog))
return s
import usocket as socket
# Create and bind server socket.
host = socket.getaddrinfo(host, port)[0] # TODO this is blocking!
s = socket.socket()
s.setblocking(False)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(host[-1])
s.listen(backlog)
# Create and return server object and task.
srv = Server()
srv.task = core.create_task(srv._serve(s, cb))
return srv
################################################################################

View File

@ -0,0 +1,31 @@
# Test basic behaviour of uasyncio.start_server()
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
async def test():
# Test creating 2 servers using the same address
print("create server1")
server1 = await asyncio.start_server(None, "0.0.0.0", 8000)
try:
print("create server2")
await asyncio.start_server(None, "0.0.0.0", 8000)
except OSError as er:
print("OSError")
# Wait for server to close.
async with server1:
print("sleep")
await asyncio.sleep(0)
print("done")
asyncio.run(test())

View File

@ -0,0 +1,5 @@
create server1
create server2
OSError
sleep
done