Skip to content

fix(mux): bound what the decode clock holds in MKV and TS export - #6417

Merged
kixelated merged 3 commits into
mainfrom
quest/m1/release-audit/export-reorder
Oct 24, 2026
Merged

kixelated merged 3 commits into
mainfrom
quest/m1/release-audit/export-reorder

Conversation

@kixelated

@kixelated kixelated commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Started from quest/m1/release-audit/export-reorder.md, which main has since folded into Decode order (moq-dev/moq#6390), so this PR no longer deletes a quest. moq-dev/moq#6453, that quest's PR, keeps the inferred clock for renditions without a declared reorder and files its bound as quest/m1/release-audit/inferred-clock-bound.md: this PR is that bound.

MKV and TS export run reordered video through DecodeClock to author DTS. The clock sits upstream of the jitter buffer, so jitter.full backpressure never saw it. If PTS stopped advancing past the lookahead in an open group (flat, or running backward), every frame stayed in the clock. Flushing then drained it in quadratic time.

Reproduced at the exporter level with a live H.264 track (120 ms catalog jitter, 100 ms delay, 20 ms frames, 500 frames), using buffered() extended to count clock-held frames:

  • MKV and TS, flat PTS: 500 frames held (all of them).
  • Old clock flushing N flat or backward frames: 1k 0.5 ms, 10k 38 ms, 100k 3.8 s (quadratic).

Fix (in rs/moq-mux/src/container/decode.rs):

  • Each held frame now stores its arrival. A frame counts as overdue once the latest arrival read is more than its effective lookahead (max(lookahead, reach), clamped to MAX_REORDER) plus the export's delay past it. DecodeClock::settle then returns Settle::Front, and pop settles that frame along with whatever is already due. A live source's PTS keeps pace with its arrival, so the clock holds at most what the source sends in that window. That is the same time-times-ingress bound the jitter buffer uses. No timer, no frame count.
  • The check runs only after the newest frame has been pushed. A valid stream is therefore always settled by the normal rule first. A live B-pyramid run through MKV gives byte-identical DTS with and without the rule.
  • pop now takes Settle { Due, Front, All } in place of flush: bool. Front settles only the front slot, not every held pair, so a hostile stream cannot force a full rescan on each frame.
  • slots is now a VecDeque, and a bounded cursor records which slot/frame pairs already bound the delay. Each pair is scanned once and the drain is linear. Outputs are unchanged: every existing clock and exporter test passes.
  • The clock now clamps the lookahead to MAX_REORDER itself. TS's catalog-jitter path had skipped that clamp (MKV already clamped).

Measured with the new decode_clock bench (nightly rust-extra), swept over held frames x PTS shape x payload size:

1k 10k 100k
drain, flat (old clock) 0.5 ms 38 ms 3.8 s
drain, flat 29 us 0.34 ms 4.2 ms
drain, backward 30 us 0.39 ms 5.5 ms
read, backward under the bound 29 us 0.39 ms 4.5 ms

Payload size (64 B vs 4 KiB) does not change drain time. The clock moves Bytes handles, so retained memory is held frames x payload, and that is what the exporter tests bound.

Tests:

  • mkv::export_test::stalled_presentation_time_keeps_the_export_bounded and ts::export_test::stalled_presentation_time_keeps_the_export_bounded: flat and backward PTS in an open group. Each asserts that the export holds no more than what arrives within lookahead + delay.
  • mkv::export_test::live_reordered_video_keeps_every_frame: a live B-pyramid keeps every frame.

Behavior changes for malformed input

  • Video whose PTS stops advancing (flat, or backward past the lookahead) is no longer held until the group or track ends. The frame held longest goes out once overdue. Its DTS stays strictly increasing (later frames below it are nudged, as before), and the jitter buffer may drop it as late.
  • Under TS, a catalog jitter above 2 s now waits at most 2 s (MAX_REORDER), as MKV already did.
  • The same rule applies to valid streams after a network stall longer than lookahead + delay. Those frames were already late. TS with a zero delay waits only the lookahead, so undeclared reordering under network jitter wider than that can get nudged DTS.

Decisions

(Written by Claude Opus 5.5)

Public API / wire impact

None. All changes are pub(crate) inside moq-mux. No wire change.

Consumers: CLI MKV and TS export.

Checks

  • Merged main, which removed export ts --sync (moq-dev/moq#6554): this PR's pop_counted and discard hunks are dropped with it.
  • just check: rs passed. The flake module fails locally building the runner VM image (unshare: write failed /proc/self/uid_map, a host user-namespace limit, unrelated to this diff); CI runs it.
  • cargo nextest run -p moq-mux: 1669 passed.
  • quest check --base origin/main: passed.
  • cargo bench -p moq-mux --bench decode_clock: ran; sh/gh/nightly.test.py passes with the new bench target.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EeyiEy5FWz3z44DYZVo7vU

kixelated and others added 2 commits October 22, 2026 16:44
A video track whose PTS stopped advancing past the reorder lookahead in an
open group held every frame in the decode clock, outside the jitter buffer's
backpressure, and a flush then drained it in quadratic time.

The frame held longest now settles once it has waited, by the latest arrival
read, longer than its lookahead plus the export's delay, so what the clock
holds stays within what the source sends in that time. The lookahead is
clamped to MAX_REORDER in the clock, which TS's catalog jitter skipped.
Slots are a VecDeque and each slot/frame pair bounds the delay once, so a
drain is linear.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EeyiEy5FWz3z44DYZVo7vU
@kixelated kixelated changed the title Include decode reordering in exporter backpressure fix(mux): bound what the decode clock holds in MKV and TS export Oct 23, 2026
@kixelated
kixelated marked this pull request as ready for review October 23, 2026 00:27
@kixelated

Copy link
Copy Markdown
Collaborator Author

Outcome: reproduced unbounded clock retention (500/500 frames held on flat PTS, MKV and TS) and quadratic flush (100k frames 3.8 s). Fixed by settling the frame held longest once it has waited longer than lookahead + export delay by arrival, plus a linear drain (100k: 4.2 ms). No open decisions. just check passed. (Written by Claude Opus 5.5)

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 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-10-24T00:20:49.449529Z 8736d83 New commits
ℹ️ 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.

@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You'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 43 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 2cd38788-3da7-4380-9b86-1f0597079aad
📥 Commits

Reviewing files that changed from the base of the PR and between 21c850e and 8736d83.

📒 Files selected for processing: 8
  • rs/moq-mux/Cargo.toml
  • rs/moq-mux/benches/decode_clock.rs
  • rs/moq-mux/src/container/decode.rs
  • rs/moq-mux/src/container/mkv/export.rs
  • rs/moq-mux/src/container/mkv/export_test.rs
  • rs/moq-mux/src/container/ts/export.rs
  • rs/moq-mux/src/container/ts/export_test.rs
  • sh/gh/nightly.sh
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e74d92e339

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rs/moq-mux/src/container/decode.rs
@kixelated

Copy link
Copy Markdown
Collaborator Author

Coordination with moq-dev/moq#6554, which removes export ts --sync: if it merges first, drop this PR's sync-specific hunks in rs/moq-mux/src/container/decode.rs and ts/export.rs when merging main instead of carrying them forward.

(Written by Claude Opus 5.5)

# Conflicts:
#	quest/m1/release-audit/README.md
#	rs/moq-mux/src/container/decode.rs
#	rs/moq-mux/src/container/ts/export.rs
@kixelated
kixelated enabled auto-merge October 24, 2026 00:36
@kixelated
kixelated added this pull request to the merge queue Oct 24, 2026
kixelated added a commit that referenced this pull request Oct 24, 2026
Main removed `export ts --sync` (#6554), so the counted clock, its tests and
the sync docs go; the declared clock and its legs test stay, the latter on a
B-pyramid Live re-added to the timing harness. Drop the inferred-clock-bound
follow-up quest, which #6417 delivers, and unblock encoder-reorder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged via the queue into main with commit ccd9609 Oct 24, 2026
12 checks passed
@kixelated
kixelated deleted the quest/m1/release-audit/export-reorder branch October 24, 2026 03:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant