From cff5a9f08c6ffe22dfb0d96d6715efa45b1f2828 Mon Sep 17 00:00:00 2001 From: wh6gxz nurse dude Date: Fri, 10 Jul 2026 09:18:26 -1000 Subject: [PATCH] =?UTF-8?q?Detach=20the=20poll=20thread=20when=20deattach?= =?UTF-8?q?=5Finterrupt=20runs=20on=20it=20=E2=80=94=20don't=20strand=20it?= =?UTF-8?q?s=20stack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pinedio_deattach_interrupt() skips pthread_join() when called from the poll thread itself (the af9bc27c self-join-deadlock fix). But skipping reclamation entirely strands the thread: it exits joinable, nothing can ever join it (the next attach_interrupt() overwrites the only handle), and glibc can never free or reuse a joinable thread's stack — ~8 MB of address space leaked per detach-from-callback. Callers that detach the interrupt from inside the callback on every radio interrupt (meshtastic firmware's RadioLibInterface ISR does) leak at interrupt rate: meshtastic/firmware#10468 measures ~9-12 stacks/min, hundreds of GB of VSZ within days, on every CH341-attached radio. pthread_detach(pthread_self()) in that branch lets glibc reclaim the stack when the thread exits, while the cannot-join-self deadlock stays fixed. The main-thread deattach path is unchanged (still joins). --- libpinedio-usb.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libpinedio-usb.c b/libpinedio-usb.c index a8c2d87..babd694 100644 --- a/libpinedio-usb.c +++ b/libpinedio-usb.c @@ -588,6 +588,18 @@ int32_t pinedio_deattach_interrupt(struct pinedio_inst *inst, enum pinedio_int_p pinedio_mutex_unlock(&inst->usb_access_mutex); if (inst->pin_poll_thread != pthread_self()) pthread_join(inst->pin_poll_thread, NULL); + else + /* Called from within the poll thread itself (e.g. an interrupt + * callback detaching its own interrupt). We cannot join ourselves, + * but skipping reclamation entirely strands this thread's stack: + * nothing ever joins it, and the pthread_create() in the next + * pinedio_attach_interrupt() overwrites the only handle. Each + * strand permanently leaks the thread's ~8 MB stack mapping; + * callers that detach from the callback on every radio interrupt + * leak at interrupt rate (meshtastic/firmware#10468 — hundreds of + * GB of VSZ within days). Detaching ourselves instead lets glibc + * reclaim the stack when the thread exits. */ + pthread_detach(pthread_self()); return 0; } unlock: