Skip to content

Fix reading catalogs#1404

Merged
kixelated merged 2 commits into
moq-dev:mainfrom
Qizot:fix-catalog-consumer
May 15, 2026
Merged

Fix reading catalogs#1404
kixelated merged 2 commits into
moq-dev:mainfrom
Qizot:fix-catalog-consumer

Conversation

@Qizot
Copy link
Copy Markdown
Contributor

@Qizot Qizot commented May 14, 2026

It happened that the previous logic for reading the catalogs had a race condition where a pending frame in a group could case a group drop in self.group.take().

This PR changes the behaviour of when we drop old groups.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b94f9cd4-fdb2-4749-a599-a4b2fb501a7e

📥 Commits

Reviewing files that changed from the base of the PR and between 86ee91b and 6bf3e2a.

📒 Files selected for processing (1)
  • rs/moq-mux/src/catalog/consumer.rs

Walkthrough

Consumer now tracks an internal finished flag and implements a state-aware poll loop: it repeatedly calls TrackConsumer::poll_next_group, marks finished when the track yields Ready(None), and either returns Ok(None) if no group exists or polls the acquired group's poll_read_frame. When a frame arrives it is decoded into a Catalog and returned; otherwise poll_next can remain Pending even after the track finished. A comprehensive test module validates pending behavior, ordering, superseding, and empty-track end semantics.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main objective of the PR: fixing a race condition in catalog reading logic.
Description check ✅ Passed The description is related to the changeset and explains the race condition being fixed and the behavioral change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
rs/moq-mux/src/catalog/consumer.rs (1)

50-103: ⚡ Quick win

Add test coverage for the new Ok(None) end-of-stream and multi-group paths.

The two added tests validate the basic happy path and the "track finished before frame written" race. Two gaps remain on paths this PR introduces or changes:

  1. The new Poll::Ready(None) => Poll::Ready(Ok(None)) branch in poll_next is not exercised. A small test that finishes both the group and the track, drains the catalog, and then asserts the subsequent poll_next returns Ok(None) would lock in end-of-stream behavior.
  2. With the group-tracking logic removed, there is no test asserting that when multiple catalog groups are written, the consumer surfaces the newest catalog rather than an older one. This is the semantic the previous implementation explicitly preserved, and tying down a test would prevent silent regressions if the underlying moq_lite behavior shifts.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rs/moq-mux/src/catalog/consumer.rs` around lines 50 - 103, Add two unit tests
in the existing test module: (1) a test (e.g.,
waits_for_end_of_stream_returns_ok_none) that creates a track via
Catalog::default_track().produce(), a Consumer::new(track.consume()), appends a
group, writes a catalog frame, then finishes the group and finishes the track,
drains the single catalog via consumer.poll_next(&waiter) and finally calls
consumer.poll_next(&waiter) again to assert it returns Poll::Ready(Ok(None)) to
cover the end-of-stream branch in Consumer::poll_next; (2) a test (e.g.,
surfaces_newest_catalog_from_multiple_groups) that appends two groups to the
same track, writes an older catalog payload to the first group and a newer
catalog payload to the second group, finishes both groups (and optionally the
track), then asserts successive consumer.poll_next(&waiter) yields the newer
catalog (the last-written group) to lock in the multi-group semantics now
expected by Consumer::poll_next; reuse existing helpers like
Catalog::default_track, append_group, write_frame, finish and
conducer::Waiter::noop to set up and poll.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@rs/moq-mux/src/catalog/consumer.rs`:
- Around line 50-103: Add two unit tests in the existing test module: (1) a test
(e.g., waits_for_end_of_stream_returns_ok_none) that creates a track via
Catalog::default_track().produce(), a Consumer::new(track.consume()), appends a
group, writes a catalog frame, then finishes the group and finishes the track,
drains the single catalog via consumer.poll_next(&waiter) and finally calls
consumer.poll_next(&waiter) again to assert it returns Poll::Ready(Ok(None)) to
cover the end-of-stream branch in Consumer::poll_next; (2) a test (e.g.,
surfaces_newest_catalog_from_multiple_groups) that appends two groups to the
same track, writes an older catalog payload to the first group and a newer
catalog payload to the second group, finishes both groups (and optionally the
track), then asserts successive consumer.poll_next(&waiter) yields the newer
catalog (the last-written group) to lock in the multi-group semantics now
expected by Consumer::poll_next; reuse existing helpers like
Catalog::default_track, append_group, write_frame, finish and
conducer::Waiter::noop to set up and poll.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3edb61a9-c2ed-4263-81e1-bed4fb3ecc13

