Make telemetry lines atomic with a scoped sink hold - #73
Merged
Conversation
Co-Authored-By: Claude <noreply@anthropic.com>
georgesleen
force-pushed
the
telemetry-line-mutex
branch
from
August 16, 2026 20:26
da96d41 to
5f08fc5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
About 6% of telemetry lines arrive spliced (114 of ~1900 in a 60 s bench capture). Two causes, both fixed here.
Lines were never atomic
Four tasks write to the one
TelemetrySink, and a telemetry line is severalPrintcalls long: a tag, its fields, then the newline. Nothing held the sink across those calls, so another task could cut in mid-line. Locking eachwrite()would not have helped, because the line boundary is above that level.main.cppandpersist_taskshow the same shape outside theF/I/M/Grecords, where one message is aprintfollowed by aprintln.TelemetrySinknow hands out aLine: an RAII hold taken for one line and released at end of scope. It is the only thing that implementsPrint, so the sink itself can no longer be written to directly and interleaving stops being something a caller has to remember. That matters here because the failure is silent, a spliced line still parses as two damaged ones.The GNSS task bypassed the sink
gnss_taskwrote its# PVT,# AUXand# GNSSdiagnostics straight toSerial, so they raced with the sink's ownSerialwrites and never reached RTT or the bench UART at all. It now takes aTelemetrySink &like the other three tasks and prints through it.Notes
I2cTransport::scankeeps its plainPrint ¶meter and the caller opens the line around the whole call, solib/bno08x_imugains no dependency on the firmware's sink.A
Lineblocks other tasks' telemetry while held, so the hold is scoped to a single line everywhere and never spans a wait.The mutex is created in the constructor. If the FreeRTOS heap were exhausted there,
xSemaphoreCreateMutexreturns null and writes fall back to unlocked rather than dereferencing it; on this firmware that case means boot has already failed.Verification
217 native tests pass and
pio run -e pico2builds. Not yet verified on hardware: the board dropped off USB before the corruption rate could be re-measured. The check to run is the same 60 s capture, expecting the corrupt-line count to go to zero.