fix(daemon): the receive tick transmits and never dropped its capture stream (#1319) - #1322
Merged
Merged
Conversation
… stream (#1319) #1007 established that the daemon must not hold its capture stream across a keyed transmit: nothing reads it, so on cpal the callback appends to an unbounded buffer and the next tick is handed one discontinuous blob of audio captured while this station was transmitting. `rx_stream` is assigned at four sites and none is on the rx tick arm — yet that arm keys the OTA ACK, a CONACK or QSY reply out of `process_received_bytes`, and the periodic §97.119 station ID. That last one makes it unconditional: every compliant station identifies on a schedule, so every station did this, not just one mid-QSY-exchange. A QSY line at BPSK31 is tens of seconds of it. Fixed the same way the command arm already is: snapshot `frames_transmitted()` at the top of the arm, drop on change at the bottom. Keyed on the counter rather than the emission, for the reason the existing comment gives — a future keyed emission on this arm must not silently miss it. Gated through a real daemon on the counting backend #1007's own test built. The fixture took three attempts and the first two passed against unfixed code: §97.119 only obliges a station that has transmitted, so the ID timer arms from TX activity and a daemon that has sent nothing never IDs. The working shape uses one command that produces two keyed transmits on two different arms, so the reopen count discriminates — 2 with the fix, 1 without. This is a patch on an ownership question, not an answer to it. #1308 — who owns the capture stream — remains the answer; this closes a hole that would otherwise stay open for as long as that decision takes. Refactors: CAP-55 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6
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.
Closes #1319.
What was wrong
#1007 established that the daemon must not hold its capture stream across a keyed transmit: nothing reads it, so on cpal the callback appends to an unbounded buffer and the next tick is handed one discontinuous blob of audio captured while this station was transmitting.
rx_streamis assigned at four sites, all inserver::run— and none is on the rx tick arm. Yet that arm keys the transmitter:server.rs:~991)process_received_bytes(:1043):1183)The station ID makes it unconditional
I filed this expecting it to affect OTA ACKs and QSY replies. Working out how to trigger one showed the periodic station ID transmits on that arm too — so every compliant station held its capture stream across a transmit on a fixed schedule, not just one mid-exchange.
Magnitude scales with airtime: an ACK is short, a QSY line at BPSK31 is tens of seconds.
The fix
Snapshot
frames_transmitted()at the top of the arm, drop on change at the bottom — the same shape as the command arm's:865drop, and keyed on the counter rather than the emission for the reason that comment already gives: a future keyed emission on this arm must not silently miss it.The fixture took three attempts, and the first two passed against unfixed code
§97.119 only obliges a station that has transmitted, so
id_duerequirestx_since_id. A daemon that has sent nothing never IDs, and both early versions were therefore vacuous.The working shape uses one command that produces two keyed transmits on two different arms: the
SendMessageon the command arm — whose drop is already correct — and the station ID it arms, which fires on the tick arm at once becauselast_id_msstarts at zero. The reopen count then discriminates directly.Driven through a real daemon over TCP on the counting backend #1007's own test built.
Sabotage-verified: disabling the tick-arm drop yields 1 reopen against the required 2, while the file's two pre-existing tests stay green — so the failure is attributable to this arm, not to the command arm's.
GATE: PASS c3a96970778ac953057ea306b7628ccc38e6d6ad clean— 328 suites, 2516 passed, 0 failed.This is a patch, not an answer
A third drop site in
server::runtreats the symptom of an ownership question. #1308 — who owns the capture stream — is the answer, and it has now been decided (the repeater consumes the daemon's burst stream). This closes a hole that would otherwise stay open for as long as that work takes, and it remains correct afterwards.Consumer
server::run's rx tick arm (server.rs:868-1252), the onlyselect!arm that both transmits and holdsrx_stream.Prior art
server.rs:826(pre-OTA, variant-keyed) and:865(post-command, counter-keyed) are the two existing drops; this is:865applied to its sibling arm. #1007 (8711a06c) is the rule and the source of the counting-backend instrument.Twins
rx_streamnow has a drop on both arms that transmit. The watchdog arm does not transmit. Found by running the twins grep I had left UNCHECKED on #1312 — which is also where the QSY scan's own capture defect came from.Refactors: CAP-55
🤖 Generated with Claude Code
https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6