Skip to content

Commit 7ec95c2

Browse files
committed
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 adafruit#7444. Signed-off-by: Damien George <damien@micropython.org>
1 parent cbc9a59 commit 7ec95c2

File tree

3 files changed

+51
-12
lines changed

3 files changed

+51
-12
lines changed

extmod/uasyncio/stream.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,7 @@ def close(self):
107107
async def wait_closed(self):
108108
await self.task
109109

110-
async def _serve(self, cb, host, port, backlog):
111-
import usocket as socket
112-
113-
ai = socket.getaddrinfo(host, port)[0] # TODO this is blocking!
114-
s = socket.socket()
115-
s.setblocking(False)
116-
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
117-
s.bind(ai[-1])
118-
s.listen(backlog)
110+
async def _serve(self, s, cb):
119111
# Accept incoming connections
120112
while True:
121113
try:
@@ -137,9 +129,20 @@ async def _serve(self, cb, host, port, backlog):
137129
# Helper function to start a TCP stream server, running as a new task
138130
# TODO could use an accept-callback on socket read activity instead of creating a task
139131
async def start_server(cb, host, port, backlog=5):
140-
s = Server()
141-
s.task = core.create_task(s._serve(cb, host, port, backlog))
142-
return s
132+
import usocket as socket
133+
134+
# Create and bind server socket.
135+
host = socket.getaddrinfo(host, port)[0] # TODO this is blocking!
136+
s = socket.socket()
137+
s.setblocking(False)
138+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
139+
s.bind(host[-1])
140+
s.listen(backlog)
141+
142+
# Create and return server object and task.
143+
srv = Server()
144+
srv.task = core.create_task(srv._serve(s, cb))
145+
return srv
143146

144147

145148
################################################################################
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Test basic behaviour of uasyncio.start_server()
2+
3+
try:
4+
import uasyncio as asyncio
5+
except ImportError:
6+
try:
7+
import asyncio
8+
except ImportError:
9+
print("SKIP")
10+
raise SystemExit
11+
12+
13+
async def test():
14+
# Test creating 2 servers using the same address
15+
print("create server1")
16+
server1 = await asyncio.start_server(None, "0.0.0.0", 8000)
17+
try:
18+
print("create server2")
19+
await asyncio.start_server(None, "0.0.0.0", 8000)
20+
except OSError as er:
21+
print("OSError")
22+
23+
# Wait for server to close.
24+
async with server1:
25+
print("sleep")
26+
await asyncio.sleep(0)
27+
28+
print("done")
29+
30+
31+
asyncio.run(test())
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
create server1
2+
create server2
3+
OSError
4+
sleep
5+
done

0 commit comments

Comments
 (0)