call ready before starting the goroutine that calls ended - #495
Open
erikhortsch wants to merge 1 commit into
Open
call ready before starting the goroutine that calls ended#495erikhortsch wants to merge 1 commit into
erikhortsch wants to merge 1 commit into
Conversation
erikhortsch
force-pushed
the
erikhortsch/lifecycle-driven-session-tracking
branch
from
September 9, 2026 03:49
abeb962 to
4c9ff78
Compare
Base automatically changed from
erikhortsch/lifecycle-driven-session-tracking
to
main
September 9, 2026 03:57
createStream hands the caller ready and ended as an unordered pair. ready was deferred, so it ran on the way out of the goroutine that launches the one calling ended -- and a session whose done channel is already broken reaches ended without blocking. Init registers the RTC notify topic before it returns, so the SFU can break done at any point after the SDP exchange. The ordering is not close. ready's second statement sends a state update, which is a psrpc round trip, so ready parks almost immediately and hands its P to the goroutine sitting in runnext. ended then runs to completion before ready resumes to register the session. Calling ready inline before that goroutine exists removes the race by construction. It also keeps IngressStarted from landing after IngressEnded, which would leave a session in the manager that nothing removes and an IsIdle that never comes true.
erikhortsch
force-pushed
the
erikhortsch/whip-ready-before-ended
branch
from
September 9, 2026 17:22
19df8b0 to
a2d7e80
Compare
erikhortsch
marked this pull request as ready for review
September 10, 2026 16:59
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.
Stacked on #491. Review that first; this branch is one file.
The defect
createStreamhands the callerreadyandendedas a pair with no ordering between them.readywas deferred, so it ran on the way out of the goroutine that launches the one callingended:For a bypassed WHIP session that is already over,
WaitForSessionEndselects on an already-brokendoneand returns without blocking.Initregisters the RTC notify topic before it returns, so the SFU can breakdoneat any point after the SDP exchange — a publisher whose connection fails during the handshake, a client that hangs up on the answer, an immediate resource delete.This is not a narrow scheduling window.
ready's second statement isSendStateUpdate, a psrpc round trip, soreadyparks about two statements in and hands its P to the goroutine sitting inrunnext.endedthen runs to completion in the middle of it:Three consequences: the terminal state is overwritten by
PUBLISHING(SetStatushas no terminal guard, last write wins); the notifier is told a released session started; andIngressStartedlands afterIngressEnded, leaving an entry in the session manager that nothing removes, soIsIdlenever comes true and shutdown does not complete.Only bypassed WHIP is affected —
endedis non-nil only under!*p.EnableTranscoding. Every other input registers synchronously insidereadyand is handed a nilended.The fix
Call
readyinline, before the goroutine that callsendedexists. There was exactly onereturnbetween the defer and thego, so the restructure is mechanical: the deferred body becomes straight-line code and the error branch keeps its ownreturn.No synchronization primitive. A
sync.Oncewould forceendedto synthesize aready(nil, err)it has no arguments for, which rewrites a clean short session's terminal state toENDPOINT_ERROR; a channel barrier works but deadlocks ifreadyis ever nil, since the close sits under the sameif.The only thing lost is panic-unwinding coverage, which was not real — nothing recovers here, so the process dies either way.
Verification
go build ./...,go vet ./pkg/...,go test -count=1 ./pkg/...— 8 packages, all pass.Not covered by a test:
createStreamreaches the race only through a live SFU HTTP exchange inInitand a psrpc-backed notifier, neither of which has a seam here.