From 444e5f951e3bab1bf640cc5a61865fc2b6266ec4 Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Sat, 1 Aug 2026 08:38:11 -0700 Subject: [PATCH] supervisor/web_workflow: check the listener socket calls for failure start_web_workflow() created the listening socket, bound it and called listen() without checking any of them, with an explicit "(Not checking for failures)" comment on the bind. On a port whose network stack can refuse to create a socket, the descriptor stays unset and bind() and listen() then run on -1 and fail with EBADF, but the function still returns true. The caller takes that as success and installs the background callback, so the supervisor polls a socket that was never opened and the board can hang before running code.py. Nothing is ever served, and there is no diagnostic. Observed on a Silicon Labs SiWx917-DK2605A (BRD2605A) running the zephyr-cp port, CircuitPython 10.3.0-alpha.4, where socket() returns ENOTCONN until the interface has an address: socket() ok=0 num=-1 errno=107 ENOTCONN bind port 80 -> 9 errno=9 EBADF listen -> 0 errno=9 closed=1 (returns true) Return false instead of reporting success, and close the socket if bind or listen is the step that failed. Note this is not a retry: the next attempt is the next supervisor_workflow_reset(). Bringing the workflow up once the interface acquires an address later is a separate change. This is not specific to that port. #10054 reports web workflow unreachable after a watchdog reset on several ESP32 boards, and the attempted fix in #10948 moved port 80 from closed to filtered, i.e. bound but never serving, which is the same end state this produces. Co-Authored-By: Claude Opus 5 (1M context) --- supervisor/shared/web_workflow/web_workflow.c | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/supervisor/shared/web_workflow/web_workflow.c b/supervisor/shared/web_workflow/web_workflow.c index f88e3322e23..e0c9ba45095 100644 --- a/supervisor/shared/web_workflow/web_workflow.c +++ b/supervisor/shared/web_workflow/web_workflow.c @@ -398,14 +398,33 @@ bool supervisor_start_web_workflow(void) { if (common_hal_socketpool_socket_get_closed(&listening)) { #if CIRCUITPY_SOCKETPOOL_IPV6 - socketpool_socket(&pool, SOCKETPOOL_AF_INET6, SOCKETPOOL_SOCK_STREAM, 0, &listening); + bool opened = socketpool_socket(&pool, SOCKETPOOL_AF_INET6, SOCKETPOOL_SOCK_STREAM, 0, &listening); #else - socketpool_socket(&pool, SOCKETPOOL_AF_INET, SOCKETPOOL_SOCK_STREAM, 0, &listening); + bool opened = socketpool_socket(&pool, SOCKETPOOL_AF_INET, SOCKETPOOL_SOCK_STREAM, 0, &listening); #endif + // Ports with an offloaded network stack can refuse to create a + // socket until the interface has an address (this board returns + // ENOTCONN before DHCP completes). Binding and listening on the + // unset descriptor then fails with EBADF, and reporting success + // leaves the supervisor polling a socket that was never opened. + // + // Returning false avoids that but is not a retry: the background + // callback never re-enters this function, so the next attempt is + // the next supervisor_workflow_reset(), i.e. a VM restart. A board + // that only gets an address after boot will not bring the workflow + // up on its own until then. + if (!opened) { + return false; + } common_hal_socketpool_socket_settimeout(&listening, 0); - // Bind to any ip. (Not checking for failures) - common_hal_socketpool_socket_bind(&listening, "", 0, web_api_port); - common_hal_socketpool_socket_listen(&listening, 1); + if (common_hal_socketpool_socket_bind(&listening, "", 0, web_api_port) != 0) { + common_hal_socketpool_socket_close(&listening); + return false; + } + if (!common_hal_socketpool_socket_listen(&listening, 1)) { + common_hal_socketpool_socket_close(&listening); + return false; + } } // Wake polling thread (maybe) socketpool_socket_poll_resume();