Skip to content

moq-net(stats): take a StatsConfig value type in Stats::new#1537

Merged
kixelated merged 3 commits into
mainfrom
claude/thirsty-sammet-d3c1fc
May 29, 2026
Merged

moq-net(stats): take a StatsConfig value type in Stats::new#1537
kixelated merged 3 commits into
mainfrom
claude/thirsty-sammet-d3c1fc

Conversation

@kixelated
Copy link
Copy Markdown
Collaborator

@kixelated kixelated commented May 29, 2026

Summary

Follow-up to #1517, which left moq_net::Stats::new taking 5 positional args (prefix, interval, retention, node, origin), two of them path-like and easy to misorder. This replaces it with a dedicated StatsConfig value type and bumps the patch versions for the (unreleased) stats API break.

What changed

  • StatsConfig value type + Stats::new(config). StatsConfig::new(origin) plus chained with_prefix / with_interval / with_retention / with_node, each defaulting to sensible values (.stats, 1s, retention 1, no node), handed to Stats::new(config).
  • Why not a by-value builder on Stats? Stats is a cheap-clone shared handle, so Stats::new(origin).with_foo() style setters would be a footgun: calling one after cloning or deriving tier handles would silently configure only one copy. A separate config value type sidesteps that entirely.
  • #[non_exhaustive] on StatsConfig so new knobs can land without breaking call sites; construct via StatsConfig::new rather than a struct literal. origin lives in the config (it's required, so there's no Default).
  • Internal restructure. The set-once config (prefix, node, interval, retention, enabled) lives in plain Stats fields; only genuinely shared runtime state (origin, entries, task, tick_counter) stays in Arc<StatsShared>. Stats::new destructures the config, normalizing an empty node to None there (so a directly-assigned field is handled too). advertised is derived lazily from prefix + node at task-spawn time; disabled detection is an explicit enabled flag. The snapshot task takes the config it needs by value at spawn.
  • Single call site updated (moq-relay StatsConfig::build), referring to it as moq_net::StatsConfig to avoid colliding with the relay's own clap-derived StatsConfig. Plus all stats tests.
  • Manual patch bumps: moq-net 0.1.4 -> 0.1.5, moq-relay 0.12.1 -> 0.12.2. The stats API is new with no external consumers, so the deliberate break is taken as a patch bump this time rather than the 0.x minor semver-checks would otherwise force. semver_check stays enabled so real violations are still caught.

Reviewer notes

Test plan

  • cargo test -p moq-net stats:: (17 passed)
  • cargo test -p moq-relay stats (config merge tests passed)
  • cargo clippy -p moq-net -p moq-relay --all-targets (clean)
  • cargo check -p moq-net -p moq-relay after version bump (Cargo.lock refreshed)

(Written by Claude)

kixelated and others added 2 commits May 28, 2026 20:48
The #1517 rewrite left Stats::new taking 5 positional args (prefix,
interval, retention, node, origin), two of them path-like, which trips
the "4+ args is a struct waiting to happen" guideline and is easy to
misorder at the call site.

Replace it with Stats::new(origin) plus chained with_prefix /
with_interval / with_retention / with_node setters, each defaulting to
sensible values. To make the by-value `with_*(self) -> Self` setters
safe, the set-once config (prefix, node, interval, retention, enabled)
moves out from behind the Arc into plain Stats fields; only the genuinely
shared runtime state (origin, entries, task, tick_counter) stays in
Arc<StatsShared>. This avoids the Arc::get_mut "already cloned" hazard a
builder would otherwise have, with no separate builder struct.

advertised stops being precomputed in the Arc and is derived lazily from
prefix + node at task-spawn time; disabled detection moves to an explicit
enabled flag. The snapshot task takes the config it needs by value at
spawn (it never re-reads config per tick).

Also mark moq-net and moq-relay semver_check = false in .release-plz.toml:
no external consumers yet, so the deliberate stats API break only warrants
a patch bump rather than the 0.x minor release-plz would otherwise force.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Keep semver_check enabled (we want real violations caught), but take the
deliberate stats API break manually this time: the stats API is new and
has no external consumers, so a patch bump is enough.

- moq-net 0.1.4 -> 0.1.5
- moq-relay 0.12.1 -> 0.12.2

Reverts the .release-plz.toml semver_check = false escape hatch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

Review Change Stack

Walkthrough

This pull request refactors the Stats type from a multi-parameter constructor to a builder-based API. A new public StatsConfig struct provides fluent configuration methods. The Stats struct now stores configuration fields directly (prefix, node, interval, retention, enabled) and maintains an Arc<StatsShared> for runtime state instead of Arc<StatsInner>. The background publisher task lifecycle is rewired to gate on the enabled flag and pass advertised path as a parameter. All tests are updated to use the builder pattern and reference shared state via stats.shared.entries. The moq-relay consumer is adapted to use the new API. Package versions are bumped accordingly.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: refactoring Stats::new to accept a StatsConfig value type instead of five positional arguments.
Description check ✅ Passed The description comprehensively explains the motivation, implementation details, design decisions, and testing performed, directly relating to all changes in the pull request.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/thirsty-sammet-d3c1fc

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.

