Repository navigation
feat(studio): quitting delivers the broadcast's tail before the relay session closes - #46
Conversation
Quest-Claim: e82aaaf920ecbe45
… session closes Quitting now stops every source, so each finishes its own track, and then closes each relay session once the relay has every finished track's tail, bounded at a second by the session and three overall, before the runtime goes. A start whose worker thread fails partway stops those already running. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 27 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 235e970
No actionable findings. Joining the program/audio workers before graceful connection closure addresses the earlier relay-drain concern, and startup rollback joins workers already started. The shutdown ordering fits the existing source and transport lifecycles.
Verification: GitHub-only static review, including the pinned transport. I did not run tests, GPU/capture execution or thread-failure injection; hosted Check was queued when inspected.
There was a problem hiding this comment.
Important
The relay close starts in parallel with the source-thread wait, so a source's last group can still be cut — the failure this PR sets out to remove, now only a race away.
Reviewed changes
- Sources stop at quit:
Sources::stopdrops every feed, so eachDriverreturns and itsVideo/Audio/Stillfinishes its own track;Engine::stopcalls it after joining the program, audio, and encode threads. - Relay sessions close before the runtime goes:
Engine::closewaits on the source threads and closes each relayConnection(drain) then itsClient(QUIC close), bounded byCLOSE;mainjoins it withrecorder.done(). - Failed starts roll back: a
Startingguard holds the engine until everyclockedspawn succeeds, dropping intoEngine::stopon the first failure. - Tests and docs: a paused-clock
closing_delivers_every_tail,stopping_the_sources_finishes_their_tracks, the GPU quit test now reading the program audio to its end, the README quit bullet, and the completed quest deleted.
big-pickle (free) | 𝕏
# Conflicts: # rs/moq-studio/src/engine.rs
…s video A further canvas's broadcast now lives on in what its line leaves running, so its catalog finishes, and the broadcast closes, only once the encoders have finished the video it lists, on removal and on quit alike. Before, the catalog was dropped unfinished as the line ended, cutting it at the relay. Tests: the further canvas GPU test reads its video and catalog to a clean end on removal and on quit; a media source's picture and sound tracks finish when the sources stop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit 74a80a6. No actionable findings in the substantive changes since 235e970, accounting for merged main changes. Further-canvas broadcasts remain alive until encoders finish, then finish their catalogs before closing. The source/relay parallel close remains appropriate because source tracks stay local. The lifetime handling is a reasonable direction.
Verification: GitHub-only static review, including relevant pinned transport and catalog behavior. I did not run tests, GPU/capture execution, or thread-failure injection.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Since the prior Pullfrog review on 235e970, the branch merged main and added 47d47b7 to finish a further canvas's catalog after its video.
- A further canvas's catalog finishes after its video:
Line::endnow takes the canvas'sBroadcastand carries it inEnded, socatalog.finish()andpublication.close()run only once the encode threads have finished, on removal and at quit alike. - Source tracks confirmed local at the join: a comment at
Engine::closerecords that source tracks live in the studio's own broadcast, which no relay session carries, so the concurrent source wait and relay close cannot cut a tail. - Media source stop test:
stopping_the_sources_finishes_a_media_files_tracksreads a looping media file's picture and sound to a clean end afterSources::stop. - Further canvas GPU test extended: it now shares
routed_tall/endedhelpers and reads the canvas's video and then catalog to a clean end both on removal and on quit.
big-pickle (free) | 𝕏

Finishes quest/m0/clean-quit.md, from moq-dev/studio#23 and moq-dev/studio#22.
Problem
Engine::stopdrained and finished the program video, butmainthen returned and dropped the runtime, so each relay session went with the tail still on its way. Source threads were never stopped at exit. AndEngine::startstarted the program thread before the audio and autosave threads, so one of those failing to start left the program thread running, holding the engine and its publication beside the retry setup makes.Approach
moq_scene::source::Sources::stopdrops every feed, as removing a source would, so each driver returns and its own track finishes cleanly (the existingDroppaths ofVideo,Audio, andStill).Engine::stopcalls it after joining the program and audio threads, before stopping the browser helper.Engine::close(async), run bymainafterstopand besiderecorder.done(), before the runtime drops:spawnnow returns a oneshot receiver that closes as the thread's task ends; the engine keeps them, forgetting ended ones as new sources start.Output::closecloses every relay connection in parallel withmoq_tokio::Connection::close, which withdraws the announcement and drains finished tracks' tails (bounded at one second by the session), thenClient::closeso the relay gets the QUIC close rather than waiting out its idle timeout.Outputnow keeps each relay's client for that.CLOSE), so a source or relay that never answers cannot hang quitting.Startingguard holds the engine while the worker threads start; if anyclockedspawn fails, dropping the guard callsEngine::stop, which ends and joins the threads already running.Engine::stopalready joins every canvas's encoders. A further canvas'sBroadcastnow rides in theEndedits line leaves, so it drops only after those encode threads, and itsDropfinishes the canvas's catalog before closing the publication. Before, the catalog was dropped unfinished as the line ended, cutting it at the relay; this applies to removing a canvas too.Sources::stopcovers them as is; their picture (Encoding) and sound (Audio) tracks finish in theirDropas the driver returns.Tests
engine::tests::closing_delivers_every_tail(default suite, paused tokio clock, localmoq_relay::test_relay): two tracks each get a head group read through the relay, then a 500 KB last group and a finish;Output::closeruns while readers at the relay read. Each reader gets its whole last group and then a clean end, and the relay's announcements endstudio. Mutation-checked: dropping the output instead of closing it cuts the tail withSession(Cancel). Stress-run 30 times on the paused clock with no failure.publish::tests::stopping_the_sources_finishes_their_tracks(moq-scene):Sources::stopfinishes a color source's still track cleanly and takes it out of the local catalog.publish::tests::stopping_the_sources_finishes_a_media_files_tracks(moq-scene, paused clock): a looping media file on program, shown and heard, has both its picture and sound tracks read to a clean end afterSources::stop. Mutation-checked: skippingEncoding's end in itsDropfails it.engine::tests::the_program_of_a_further_canvas_has_its_own_broadcast(GPU, nightlyrender-sw) now reads the canvas's video and catalog to a clean end through the relay on removal, then undoes the removal and does the same on quit (stopthenclose). Mutation-checked: not finishing the catalog fails it.engine::tests::the_program_reaches_a_relay(GPU, nightlyrender-sw) now quits withstopthencloseand reads both the program video and the program audio to a clean end at the relay. Passes locally on macOS (Metal and VideoToolbox), as doesthe_program_records_without_a_relay.Choices made without asking
Dropjoins the started threads inside the asyncstart, which takes about a frame plus the encoders' tails. It only runs on a failed start.guests::Renewal) is not closed gracefully. It carries the guests' return feeds, not the broadcast, so it is outside this quest's goal.Validation
just cipasses locally on47d47b7(225 tests).cargo nextest run -p moq-studio --features openh264 --run-ignored only -E 'test(/^engine::tests::the_program_/)'passes on macOS (Metal, VideoToolbox): all three, including the further canvas test.Review
Codex (
quest review), gpt-6.1-sol, medium effort, againstorigin/main(206e91e, merge basee012e2e).47d47b7a2a83bbdd0113f3ebaccb977c79076a1b(after merging main with moq-dev/studio#42, #43, #44): no findings.235e970: no findings.Follow-ups
None.
(Written by Claude Opus 5.5)
🤖 Generated with Claude Code