Skip to content

fix(media): keep a publication Cloudflare lists as active through propagation lag - #792

Merged
realhasanshoaib merged 1 commit into
masterfrom
fix/media-publication-active-track-guard
Nov 1, 2026
Merged

realhasanshoaib merged 1 commit into
masterfrom
fix/media-publication-active-track-guard

Conversation

@realhasanshoaib

@realhasanshoaib realhasanshoaib commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

In a rig with an impaired network, a speaker's new microphone track was closed by the API about 3 s after it was published, so all 7 listeners never heard that speaker until the speaker toggled the mic by hand. This was the only closure in 262 rooms. The server does it, so every SDK version is hit, including 5.2.6.

The API checks Cloudflare for the publisher track before closing, and Cloudflare did list it as active. The close came from a second rule: if listeners' pulls of an active track keep failing for 2 s, treat the track as stuck and withdraw it so the speaker republishes. But Cloudflare answers pulls with not_found_track_error for several seconds (4.5 s in the rig) after a track turns active, so a healthy track hit that rule.

Fix

  • In apps/api/internal/mediapublications/liveness.go, listener failures on an active track count only after 10 s (publicationActiveTrackGrace), up from 2 s. A track that is still stuck after 10 s is withdrawn as before.
  • A track that Cloudflare reports as missing, inactive or with no session still closes at once, and a failed Cloudflare check still falls through to the same path as today. No new Cloudflare call is added.
  • Every skipped closure increments chalk.media.publication_closure_skipped and logs media_publication.publisher_track_close_skipped with listener_failing_ms, at most once per 2 s per publication.

I chose a longer failure window over a minimum publication age because the failure timer already starts at the first failed pull, which follows the publish within milliseconds. It also needs no new data and still lets a late-joining listener trigger healing on an old stuck track.

Proof

  • TestConfirmRemoteTrackFailuresKeepsActiveTrackThroughPropagationLag feeds three listeners failing every 1.2 s for 6 s against an active track. It fails on master (closed at 2.4 s) and passes now.
  • TestConfirmRemoteTrackFailuresClosesOnlyGoneOrInactiveTracks still passes: missing, inactive and session-not-found tracks close on the first check. TestConfirmRemoteTrackFailuresWithdrawsActiveTrackListenersStillFail and the withdrawal table test now wait for the 10 s window and still withdraw a stuck track.
  • Not checked: the real Cloudflare propagation lag beyond the 4.5 s seen in the rig, so 10 s is a margin of about 2x, not a measured bound.

Needs you

  • Healing of a track that is really stuck while Cloudflare lists it as active now takes about 10 to 12 s instead of 2 to 4 s. Change publicationActiveTrackGrace to tune it.

Undo: revert this commit; it changes one constant's use and adds a counter.

Created with Claude Sonnet 5.5 in Claude Code.


Devin Review

…pagation lag

Listener pulls answer "track not found" for several seconds after a track
turns active. The liveness sweep treated 2 s of such failures as a stuck track
and closed the publication, so every listener lost the speaker until the
speaker toggled the mic. Failures on an active track now count only after
10 s. Dead or inactive tracks still close at once. Each skipped closure is
counted (chalk.media.publication_closure_skipped) and logged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@realhasanshoaib realhasanshoaib added risk:medium Medium risk: Hasan reads the diff before it merges. size:M 30-99 changed lines, not counting tests, lockfiles or generated files. labels Nov 1, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-01T22:34:45.955921Z 04e4704 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

Devin Review

Comment on lines +123 to +138
func TestConfirmRemoteTrackFailuresKeepsActiveTrackThroughPropagationLag(t *testing.T) {
now := time.Date(2026, 8, 25, 12, 0, 0, 0, time.UTC)
repository := &reconciliationRepositoryStub{}
service := newService(repository, func() time.Time { return now })
tenantID, episodeID, publisherID, identity := publishRemoteObservationFixture(t, service, repository)
listeners := []utilities.ID{
reconciliationTestID(t, "44444444-4444-4444-8444-444444444444"),
reconciliationTestID(t, "55555555-5555-4555-8555-555555555555"),
reconciliationTestID(t, "66666666-6666-4666-8666-666666666666"),
}

// Every listener retries its failed pull about every 1.2 s for 6 s.
for elapsed := time.Duration(0); elapsed <= 6*time.Second; elapsed += 1200 * time.Millisecond {
now = time.Date(2026, 8, 25, 12, 0, 0, 0, time.UTC).Add(elapsed)
for _, listenerID := range listeners {
if result := confirmActiveTrackFailure(t, service, tenantID, episodeID, listenerID, identity); result.Closed != 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Production regression lacks observed failure fixture

The new TestConfirmRemoteTrackFailuresKeepsActiveTrackThroughPropagationLag uses invented camera failures and retry timing, not the observed microphone failure. The required production regression must derive from a sanitized request, trace, or bundle.

Learn more

The repository's review standard requires a production-failure fix to have a regression built from an observed request, retained trace, or sanitized bundle. This test creates a synthetic camera publication and assumes three listeners retry at fixed 1.2-second intervals. It does not encode the reported microphone failure from the rig, so it cannot establish that the observed sequence stays open.

Example: The reported failure closed a new microphone publication after roughly three seconds with seven listeners; this fixture uses a camera publication and three listeners for six seconds.

Recommended fix: Use a sanitized fixture derived from the rig's logged request or retained trace, including the observed track source, failure sequence, and timestamps. Assert that the observed sequence keeps the publication enabled, alongside the existing synthetic coverage.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +326 to +329
publicationClosuresSkipped.Add(ctx, 1)
slog.InfoContext(ctx, "kept media publication that Cloudflare lists as active",
"event", "media_publication.publisher_track_close_skipped",
"listener_failing_ms", failingFor.Milliseconds(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Skipped-closure count mixes distinct decisions

publicationClosuresSkipped counts every retained active track, including reports from departed listeners and tracks successfully served to others. It cannot isolate propagation-lag cases without a reason breakdown.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@realhasanshoaib
realhasanshoaib merged commit 570a3d3 into master Nov 1, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:medium Medium risk: Hasan reads the diff before it merges. size:M 30-99 changed lines, not counting tests, lockfiles or generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant