Skip to content

A Drive that fails as it joins mid-Take gets no audio, so no false dropped frames (#544) - #944

Merged
Skeyelab merged 1 commit into
mainfrom
agent3/join-stop-race
Oct 23, 2026
Merged

Skeyelab merged 1 commit into
mainfrom
agent3/join-stop-race

Conversation

@Skeyelab

Copy link
Copy Markdown
Member

Closes #544.

Real-time path (ADR 0002): human review of the diff. This changes Capture.receive, which runs on the audio thread. The change is atomics only: no locks, no allocation, no blocking calls (checked by a Code Reviewer).

The bug

receive reads joinRequests, then a few lines later ORs them into capturing. A stopFeeding (the Drive failed or retired) that landed between the two left the Copy fed with no writer draining it. After about 4 s its ring overflowed and the Take reported dropped frames that never happened. #872 closed the rest of this; this window was left.

The fix

  • New stoppedFeeds mask, one bit per Copy. stopFeeding sets it first, before clearing the request and the capturing bit.
  • Every block masks capturing with ~stoppedFeeds, so a stopped Copy gets at most the block in flight.
  • The join block takes any Copy stopped since its request was read back out of capturing, so no stale bit is left for a later rejoin.
  • attachMidTake clears the Copy's stopped bit; startCapturing clears all of them.
  • A test-only hook, afterJoinRead (nil in the app), runs between reading the requests and acting on them. In the app it costs one nil check, only on a block with a join waiting.

Tests

  • stopFeedingDuringJoinRead: the Copy is stopped from inside the hook. Red first: before the fix, the stopped Copy got 960 frames; now 0.
  • rejoinAfterStopDuringJoinRead: a Copy that lost that race and joins again is fed only from its new join block, at its new join frame. Passed on its first run (it guards the stopped bit being cleared on rejoin).
  • Not covered by a test: a rejoin landing between receive's two mask loads. The join-block clear above closes it; hitting it deterministically would need a second hook.
  • Full ./scripts/verify.sh passed (spelling, vocabulary, package tests, macOS and iOS app builds).

Device Take needed (before External testers)

Checked by tests only. Ship in an [internal] tag (v1.1.16). Eric's Take from that TestFlight build: a Drive that fails as it joins mid-Take, with no false dropped frames in the report.

Follow-up noted, not fixed

Older than this change: joinRequests.bitwiseAnd(~requested) can wipe a new attach request that lands in the same block window, which needs a stop plus a reattach inside one block.

…opped frames (#544)

A stop that landed between receive reading the join requests and adding
them to capturing left the Copy fed: its ring overflowed after about 4 s
and the Take reported drops that never happened. A stopped mask now
masks capturing on every block, and the join block takes a Copy stopped
since its request was read back out of capturing, so no stale bit is
left for a rejoin. Real-time path (ADR 0002): atomics only.

Assisted-by: Claude Code:claude-opus-5-5
@codecov

codecov Bot commented Oct 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Skeyelab
Skeyelab merged commit 960b1e7 into main Oct 23, 2026
11 checks passed
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.

A Drive that fails as it joins mid-Take can report dropped frames that never happened

1 participant