byoc: drain data subscriber on /stream/stop before tearing down#3927
byoc: drain data subscriber on /stream/stop before tearing down#3927rickstaa wants to merge 1 commit into
Conversation
Fixes #3924. The gateway's data SSE proxy was tearing down its trickle subscriber as soon as /stream/stop arrived, which races the runner's on_stream_stop hook. Any final emit_data the runner publishes during its stop hook (e.g. live_transcribe's last partial-window flush) is written to the trickle channel after the gateway has already cancelled its subscriber's context — so the segment is never read, never relayed to the SSE caller. Decouple the data subscriber's context from /stream/stop. After the stream context is cancelled, give the subscriber up to drainTimeout (10s) to keep reading. The trickle channel closes cleanly when the runner SDK tears down its data publisher (DELETE → next Read returns trickle.EOS), at which point the loop exits via the existing EOS path. The timeout caps the drain so a hung runner can't keep the goroutine alive forever. Bug reproduced 3/3 against examples/runner/live_transcribe in the livepeer-python-gateway SDK: the on_stream_stop final-flush transcript consistently drops. Fix not yet validated end-to-end pending a test image build. Refs livepeer/livepeer-python-gateway#12 (companion SDK-side fix).
5adcbbb to
5139d15
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## trickle-next-endpoint #3927 +/- ##
===============================================================
- Coverage 32.88179% 32.85022% -0.03157%
===============================================================
Files 171 171
Lines 42078 42088 +10
===============================================================
- Hits 13836 13826 -10
- Misses 27199 27219 +20
Partials 1043 1043
... and 1 file with indirect coverage changes Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
|
Closing — going with the structural fix instead of the gateway patch. The data SSE proxy race fixed here only affects the legacy curl-through-gateway path. The canonical Python client SDK (livepeer/livepeer-python-gateway#6) subscribes to the trickle channel directly and provides its own Once tests + downstream consumers migrate to the SDK path, this gateway path is deprecated. Adding 26 lines of drain logic here doesn't pay off if the path is going away. Branch left in place at |
Draft. Pending end-to-end validation against
examples/runner/live_transcribeonce CI publishes a docker image for this branch.Fixes #3924.
The bug
The gateway's data SSE proxy was tearing down its trickle subscriber as
soon as
/stream/stoparrived, which races the runner'son_stream_stophook. Any final
emit_datathe runner publishes during its stop hook(e.g.
live_transcribe's last partial-window flush) is written to thetrickle channel after the gateway has already cancelled its
subscriber's context — so the segment is never read, never relayed to
the SSE caller.
Reproduction (3/3)
Against
livepeer-python-gateway/examples/runner/live_transcribe, running the existing./test.sh3 times:Gateway log on each run:
The data subscriber was mid-GET on the next segment when ctx cancellation fired.
The fix
Decouple the data subscriber's context from
/stream/stop. After thestream context is cancelled, give the subscriber up to
drainTimeout(10s) to keep reading. The trickle channel closes cleanly when the
runner SDK tears down its data publisher (DELETE → next Read returns
trickle.EOS), at which point the loop exits via the existing EOSpath. The timeout caps the drain so a hung runner can't keep the
goroutine alive forever.
What changed
byoc/trickle.go:startDataSubscribe:drainCtxrooted incontext.Background()so it doesn't get cancelled by/stream/stopctx.Done()(stream stop), then starts a 10s timer before cancellingdrainCtxdrainCtxinstead ofctxinputStreamExists()guard is skipped oncectxis already cancelled — during drain the stream tracking is gone but we still want to read pending segmentsdefer drainCancel()ensures the watchdog goroutine never leaksCompanion SDK fix
Tracked at livepeer/livepeer-python-gateway#12 (probe + fallback fix landed in PR #3925 + #3926). This PR closes the data-channel half of the live_transcribe drop story.
Test plan
go build ./...passesgo test ./byoc/...passes (existing tests; no new ones added in this PR)live_transcribecompose against this image, run./test.sh3× and confirm 7/7 transcripts deliver/stream/stopresponseRisk / scope
Worst case adds up to 10s to gateway cleanup when the runner doesn't close its data publisher. Doesn't affect the
/stream/stopHTTP response time — the drain happens in a background goroutine. No changes to other subscribers (media-out, events) or to the publisher path.🤖 Generated with Claude Code