fix(native): implement BinarySemaphorePosix with proper pthread synchronization#9895
Conversation
…ronization The BinarySemaphorePosix class (used on all Linux/portduino/native builds) had stub implementations: give() was a no-op and take() just called delay(msec) and returned false. This broke the cooperative thread scheduler on native platforms — threads could not wake the main loop, radio RX interrupts were missed, and telemetry never transmitted over the mesh. Replace the stubs with a proper binary semaphore using pthread_mutex_t + pthread_cond_t + bool signaled: - take(msec): pthread_cond_timedwait with CLOCK_REALTIME timeout, consumes signal atomically (binary semaphore semantics) - give(): sets signaled=true, signals condition variable - giveFromISR(): delegates to give(), sets pxHigherPriorityTaskWoken Tested on Raspberry Pi 3 Model B (ARM64, Debian Bookworm) with Adafruit LoRa Radio Bonnet (SX1276). Before fix: no radio TX/RX, no telemetry on mesh. After fix: bidirectional LoRa, MQTT gateway, telemetry all working. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@iannucci, Welcome to Meshtastic!Thanks for opening your first pull request. We really appreciate it. We discuss work as a team in discord, please join us in the #firmware channel. Welcome to the team 😄 |
|
Very nice work. I believe this might also solve the issue mentioned in #9939, or at least make it way less common, and it is a better solution IMO. |
|
It needs to be specific for portduino rather than just any target without HAS_FREE_RTOS, but yes, this looks good. |
|
Testing this out with my DMShell branch, and it appears to fix the RX detection issue I was seeing. Which in retrospect is exactly what the PR said it did. |
There was a problem hiding this comment.
Pull request overview
Implements a real POSIX-backed binary semaphore for native/portduino builds to fix cooperative scheduler wakeups (replacing prior stubbed give() / take() behavior).
Changes:
- Add
pthread_mutex_t/pthread_cond_tstate toBinarySemaphorePosix(portduino builds). - Implement
take(msec)usingpthread_cond_timedwaitwith binary “consume” semantics. - Implement functional
give()andgiveFromISR()to wake blocked waits.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
src/concurrency/BinarySemaphorePosix.h |
Adds pthread-based members/includes for portduino builds. |
src/concurrency/BinarySemaphorePosix.cpp |
Replaces stubs with a pthread mutex/condvar implementation of a binary semaphore. |
…ronization (#9895) * fix(native): implement BinarySemaphorePosix with proper pthread synchronization The BinarySemaphorePosix class (used on all Linux/portduino/native builds) had stub implementations: give() was a no-op and take() just called delay(msec) and returned false. This broke the cooperative thread scheduler on native platforms — threads could not wake the main loop, radio RX interrupts were missed, and telemetry never transmitted over the mesh. Replace the stubs with a proper binary semaphore using pthread_mutex_t + pthread_cond_t + bool signaled: - take(msec): pthread_cond_timedwait with CLOCK_REALTIME timeout, consumes signal atomically (binary semaphore semantics) - give(): sets signaled=true, signals condition variable - giveFromISR(): delegates to give(), sets pxHigherPriorityTaskWoken Tested on Raspberry Pi 3 Model B (ARM64, Debian Bookworm) with Adafruit LoRa Radio Bonnet (SX1276). Before fix: no radio TX/RX, no telemetry on mesh. After fix: bidirectional LoRa, MQTT gateway, telemetry all working. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ARCH_PORTDUINO * Refactor BinarySemaphorePosix header for ARCH_PORTDUINO * Change preprocessor directive from ifndef to ifdef * Gate new Semaphore code to Portduino and fix STM compilation * Binary Semaphore Posix better error handling --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
…ronization (meshtastic#9895) * fix(native): implement BinarySemaphorePosix with proper pthread synchronization The BinarySemaphorePosix class (used on all Linux/portduino/native builds) had stub implementations: give() was a no-op and take() just called delay(msec) and returned false. This broke the cooperative thread scheduler on native platforms — threads could not wake the main loop, radio RX interrupts were missed, and telemetry never transmitted over the mesh. Replace the stubs with a proper binary semaphore using pthread_mutex_t + pthread_cond_t + bool signaled: - take(msec): pthread_cond_timedwait with CLOCK_REALTIME timeout, consumes signal atomically (binary semaphore semantics) - give(): sets signaled=true, signals condition variable - giveFromISR(): delegates to give(), sets pxHigherPriorityTaskWoken Tested on Raspberry Pi 3 Model B (ARM64, Debian Bookworm) with Adafruit LoRa Radio Bonnet (SX1276). Before fix: no radio TX/RX, no telemetry on mesh. After fix: bidirectional LoRa, MQTT gateway, telemetry all working. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ARCH_PORTDUINO * Refactor BinarySemaphorePosix header for ARCH_PORTDUINO * Change preprocessor directive from ifndef to ifdef * Gate new Semaphore code to Portduino and fix STM compilation * Binary Semaphore Posix better error handling --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Summary
The
BinarySemaphorePosixclass, used on all Linux/portduino/native builds, has stub implementations that completely break the cooperative thread scheduler:give()is a no-op (empty function body)take(uint32_t msec)just callsdelay(msec)and always returns falsegiveFromISR()is a no-opThe
// FIXMEcomment intake()confirms this was known to be incomplete.This means the
mainDelaysemaphore (and any otherBinarySemaphorePosixusage) cannot function: threads cannot wake the main loop early viagive(), theNotifiedWorkerThreadpattern is broken (notify()→mainDelay.interrupt()→give()does nothing), and the scheduler degrades to a fixed-interval poller that always sleeps the full timeout.What this PR does
Replaces the stubs with a proper binary semaphore implementation using POSIX threads:
Header (
BinarySemaphorePosix.h):#include <pthread.h>(guarded by#ifndef HAS_FREE_RTOS)SemaphoreHandle_twithpthread_mutex_t mutex,pthread_cond_t cond,bool signaledImplementation (
BinarySemaphorePosix.cpp):pthread_mutex_init+pthread_cond_init,signaled = falsepthread_cond_destroy+pthread_mutex_destroytake(msec):pthread_cond_timedwaitwithCLOCK_REALTIMEtimeout. Uses awhile (!signaled)loop to handle spurious wakeups. Atomically consumes the signal (binary semaphore semantics). Returnstrueif signaled,falseon timeout.give(): Setssignaled = true, callspthread_cond_signalunder mutexgiveFromISR(): Delegates togive(), sets*pxHigherPriorityTaskWoken = trueSymptoms observed without this fix
Tested on Raspberry Pi 3 Model B (ARM64, Debian 12 Bookworm) with Adafruit LoRa Radio Bonnet (RFM9x/SX1276):
give())After the fix: bidirectional LoRa, MQTT gateway relay, telemetry over mesh, and OLED display all work correctly.
Scope
This change only affects
#ifndef HAS_FREE_RTOSbuilds (Linux/portduino/native). FreeRTOS builds useBinarySemaphoreFreeRTOSand are unaffected.Test plan
bash ./bin/build-native.sh native(Docker cross-compilation,python:3.11-slim-bookworm,--platform linux/arm64)🤖 Generated with Claude Code