Skip to content

Commit

Permalink
extmod/uasyncio: Get addr and bind server socket before creating task.
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
dpgeorge committed Jun 26, 2021
1 parent cbc9a59 commit 7ec95c2
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 12 deletions.
27 changes: 15 additions & 12 deletions extmod/uasyncio/stream.py
Expand Up @@ -107,15 +107,7 @@ def close(self):
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:
Expand All @@ -137,9 +129,20 @@ async def _serve(self, cb, host, port, backlog):
# 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


################################################################################
Expand Down
31 changes: 31 additions & 0 deletions tests/net_hosted/uasyncio_start_server.py
@@ -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())
5 changes: 5 additions & 0 deletions tests/net_hosted/uasyncio_start_server.py.exp
@@ -0,0 +1,5 @@
create server1
create server2
OSError
sleep
done

0 comments on commit 7ec95c2

Please sign in to comment.