Claude wrote this:
ports/raspberrypi/common-hal/mdns/Server.c calls lwip's mdns API without holding the lwip core lock.
lwip expects it. LWIP_ASSERT_CORE_LOCKED() is the first statement in mdns_resp_add_service(), mdns_resp_del_service(), mdns_resp_add_netif(), mdns_resp_rename_netif(), mdns_resp_remove_netif() and others (ports/raspberrypi/lib/lwip/src/apps/mdns/mdns.c:2398,2460,2502,2546,2585, among more). It compiles to a no-op here because LWIP_CHECK_CORE_LOCKING isn't set in lwipopts.h, which is presumably why this hasn't been noticed.
The port runs lwip from a low-priority IRQ — async_context_threadsafe_background.c and cyw43_arch_threadsafe_background.c are in ports/raspberrypi/Makefile:43,45 — so these calls really can race with lwip's own processing rather than merely violating a convention.
Unlocked call sites in that file:
mdns_server_construct() — mdns_resp_init(), mdns_resp_restart(), mdns_resp_add_secondary_hostname()
common_hal_mdns_server_set_hostname() — mdns_resp_netif_active(), mdns_resp_rename_netif(), mdns_resp_add_netif()
common_hal_mdns_server_deinit() — mdns_resp_remove_netif()
common_hal_mdns_server_advertise_service() — mdns_resp_del_service(), mdns_resp_add_service()
mdns_server_find() and common_hal_mdns_server_find() — mdns_search_service(), mdns_search_stop()
The port already has the primitive: MICROPY_PY_LWIP_ENTER / MICROPY_PY_LWIP_EXIT map to cyw43_arch_lwip_begin() / cyw43_arch_lwip_end() (ports/raspberrypi/mpconfigport.h:50-52), and common-hal/socketpool/ and common-hal/wifi/Radio.c use them.
Care is needed where a locked region can raise — mp_raise_RuntimeError() on a failed mdns_resp_add_service() slot, and m_malloc_fail() — since a longjmp out of a locked region would leave the lock held.
Claude noticed this while reviewing #11181, which locks the TXT record swap in assign_txt_records() but deliberately leaves the rest of the file alone so this can be looked at on its own.
Claude wrote this:
ports/raspberrypi/common-hal/mdns/Server.ccalls lwip's mdns API without holding the lwip core lock.lwip expects it.
LWIP_ASSERT_CORE_LOCKED()is the first statement inmdns_resp_add_service(),mdns_resp_del_service(),mdns_resp_add_netif(),mdns_resp_rename_netif(),mdns_resp_remove_netif()and others (ports/raspberrypi/lib/lwip/src/apps/mdns/mdns.c:2398,2460,2502,2546,2585, among more). It compiles to a no-op here becauseLWIP_CHECK_CORE_LOCKINGisn't set inlwipopts.h, which is presumably why this hasn't been noticed.The port runs lwip from a low-priority IRQ —
async_context_threadsafe_background.candcyw43_arch_threadsafe_background.care inports/raspberrypi/Makefile:43,45— so these calls really can race with lwip's own processing rather than merely violating a convention.Unlocked call sites in that file:
mdns_server_construct()—mdns_resp_init(),mdns_resp_restart(),mdns_resp_add_secondary_hostname()common_hal_mdns_server_set_hostname()—mdns_resp_netif_active(),mdns_resp_rename_netif(),mdns_resp_add_netif()common_hal_mdns_server_deinit()—mdns_resp_remove_netif()common_hal_mdns_server_advertise_service()—mdns_resp_del_service(),mdns_resp_add_service()mdns_server_find()andcommon_hal_mdns_server_find()—mdns_search_service(),mdns_search_stop()The port already has the primitive:
MICROPY_PY_LWIP_ENTER/MICROPY_PY_LWIP_EXITmap tocyw43_arch_lwip_begin()/cyw43_arch_lwip_end()(ports/raspberrypi/mpconfigport.h:50-52), andcommon-hal/socketpool/andcommon-hal/wifi/Radio.cuse them.Care is needed where a locked region can raise —
mp_raise_RuntimeError()on a failedmdns_resp_add_service()slot, andm_malloc_fail()— since a longjmp out of a locked region would leave the lock held.Claude noticed this while reviewing #11181, which locks the TXT record swap in
assign_txt_records()but deliberately leaves the rest of the file alone so this can be looked at on its own.