📥 Commits

Reviewing files that changed from the base of the PR and between 6adf3c3 and c138466.

📒 Files selected for processing (1)
  • rs/moq-mux/src/catalog/consumer.rs

@Qizot Qizot force-pushed the fix-catalog-consumer branch from c138466 to 86ee91b Compare May 14, 2026 10:41
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
rs/moq-mux/src/catalog/consumer.rs (1)

82-234: 💤 Low value

Test coverage is solid; minor nit on module naming.

The six tests cover the meaningful state transitions (pending-before-payload, pending-after-track-finish, latest-wins, newer-pending-preferred-over-older-ready, retained-pending-superseded, empty-finished-track). Nicely scoped.

Optional nit: mod testmod tests is the more conventional Rust idiom for a test submodule, e.g., as recommended by Rust API guidelines and used throughout the standard library. Trivial rename if you care.

♻️ Proposed rename
 #[cfg(test)]
-mod test {
+mod tests {
     use std::task::Poll;

One coverage gap you might consider (also optional): there is no test for group.poll_read_frame returning Poll::Ready(None) while the track is still open (i.e., a group that finishes without ever delivering a frame). The branch on Lines 51-54 currently isn't exercised by any of the new tests. Easy to add if you want full branch coverage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rs/moq-mux/src/catalog/consumer.rs` around lines 82 - 234, Rename the test
module from "mod test" to the conventional "mod tests" (update the #[cfg(test)]
mod declaration) and keep all existing tests (e.g.,
waits_for_pending_catalog_group_payload, returns_latest_complete_catalog_group,
retained_pending_group_is_superseded_by_newer_group) intact; optionally add one
more unit test that constructs a track and group via
Catalog::default_track().produce(), calls track.append_group() then immediately
finishes the group without writing a frame (exercise group.poll_read_frame ->
Poll::Ready(None) while track still open) and assert Consumer::poll_next returns
the expected Ready(Ok(None)) or pending behavior to cover the unexercised
branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@rs/moq-mux/src/catalog/consumer.rs`:
- Around line 82-234: Rename the test module from "mod test" to the conventional
"mod tests" (update the #[cfg(test)] mod declaration) and keep all existing
tests (e.g., waits_for_pending_catalog_group_payload,
returns_latest_complete_catalog_group,
retained_pending_group_is_superseded_by_newer_group) intact; optionally add one
more unit test that constructs a track and group via
Catalog::default_track().produce(), calls track.append_group() then immediately
finishes the group without writing a frame (exercise group.poll_read_frame ->
Poll::Ready(None) while track still open) and assert Consumer::poll_next returns
the expected Ready(Ok(None)) or pending behavior to cover the unexercised
branch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5ee442a2-2c99-4d94-9b31-851ee47cdb1a

📥 Commits

Reviewing files that changed from the base of the PR and between c138466 and 86ee91b.

📒 Files selected for processing (1)
  • rs/moq-mux/src/catalog/consumer.rs

Track finished state inside poll_next via a local variable. The underlying
TrackConsumer reports Ready(None) idempotently once finished, so re-deriving
it each call is equivalent to caching it in a field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kixelated kixelated merged commit 6e7d918 into moq-dev:main May 15, 2026
1 check passed
@moq-bot moq-bot Bot mentioned this pull request May 15, 2026
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.

2 participants