…ilder

A by-value builder on Stats was a footgun: Stats is a cheap-clone shared
handle, so calling a with_* setter after cloning or deriving tier handles
would silently configure only one copy. Move the knobs into a dedicated
StatsConfig value type instead.

StatsConfig::new(origin) plus with_prefix / with_interval / with_retention
/ with_node, handed to Stats::new(config). The config is #[non_exhaustive]
so new knobs can land without breaking call sites. origin lives in the
config (it's required, so there's no Default). Stats::new destructures the
config, normalizing an empty node to None there so a directly-assigned
field is handled too.

The relay refers to it as moq_net::StatsConfig to avoid colliding with its
own clap-derived StatsConfig.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@kixelated kixelated changed the title moq-net(stats): reshape Stats::new into a by-value builder moq-net(stats): take a StatsConfig value type in Stats::new May 29, 2026
@kixelated kixelated enabled auto-merge (squash) May 29, 2026 04:09
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rs/moq-net/src/stats.rs (1)

855-890: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard against a zero interval so run_publisher can’t panic

tokio::time::interval panics when given a Duration::ZERO period, so ensure the task never constructs a ticker with a zero period.

🛡️ Proposed diff
-	let mut ticker = tokio::time::interval(interval);
+	// `tokio::time::interval` panics on a zero period; fall back to 1s so a
+	// misconfigured `with_interval(Duration::ZERO)` can't kill the task.
+	let period = if interval.is_zero() { Duration::from_secs(1) } else { interval };
+	let mut ticker = tokio::time::interval(period);
🤖 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-net/src/stats.rs` around lines 855 - 890, run_publisher currently
constructs a tokio::time::interval with the supplied interval which will panic
if interval == Duration::ZERO; guard against that by validating/clamping the
incoming interval before creating the ticker (inside run_publisher): check
interval.is_zero() and either return early with a warning via tracing::warn! or
replace it with a small non‑zero fallback (e.g., Duration::from_millis(1)) and
then call tokio::time::interval with the safe value; update the variable used to
create ticker and keep the existing ticker.set_missed_tick_behavior call.
🤖 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.

Outside diff comments:
In `@rs/moq-net/src/stats.rs`:
- Around line 855-890: run_publisher currently constructs a
tokio::time::interval with the supplied interval which will panic if interval ==
Duration::ZERO; guard against that by validating/clamping the incoming interval
before creating the ticker (inside run_publisher): check interval.is_zero() and
either return early with a warning via tracing::warn! or replace it with a small
non‑zero fallback (e.g., Duration::from_millis(1)) and then call
tokio::time::interval with the safe value; update the variable used to create
ticker and keep the existing ticker.set_missed_tick_behavior call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ae6519e3-4a0d-430b-b1ac-d8ebadf5b687

📥 Commits

Reviewing files that changed from the base of the PR and between b7bfc31 and 38e7d5b.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • rs/moq-net/Cargo.toml
  • rs/moq-net/src/stats.rs
  • rs/moq-relay/Cargo.toml
  • rs/moq-relay/src/stats.rs

@kixelated kixelated merged commit ce245f6 into main May 29, 2026
1 check passed
@kixelated kixelated deleted the claude/thirsty-sammet-d3c1fc branch May 29, 2026 04:16
This was referenced May 29, 2026
kixelated added a commit that referenced this pull request May 30, 2026
Reconcile main into dev. Key conflict resolutions:

- conducer crate renamed to kio (main #1547): applied across all of dev's
  newer code; dropped the stale conducer path-dep, kept dev's new flate2 dep.
- moq-mux: kept dev's thiserror Result (#1495); dropped main's CatalogSource
  as dead code since dev's catalog::Consumer already unifies Hang/MSF.
- moq-net: kept dev's OriginConsumer/AnnounceConsumer split (#1434) and the
  TrackConsumer end_at cap; kept dev's non-optional auto-created origins on the
  lite session/publisher (#e770).
- stats: combined main's StatsConfig + liveness retention (#1537, #1548) with
  dev's AnnounceConsumer usage.
- libmoq + moq-native: kept main's auto-reconnect (#1544), terminal-callback
  contract (#1546), and consume_announced (#1552), adapted to dev's
  AnnounceConsumer and OriginProducer connect API. Restored the InitFailed
  error variant and made moq-rtc handle the now-fallible Log::init.

cargo check/clippy/test all pass on the merged workspace.
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