Skip to content

Make the interrupt poll thread persistent: park on detach instead of exiting#11

Draft
Nursedude wants to merge 2 commits into
pine64:mainfrom
Nursedude:refactor-persistent-poll-thread
Draft

Make the interrupt poll thread persistent: park on detach instead of exiting#11
Nursedude wants to merge 2 commits into
pine64:mainfrom
Nursedude:refactor-persistent-poll-thread

Conversation

@Nursedude

Copy link
Copy Markdown
Contributor

Deeper follow-up to #10 (the minimal one-line leak fix) — this eliminates the leak class rather than the instance. Maintainers' choice: merge #10 alone, or this instead (it supersedes #10); both fix meshtastic/firmware#10468.

Why go further than #10

Creating and destroying a pthread per attach/detach cycle is churn embedded targets shouldn't pay: consumers that detach/re-attach around every radio interrupt (meshtastic firmware does) spawn ~9–12 threads per minute — a fresh 8 MB stack mmap + clone3 each time even once #10 makes them reclaimable. On 32-bit router-class platforms (OpenWrt mips/arm, ~2 GB usable VA) the stranded variant exhausts address space in hours, and vm.max_map_count (65530 default) is its own ceiling.

Design

  • The poll thread is created once per pinedio_init() (on first attach) and parks on a condition variable when the last interrupt detaches; the next attach signals it awake. Attach/detach cycles toggle a flag — zero thread churn.
  • There is never a mid-life thread to join, so detaching from within the interrupt callback (which runs on this very thread) is safe by construction — the self-join hazard the af9bc27 guard worked around no longer exists.
  • The thread terminates exactly once, in pinedio_deinit() (join; pthread_detach(self) in the pathological deinit-from-callback case).
  • Lock order is uniform (usb_access_mutexpoll_park_mutex); the poll thread never holds the park mutex while taking the USB mutex.

Deliberate behavior change: a pinedio_get_input() failure now latches in_error and keeps polling at the normal cadence — self-healing when a later read succeeds — instead of silently killing the poll thread until the next full deinit/init (the old failure mode left interrupt delivery dead with no recovery path). If you prefer the old semantics I can park on error instead.

Testing status

Compile-tested against the full meshtastic firmware native build (2.7.24/472b14c, aarch64). Not yet soak-tested on hardware — my three CH341 field units are currently validating #10 (numbers posted there: leak eliminated). I'll run this branch through the same live-hardware soak on request, or once there's maintainer appetite for the deeper change.

…exiting

Deeper fix for the thread-stack leak class (see PR pine64#10 for the minimal
one-liner). Creating and destroying a pthread per attach/detach cycle is
churn no embedded target should pay — on 32-bit router-class platforms
(OpenWrt mips/arm) the strand variant exhausts the address space in
hours, and even leak-free churn costs a stack mmap + clone per radio
interrupt.

The poll thread is now created once per pinedio_init() and PARKS on a
condition variable when the last interrupt detaches, resuming on the
next attach. There is never a mid-life thread to join, so detaching
from within the interrupt callback (which runs on this very thread) is
inherently safe — the self-join hazard af9bc27 guarded against is gone
by construction. The thread terminates exactly once, in
pinedio_deinit() (join, or detach-self in the pathological
deinit-from-callback case).

Behavior change, deliberate: a pinedio_get_input() failure now latches
in_error and keeps polling at the normal cadence (self-healing on a
later good read) instead of silently killing the poll thread until the
next full deinit/init.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: meshtasticd (portduino/Linux) VSZ + swap grow without bound under sustained load — VmPeak ~915 GB, VmSwap ~1.78 GB after 10 days

1 participant