fix(sound): guard recording lifecycle - #870
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
Reviewed the diff and checked out the branch to verify directly. This is a careful, well-tested concurrency fix — no issues found. The bug (#799): The fix: Tracing the release paths for
Provenance check: the PR body's account of "CodeRabbit flagged a stale cleanup edge case" checks out — I looked at the original #839's comment history and CodeRabbit's walkthrough is there, followed by the author's own commit message describing the exact same fix now present here ( Verified locally (checked out
No changes requested from me — this is a solid fix for a real race, and the test suite specifically targets the subtle generation-token edge case rather than just the happy path. |
|
Thanks @11suixing11 for this contribution. Good work! |
Re-opens #839, which was automatically closed (not merged) when its base branch
conductor-migrationwas deleted after being merged intomastervia #680. This branch cherry-picks the original commits from @11suixing11 unchanged, onto currentmaster.Summary
recordorrecord_foraccepts a requestrecord's returned stop function idempotent so repeated calls reuse onestopRecording()promisestartRecording/stopRecordingfailuresstop()cannot release a newer reservationFixes #799
Why
recordandrecord_forschedule delayed work beforeio().startRecording()runs. The existingactivePlayCountcheck prevents recording during playback, but there was no state reserved for a recording request itself. Two quick calls could therefore schedule independent timeout chains and eventually attempt overlapping recordings against the same tab-side recorder.The PR reserves recording state at call time, so a second request fails before either timeout chain reaches
startRecording().Review Follow-up
CodeRabbit flagged a stale cleanup edge case: after
startRecording()rejects and releases the reservation, invoking the old returnedstop()could settle a second cleanup after a newer recording had reserved the mic.reserveRecording()now returns a generation token, andreleaseRecording()only clears state when that token still identifies the active reservation. The regression test covers this exact sequence:record()fails during startupstop()from the failed first recording is invokedTesting (from original PR, re-verify on CI)
yarn workspace @sourceacademy/bundle-sound test(71 passed)yarn workspace @sourceacademy/bundle-sound tscyarn workspace @sourceacademy/bundle-sound lintOriginal author: @11suixing11