-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
CircuitPython version and board name
tested various versions from
Adafruit CircuitPython 9.2.8 on 2025-05-28; Adafruit QT Py ESP32-S3 4MB Flash 2MB PSRAM with ESP32S3
through
Adafruit CircuitPython 10.1.0-beta.1 on 2025-11-06; Adafruit QT Py ESP32-S3 4MB Flash 2MB PSRAM with ESP32S3
Code/REPL
import time
import random
import os
import wifi
import socketpool
import asyncio
NUM_CLIENTS = 4
NUM_SERVERS = 2
BASE_PORT = 5000
MAXBUF = 64
class Socket:
""" https://github.com/adafruit/circuitpython/pull/7173 """
def __init__(self, s):
self.s = s
s.setblocking(False)
async def recv_into(self, buf):
await asyncio.core._io_queue.queue_read(self.s)
return self.s.recv_into(buf)
async def send(self, buf):
await asyncio.core._io_queue.queue_write(self.s)
return self.s.send(buf)
async def accepted_conn(server_num, conn, event):
"""handles a single server connection from a client"""
inbuf = bytearray(MAXBUF)
sconn = Socket(conn)
size = False
while not size:
try:
size = await(sconn.recv_into(inbuf)) # OSError: [Errno 128] ENOTCONN
except OSError as ex:
await asyncio.sleep(0)
print(f"{time.monotonic():.1f}s SERVER {server_num} received {size} bytes {inbuf[:size]}")
outbuf = f"{time.monotonic():.1f}s Hello client from server {server_num}".encode()
await(sconn.send(outbuf))
print(f"{time.monotonic():.1f}s SERVER {server_num} sent {len(outbuf)} bytes {outbuf}")
# sconn.close() # AttributeError: 'Socket' object has no attribute 'close'
conn.close()
event.set()
async def server(server_num):
"""persistent TCP server"""
BACKLOG = NUM_CLIENTS
s = pool.socket(pool.AF_INET, pool.SOCK_STREAM)
s.setsockopt(pool.SOL_SOCKET, pool.SO_REUSEADDR, 1) #
port = BASE_PORT + server_num
s.bind((host, port))
s.listen(BACKLOG)
print(f"{time.monotonic():.1f}s SERVER {server_num} listening on port {port}")
total = 0
while True:
print(f"{time.monotonic():.1f}s SERVER {server_num} accepting connections")
s.setblocking(False)
addr = False
while not addr:
try:
conn, addr = s.accept() # OSError: [Errno 11] EAGAIN
print(f"{time.monotonic():.1f}s SERVER {server_num} accepted connection from {addr}")
event = asyncio.Event()
t = asyncio.create_task(accepted_conn(server_num, conn, event))
await event.wait()
t.cancel()
total += 1
print(f"{time.monotonic():.1f}s SERVER {server_num} has processed {total} total connections")
except OSError as ex:
await asyncio.sleep(0)
await asyncio.sleep(0)
async def connected_client(client_num, s, event):
"""handles a single client connection to a server"""
inbuf = bytearray(MAXBUF)
ss = Socket(s)
outbuf = f"{time.monotonic():.1f}s Hello server from client {client_num}".encode()
size = await(ss.send(outbuf))
print(f"{time.monotonic():.1f}s CLIENT {client_num} sent {size} bytes {outbuf}")
size = False
while not size:
try:
size = await(ss.recv_into(inbuf)) # OSError: [Errno 11] EAGAIN
except OSError as ex:
await asyncio.sleep(0)
print(f"{time.monotonic():.1f}s CLIENT {client_num} received {size} bytes {inbuf[:size]}")
# ss.close() # AttributeError: 'Socket' object has no attribute 'close'
event.set()
async def client(client_num):
"""TCP client wrapper"""
total = 0
while True:
with pool.socket(pool.AF_INET, pool.SOCK_STREAM) as s:
port = random.randint(BASE_PORT, BASE_PORT + NUM_SERVERS)
print(f"{time.monotonic():.1f}s CLIENT {client_num} connecting to ({host}, {port})")
try:
s.connect((host, port)) # OSError: [Errno 119] EINPROGRESS
print(f"{time.monotonic():.1f}s CLIENT {client_num} connected to ({host}, {port})")
event = asyncio.Event()
t = asyncio.create_task(connected_client(client_num, s, event))
await event.wait()
t.cancel
total += 1
print(f"{time.monotonic():.1f}s CLIENT {client_num} has processed {total} total connections")
except OSError as ex:
await asyncio.sleep(0)
await asyncio.sleep(random.random())
async def main():
print(f"{time.monotonic():.1f}s creating {NUM_SERVERS} servers, {NUM_CLIENTS} clients")
tasks = []
for server_num in range(NUM_SERVERS):
tasks.append(asyncio.create_task(server(server_num)))
for client_num in range(NUM_CLIENTS):
tasks.append(asyncio.create_task(client(client_num)))
print(f"{time.monotonic():.1f}s {len(tasks)} tasks created")
while True:
await asyncio.sleep(0)
time.sleep(3) # wait for serial after reset
wifi.radio.connect(os.getenv('WIFI_SSID'), os.getenv('WIFI_PASSWORD'))
host = str(wifi.radio.ipv4_address)
pool = socketpool.SocketPool(wifi.radio)
asyncio.run(main())Behavior
Espressif: ```Hard fault: memory access or instruction error.````
The time it takes to hard fault varies, but typically 1-8 hours.
Tried variations, including single server and single client, and other simplifications. This code uses TCP. I had less success constructing a UDP version that would run without exceptions.
Code does not run on raspberrypi (various exceptions).
I'm really not sure if async sockets are supposed to be supported at this point. There are numerous asyncio-socket-related issues (and some abandoned PRs) in the core, and in the asyncio and network libraries.
The inspiration for this test came from:
#7173
I'm not sure how canonical this pattern is. It doesn't seem to be CPython compatible, and it may be incomplete for fullly-featured asynchronous sockets.
Initially reported here:
https://gist.github.com/anecdata/89b4458fd7c0c01c7fc0a9ff4be5b4e6
Description
No response
Additional information
No response