Skip to content

fix(native): implement BinarySemaphorePosix with proper pthread synchronization#9895

Merged
jp-bennett merged 11 commits into
meshtastic:developfrom
iannucci:fix/binary-semaphore-posix
Apr 13, 2026
Merged

fix(native): implement BinarySemaphorePosix with proper pthread synchronization#9895
jp-bennett merged 11 commits into
meshtastic:developfrom
iannucci:fix/binary-semaphore-posix

Conversation

@iannucci

Copy link
Copy Markdown
Contributor

Summary

The BinarySemaphorePosix class, 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 calls delay(msec) and always returns false
  • giveFromISR() is a no-op

The // FIXME comment in take() confirms this was known to be incomplete.

This means the mainDelay semaphore (and any other BinarySemaphorePosix usage) cannot function: threads cannot wake the main loop early via give(), the NotifiedWorkerThread pattern 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):

  • Adds #include <pthread.h> (guarded by #ifndef HAS_FREE_RTOS)
  • Replaces commented-out SemaphoreHandle_t with pthread_mutex_t mutex, pthread_cond_t cond, bool signaled

Implementation (BinarySemaphorePosix.cpp):

  • Constructor: pthread_mutex_init + pthread_cond_init, signaled = false
  • Destructor: pthread_cond_destroy + pthread_mutex_destroy
  • take(msec): pthread_cond_timedwait with CLOCK_REALTIME timeout. Uses a while (!signaled) loop to handle spurious wakeups. Atomically consumes the signal (binary semaphore semantics). Returns true if signaled, false on timeout.
  • give(): Sets signaled = true, calls pthread_cond_signal under mutex
  • giveFromISR(): Delegates to give(), sets *pxHigherPriorityTaskWoken = true

Symptoms observed without this fix

Tested on Raspberry Pi 3 Model B (ARM64, Debian 12 Bookworm) with Adafruit LoRa Radio Bonnet (RFM9x/SX1276):

  • Radio TX works only on fixed timer intervals (no early wake from give())
  • Radio RX interrupts are not propagated to the main loop
  • Telemetry packets are sent to the phone API but never transmitted over the mesh
  • MQTT gateway receives LoRa packets but relay timing is degraded
  • OLED display (SSD1306 via I2C) was non-functional (likely related to I2C thread scheduling)

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_RTOS builds (Linux/portduino/native). FreeRTOS builds use BinarySemaphoreFreeRTOS and are unaffected.

Test plan

  • Built with bash ./bin/build-native.sh native (Docker cross-compilation, python:3.11-slim-bookworm, --platform linux/arm64)
  • Deployed to Raspberry Pi 3 Model B running Debian 12 (Bookworm) ARM64
  • Verified RF95/SX1276 radio init success
  • Verified bidirectional LoRa communication (TX and RX confirmed with multiple nodes)
  • Verified MQTT gateway operation (uplink and downlink)
  • Verified telemetry transmission over mesh
  • Verified SSD1306 OLED display working
  • Verified stable operation across power cycles
  • Needs testing on other native platforms (x86_64 Linux, macOS simulator)

🤖 Generated with Claude Code

…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>
@github-actions

Copy link
Copy Markdown
Contributor

@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.
There's a big backlog of patches at the moment. If you have time,
please help us with some code review and testing of other PRs!

Welcome to the team 😄

@CLAassistant

CLAassistant commented Mar 12, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@github-actions github-actions Bot added first-contribution needs-review Needs human review bugfix Pull request that fixes bugs labels Mar 12, 2026
@thebentern thebentern changed the base branch from master to develop March 12, 2026 18:25
@GUVWAF

GUVWAF commented Mar 29, 2026

Copy link
Copy Markdown
Member

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.

@GUVWAF GUVWAF requested a review from jp-bennett March 29, 2026 15:46
@jp-bennett

Copy link
Copy Markdown
Collaborator

It needs to be specific for portduino rather than just any target without HAS_FREE_RTOS, but yes, this looks good.

@jp-bennett

Copy link
Copy Markdown
Collaborator

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_t state to BinarySemaphorePosix (portduino builds).
  • Implement take(msec) using pthread_cond_timedwait with binary “consume” semantics.
  • Implement functional give() and giveFromISR() 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.

Comment thread src/concurrency/BinarySemaphorePosix.h
Comment thread src/concurrency/BinarySemaphorePosix.cpp
Comment thread src/concurrency/BinarySemaphorePosix.cpp
Comment thread src/concurrency/BinarySemaphorePosix.cpp
Comment thread src/concurrency/BinarySemaphorePosix.cpp
@jp-bennett jp-bennett merged commit 4ac74ed into meshtastic:develop Apr 13, 2026
78 checks passed
thebentern added a commit that referenced this pull request Apr 13, 2026
…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>
NomDeTom pushed a commit to NomDeTom/MeshtasticFirmware that referenced this pull request Apr 14, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Pull request that fixes bugs first-contribution needs-review Needs human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants