Skip to content

Conversation

@datagutt
Copy link
Member

@datagutt datagutt commented Jan 7, 2026

Summary by CodeRabbit

  • New Features

    • Batch UDP packet processing for improved throughput and reduced system overhead.
    • Connection quality scoring and selection optimization with periodic caching.
    • Enhanced keepalive packets with connection metrics reporting.
  • Bug Fixes

    • Improved congestion control for NAK-less connections with aggressive recovery logic.
    • Better packet acknowledgment and in-flight tracking.
  • Improvements

    • Enhanced registration and probing for connection establishment.
    • Optimized reconnection backoff strategy.
    • Better logging and status reporting across connections.

✏️ Tip: You can customize this high-level summary in your review settings.

datagutt and others added 24 commits January 5, 2026 02:09
**Problem**: `now_ms()` calls `SystemTime::now()` which is a syscall. Currently called 3-5 times per packet in:
- `connection/mod.rs:340` - `register_packet()`
- `packet_handler.rs:318,338` - `forward_via_connection()`
- `connection/mod.rs:375` - `handle_srt_ack()`
- `quality.rs:52` - `calculate_quality_multiplier()`

**Current Code**:
```rust
// Called multiple times per packet
pub fn now_ms() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_else(|_| std::time::Duration::from_millis(0))
        .as_millis() as u64
}
```

**Solution**: Capture timestamp once at packet receive and pass through function chain.

```rust
// In handle_srt_packet, capture once at entry:
let packet_time_ms = now_ms();
let packet_instant = Instant::now();

// Pass to all downstream functions
forward_via_connection(
    sel_idx, pkt, seq, connections,
    last_selected_idx, last_switch_time_ms,
    seq_to_conn, seq_order,
    packet_time_ms,  // Add parameter
).await;

// Update register_packet signature
pub fn register_packet(&mut self, seq: i32, send_time_ms: u64) {
    let idx = self.packet_idx % PKT_LOG_SIZE;
    self.packet_log[idx] = seq;
    self.packet_send_times_ms[idx] = send_time_ms;  // Use passed value
    // ...
}
```

**Files to Modify**:
- `src/utils.rs` - Document hot path usage
- `src/sender/packet_handler.rs` - Capture timestamp at entry
- `src/connection/mod.rs` - Update `register_packet()`, `send_data_with_tracking()`
- `src/sender/selection/quality.rs` - Accept timestamp parameter

**Estimated Impact**: 3-4 fewer syscalls per packet = ~15-20% CPU reduction in hot path
**Problem**: 3 atomic loads per packet.

**Current Code** (`packet_handler.rs:245-253`):
```rust
let enable_quality = toggles.quality_scoring_enabled.load(Ordering::Relaxed);
let enable_explore = toggles.exploration_enabled.load(Ordering::Relaxed);
let classic = toggles.classic_mode.load(Ordering::Relaxed);
```

**Solution**: Create a snapshot struct, load once at start of select iteration.

```rust
#[derive(Clone, Copy)]
pub struct ToggleSnapshot {
    pub classic_mode: bool,
    pub quality_scoring_enabled: bool,
    pub exploration_enabled: bool,
}

impl DynamicToggles {
    #[inline]
    pub fn snapshot(&self) -> ToggleSnapshot {
        ToggleSnapshot {
            classic_mode: self.classic_mode.load(Ordering::Relaxed),
            quality_scoring_enabled: self.quality_scoring_enabled.load(Ordering::Relaxed),
            exploration_enabled: self.exploration_enabled.load(Ordering::Relaxed),
        }
    }
}

// In main select loop:
loop {
    let toggle_snap = toggles.snapshot(); // Once per iteration

    tokio::select! {
        res = local_listener.recv_from(&mut recv_buf) => {
            handle_srt_packet(..., &toggle_snap).await;
        }
        // ...
    }
}
```
…oved performance; try synchronous send first
…on all connections.

Moblin does this with standard 10-byte keepalives; we use extended 38-byte keepalives to provide the receiver with telemetry (window, RTT, NAKs, bitrate).

Extended keepalives should always be sent for best performance; unlike C version which only sends keepalive for idle connections
…story

Previously, connections that never received NAKs (last_nak_time_ms == 0)
were excluded from time-based window recovery. This caused connections
to get stuck at their initial window size (20,000) after reconnection
when they didn't receive enough traffic for ACK-based growth.

This fix treats 'never had NAK' as 'perfect connection' by using
u64::MAX for time_since_last_nak, enabling aggressive recovery (200%
rate) for these healthy connections.

Note: A similar change was made earlier and then reverted in commit
a48aa8b. The reason for that revert
is unclear, but the current implementation causes a feedback loop where
connections can become permanently stuck at low throughput, especially
when combined with receiver-side ACK throttling based on extended
keepalive telemetry.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 7, 2026

Caution

Review failed

The pull request is closed.

Walkthrough

Version bump to 3.0.0 with UDP batch I/O infrastructure (BatchUdpSocket, BatchSender), protocol module refactor into submodules (constants, builders, parsers, types), fixed-size ring buffer sequence tracking replacement, quality caching, ACK/NAK in-flight tracking, and enhanced congestion control for NAK-less scenarios. Added rustc-hash and Unix libc dependencies.

Changes

Cohort / File(s) Summary
Dependencies & Versioning
Cargo.toml
Version bump to 3.0.0; added rustc-hash 2.1 and Unix-specific libc 0.2 dependencies.
Protocol Module Reorganization
src/protocol.rs (removed), src/protocol/mod.rs, src/protocol/constants.rs, src/protocol/builders.rs, src/protocol/parsers.rs, src/protocol/types.rs
Monolithic protocol.rs removed and split into submodules: constants (type identifiers, timeouts, window params), builders (packet constructors), parsers (timestamp/ACK/NAK extraction), types (ConnectionInfo, packet detection helpers). All public APIs re-exported through protocol/mod.rs for backwards compatibility.
UDP Batch I/O Infrastructure
src/connection/batch_recv.rs, src/connection/batch_send.rs
New batch receive module with Linux-optimized BatchUdpSocket using recvmmsg and fallback single-packet implementation; supports up to 32 packets per receive. New batch send module with BatchSender buffering up to 16 packets with 15ms flush timer; integrates sequence tracking for in-flight monitoring.
Sequence Tracking Refactor
src/sender/sequence.rs
Replaced HashMap/VecDeque with fixed-size ring buffer SequenceTracker; zero-allocation O(1) insertion/lookup; configurable SEQ_TRACKING_SIZE and SEQUENCE_TRACKING_MAX_AGE_MS constants; includes collision handling and per-connection removal.
Connection ACK/NAK & In-Flight Tracking
src/connection/ack_nak.rs (new), src/connection/mod.rs, src/connection/packet_io.rs (new)
Added register_packet for in-flight tracking; handle_srt_ack with range-based removal and RTT estimation; handle_nak with congestion delegation; handle_srtla_ack_specific with classic/enhanced mode support. New packet_io module adds drain_incoming and process_packet for batch packet handling and registration event dispatch.
Connection Quality Caching & Batching
src/connection/mod.rs
Added CachedQuality struct for lazy 50ms multiplier recomputation; queue_data_packet and flush_batch for batch sending; highest_acked_seq and last_keepalive_sent tracking; BatchUdpSocket replaces direct UdpSocket; batch_sender integrated per-connection.
Congestion Control Enhancement
src/connection/congestion/enhanced.rs
Window recovery now treats no NAK history (time_since_last_nak == 0) as u64::MAX for aggressive recovery; time-based thresholds (5/7/10 seconds) with proportional increments (WINDOW_INCR/4 to WINDOW_INCR\*2); NAK burst tracking always evaluated; new tests for no-NAK scenarios.
Sender Sequence Tracking Integration
src/sender/mod.rs, src/sender/packet_handler.rs, src/sender/housekeeping.rs
SequenceTracker replaces seq_to_conn/seq_order throughout; added batch_flush_timer (15ms) to event loop; reworked connection status logging to exclude sequence params; housekeeping signature updated to remove sequence cleanup (ring buffer requires no periodic cleanup).
Connection & Packet Handling
src/sender/packet_handler.rs
Added MAX_DRAIN_PACKETS control; queued per-connection batch sends with flush_all_batches; packet_time_ms caching to reduce syscalls; SequenceTracker and ToggleSnapshot replace direct flag/map access; batch flush on connection switch with recovery marking on failure.
Sender Initialization & Management
src/sender/connections.rs (new), src/sender/status.rs (new)
New connections module with PendingConnectionChanges struct, apply_connection_changes for reconciling IP set changes with SequenceTracker cleanup, create_connections_from_ips for async connection establishment. New status module with log_connection_status aggregating comprehensive per-connection metrics (bitrate, window, RTT, in-flight, status) with warnings for inactive connections.
Connection Selection & Quality
src/sender/selection/mod.rs, src/sender/selection/classic.rs, src/sender/selection/enhanced.rs, src/sender/selection/quality.rs
select_connection_idx now takes mutable slice and enable_quality/enable_explore flags; classic and enhanced modes added inline attribute; enhanced mode uses get_cached_quality_multiplier with per-connection in-place updates; quality.rs refactored to accept current_time_ms parameter with uncached calculation and RTT bonus helper.
Toggle Snapshot Optimization
src/toggles.rs
New ToggleSnapshot struct caching classic_mode, quality_scoring_enabled, exploration_enabled flags; DynamicToggles::snapshot() method reduces per-packet atomic loads to single snapshot per selection iteration.
Uplink Reader Batching
src/sender/uplink.rs
Reader now uses BatchUdpSocket with recv_batch instead of single recv_from; processes RecvMmsgBuffer in single iteration; error handling sends empty UplinkPacket and applies 100ms backoff instead of logging and breaking.
Reconnection Backoff
src/connection/reconnection.rs
Added backoff_delay() method computing saturation-based delay with BASE_RECONNECT_DELAY_MS and MAX_BACKOFF_DELAY_MS; consolidated backoff calculations in should_attempt_reconnect and record_attempt logging.
Test Infrastructure Updates
src/test_helpers.rs, src/tests/connection_tests.rs, src/tests/sender_tests.rs
Test helpers updated to create BatchUdpSocket-wrapped sockets via socket2; SrtlaConnection now initialized with new fields (highest_acked_seq, batch_sender, quality_cache, last_keepalive_sent); connection tests updated for register_packet(seq, timestamp) signature and HashMap-based packet_log; sender tests adapt to SequenceTracker API and mutable connection slices.
Configuration & Metadata
.gitignore, CLAUDE.md (new)
Reintroduced moblin/ ignore entry and added /.claude path; created empty CLAUDE.md placeholder.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐰 Whiskers twitch with batching glee,
Rings of sequences wild and free,
Packets flush in stacks so neat,
Quality cache makes selection sweet,
V3 hops toward the finish line! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'Refactor/optimizations' is too vague and generic, providing no meaningful information about the specific changes in this substantial refactor. Use a more descriptive title that highlights the primary change, such as 'Implement batch UDP I/O and sequence tracking refactor' or 'Add batching infrastructure and ring buffer-based sequence tracking'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/optimizations

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f84513 and fd678c7.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • Cargo.toml
  • src/connection/ack_nak.rs
  • src/sender/connections.rs

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
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.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In @Cargo.toml:
- Around line 22-24: The cross-platform crates (tokio, tracing,
tracing-subscriber, socket2, bytes, chrono, smallvec, mimalloc) are incorrectly
placed under [target.'cfg(unix)'.dependencies]; move each of these entries out
of that target-specific table into the main [dependencies] table so they are
available on all platforms, keeping their versions and feature lists intact, and
leave only truly Unix-only crates (e.g., libc) under
[target.'cfg(unix)'.dependencies].

In @src/connection/batch_recv.rs:
- Around line 21-22: The module `unix_impl` is incorrectly enabled for all Unix
targets but uses Linux-only syscalls (`recvmmsg`, `mmsghdr`); change its cfg
from `#[cfg(unix)]` to a Linux-only guard such as `#[cfg(target_os = "linux")]`,
and update any related re-exports (the items re-exporting from `unix_impl`) to
the same Linux-specific cfg so macOS builds won't reference Linux-only symbols;
locate the `mod unix_impl { ... }` declaration and corresponding `pub use` or
`cfg` lines and replace their `unix` cfg with `target_os = "linux"`.
- Around line 259-264: The Default impl for RecvMmsgBuffer uses unsafe
std::mem::zeroed() leaving internal iov/mmsghdr pointers null and producing an
unusable buffer; fix by removing the Default impl entirely or by making Default
produce a properly-initialized instance instead of zeroed memory. Concretely:
either delete the impl Default for RecvMmsgBuffer block, or add/adjust an
initializer so Default::default() delegates to a safe constructor (e.g., change
RecvMmsgBuffer::new() to return Self or add RecvMmsgBuffer::new_unboxed() that
returns Self) and have Default call that initializer rather than using zeroed().
- Around line 326-340: The non-Unix fallback_impl module references
tokio::net::UdpSocket (used in struct BatchUdpSocket) but tokio is only declared
under the unix-target deps, so the crate won't compile on Windows; either move
tokio to the top-level [dependencies] in Cargo.toml so tokio::net::UdpSocket is
available for the fallback_impl, or rewrite the fallback_impl/BatchUdpSocket to
avoid tokio (e.g., use std::net::UdpSocket with a blocking-to-async bridge or a
Windows-specific async API) and adjust any usages of MTU/BatchUdpSocket to match
the new API.
- Around line 46-48: Remove the manual unsafe impl blocks for Send and Sync on
BatchUdpSocket: delete the two lines `unsafe impl Send for BatchUdpSocket {}`
and `unsafe impl Sync for BatchUdpSocket {}` since AsyncFd<Socket> already
implements Send + Sync when socket2::Socket is Send + Sync; no other changes to
BatchUdpSocket or its usages are needed.

In @src/connection/batch_send.rs:
- Around line 88-123: The flush method can fail mid-loop and return before
cleaning up, causing duplicates and sequence mismatches; modify flush to track
how many packets were successfully sent (e.g., a sent_count incremented inside
the loop over self.queue) and if socket.send(...) returns an Err, remove the
first sent_count items from self.queue and the corresponding entries from
self.sequences and self.queue_times (or clear all three consistently), update
self.last_flush_time, then return the error; reference symbols: flush,
socket.send, self.queue, self.sequences, self.queue_times, last_flush_time,
packet_count.
🧹 Nitpick comments (4)
docs/PERFORMANCE_OPTIMIZATION_PLAN.md (1)

497-501: Minor version inconsistency with Cargo.toml.

The documentation shows rustc-hash = "2.0" but Cargo.toml uses version "2.1". Consider updating the documentation to match the actual dependency version for consistency.

📝 Suggested update
 # Cargo.toml
 [dependencies]
-rustc-hash = "2.0"  # Fast non-cryptographic hash
+rustc-hash = "2.1"  # Fast non-cryptographic hash
src/sender/selection/enhanced.rs (1)

42-50: Consider using #[inline] instead of #[inline(always)] for this function.

This function is ~100 lines with multiple branches and conditionals. #[inline(always)] forces inlining regardless of size, which can increase binary size and potentially hurt instruction cache performance. Regular #[inline] lets the compiler make the decision based on call site context.

♻️ Suggested change
-#[inline(always)]
+#[inline]
 pub fn select_connection(
src/test_helpers.rs (1)

19-22: Conn ID counters are fragmented across functions, risking ID collisions in tests.

NEXT_TEST_CONN_ID is defined as a static inside each function separately. On a single platform, calling both create_test_connection() and create_test_connections() will generate overlapping IDs (both starting from 1000). If any tests depend on unique conn_id values, this could cause flakiness.

♻️ Suggested fix: Hoist the counter to module level
+use std::sync::atomic::{AtomicU64, Ordering};
+
+/// Shared test connection ID counter for all helper functions
+static NEXT_TEST_CONN_ID: AtomicU64 = AtomicU64::new(1000);
+
 #[cfg(unix)]
 pub async fn create_test_connection() -> SrtlaConnection {
-    use std::sync::atomic::{AtomicU64, Ordering};
-    static NEXT_TEST_CONN_ID: AtomicU64 = AtomicU64::new(1000);
-
     use socket2::{Domain, Protocol, Socket, Type};
     // ... rest unchanged

Apply similar removal to the other three function definitions.

Also applies to: 62-63, 99-100, 148-149

src/connection/mod.rs (1)

717-778: Code duplication between Unix and non-Unix reconnect methods.

The Unix (lines 717-746) and non-Unix (lines 748-778) reconnect methods are nearly identical except for socket creation. This duplication could be reduced by extracting common reset logic.

♻️ Optional: Extract common reconnect logic
fn reset_connection_state(&mut self) {
    self.connected = false;
    self.last_received = None;
    self.window = WINDOW_DEF * WINDOW_MULT;
    self.in_flight_packets = 0;
    self.packet_log.clear();
    self.highest_acked_seq = i32::MIN;
    self.congestion.reset();
    self.rtt.reset();
    self.bitrate.reset();
    self.batch_sender.reset();
    self.reconnection.last_reconnect_attempt_ms = now_ms();
    self.reconnection.reconnect_failure_count = 0;
    self.mark_reconnect_success();
    self.reconnection.reset_startup_grace();
}

Then both reconnect variants would just do socket creation + reset_connection_state().

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ee7682 and a35a7c1.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (19)
  • Cargo.toml
  • docs/PERFORMANCE_OPTIMIZATION_PLAN.md
  • src/connection/batch_recv.rs
  • src/connection/batch_send.rs
  • src/connection/congestion/enhanced.rs
  • src/connection/mod.rs
  • src/sender/housekeeping.rs
  • src/sender/mod.rs
  • src/sender/packet_handler.rs
  • src/sender/selection/classic.rs
  • src/sender/selection/enhanced.rs
  • src/sender/selection/mod.rs
  • src/sender/selection/quality.rs
  • src/sender/sequence.rs
  • src/sender/uplink.rs
  • src/test_helpers.rs
  • src/tests/connection_tests.rs
  • src/tests/sender_tests.rs
  • src/toggles.rs
🧰 Additional context used
📓 Path-based instructions (4)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: All Rust code must pass cargo fmt --all -- --check
All Rust code must pass clippy with -D warnings (no warnings allowed)
Use anyhow::Result for error propagation
Group imports as std → external → crate with module-level granularity (imports_granularity = "Module", group_imports = "StdExternalCrate")
Follow naming conventions: constants SCREAMING_SNAKE_CASE
Follow naming conventions: structs PascalCase
Follow naming conventions: functions snake_case
Follow naming conventions: modules snake_case (file/module names)
Use tracing macros (debug!, info!, warn!, etc.) for logging
Use Tokio for async runtime (net, time, io, signal) and async operations
Keep code self-documenting; add doc comments for public API when necessary

Files:

  • src/sender/selection/classic.rs
  • src/sender/selection/mod.rs
  • src/toggles.rs
  • src/connection/batch_send.rs
  • src/connection/congestion/enhanced.rs
  • src/test_helpers.rs
  • src/sender/uplink.rs
  • src/sender/selection/enhanced.rs
  • src/sender/mod.rs
  • src/sender/housekeeping.rs
  • src/sender/sequence.rs
  • src/sender/selection/quality.rs
  • src/tests/connection_tests.rs
  • src/sender/packet_handler.rs
  • src/connection/batch_recv.rs
  • src/tests/sender_tests.rs
  • src/connection/mod.rs
src/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Expose internal fields for testing only behind #[cfg(feature = "test-internals")]

Files:

  • src/sender/selection/classic.rs
  • src/sender/selection/mod.rs
  • src/toggles.rs
  • src/connection/batch_send.rs
  • src/connection/congestion/enhanced.rs
  • src/test_helpers.rs
  • src/sender/uplink.rs
  • src/sender/selection/enhanced.rs
  • src/sender/mod.rs
  • src/sender/housekeeping.rs
  • src/sender/sequence.rs
  • src/sender/selection/quality.rs
  • src/tests/connection_tests.rs
  • src/sender/packet_handler.rs
  • src/connection/batch_recv.rs
  • src/tests/sender_tests.rs
  • src/connection/mod.rs
src/tests/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Place integration, end-to-end, and protocol tests under src/tests/

Files:

  • src/tests/connection_tests.rs
  • src/tests/sender_tests.rs
Cargo.toml

📄 CodeRabbit inference engine (AGENTS.md)

Cargo.toml: Set Rust edition to 2024 and target MSRV 1.87 (requires nightly toolchain)
Maintain build profiles: dev, release-debug (thin LTO), release-lto (full LTO, stripped)

Files:

  • Cargo.toml
🧠 Learnings (8)
📚 Learning: 2025-10-15T14:24:40.523Z
Learnt from: CR
Repo: irlserver/srtla_send PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-15T14:24:40.523Z
Learning: Applies to Cargo.toml : Set Rust edition to 2024 and target MSRV 1.87 (requires nightly toolchain)

Applied to files:

  • Cargo.toml
📚 Learning: 2025-10-15T14:24:40.523Z
Learnt from: CR
Repo: irlserver/srtla_send PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-15T14:24:40.523Z
Learning: Applies to **/*.rs : Use Tokio for async runtime (net, time, io, signal) and async operations

Applied to files:

  • Cargo.toml
  • src/connection/mod.rs
📚 Learning: 2025-10-15T14:24:40.523Z
Learnt from: CR
Repo: irlserver/srtla_send PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-15T14:24:40.523Z
Learning: Use Rust nightly toolchain for development and CI

Applied to files:

  • Cargo.toml
📚 Learning: 2025-10-15T14:24:40.523Z
Learnt from: CR
Repo: irlserver/srtla_send PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-15T14:24:40.523Z
Learning: Applies to rustfmt.toml : Use the project's rustfmt configuration with unstable features enabled

Applied to files:

  • Cargo.toml
📚 Learning: 2025-10-15T14:24:40.523Z
Learnt from: CR
Repo: irlserver/srtla_send PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-15T14:24:40.523Z
Learning: Applies to **/*.rs : All Rust code must pass clippy with -D warnings (no warnings allowed)

Applied to files:

  • Cargo.toml
📚 Learning: 2025-10-15T14:24:40.523Z
Learnt from: CR
Repo: irlserver/srtla_send PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-15T14:24:40.523Z
Learning: Applies to **/*.rs : Use anyhow::Result for error propagation

Applied to files:

  • src/sender/packet_handler.rs
📚 Learning: 2025-10-15T14:24:40.523Z
Learnt from: CR
Repo: irlserver/srtla_send PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-15T14:24:40.523Z
Learning: Applies to src/**/*.rs : Expose internal fields for testing only behind #[cfg(feature = "test-internals")]

Applied to files:

  • src/tests/sender_tests.rs
  • src/connection/mod.rs
📚 Learning: 2025-10-15T14:24:40.523Z
Learnt from: CR
Repo: irlserver/srtla_send PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-15T14:24:40.523Z
Learning: Applies to src/tests/**/*.rs : Place integration, end-to-end, and protocol tests under src/tests/

Applied to files:

  • src/tests/sender_tests.rs
🧬 Code graph analysis (7)
src/connection/batch_send.rs (1)
src/connection/mod.rs (1)
  • has_queued_packets (239-241)
src/connection/congestion/enhanced.rs (2)
src/connection/congestion/mod.rs (1)
  • perform_window_recovery (147-158)
src/utils.rs (1)
  • now_ms (15-20)
src/test_helpers.rs (5)
src/sender/mod.rs (1)
  • tokio (93-93)
src/utils.rs (1)
  • now_ms (15-20)
src/connection/batch_send.rs (2)
  • new (50-57)
  • default (43-45)
src/registration.rs (2)
  • new (53-71)
  • default (47-49)
src/connection/mod.rs (1)
  • default (49-54)
src/sender/selection/enhanced.rs (2)
src/connection/mod.rs (3)
  • total_nak_count (666-668)
  • time_since_last_nak_ms (662-664)
  • nak_burst_count (670-672)
src/connection/congestion/mod.rs (1)
  • time_since_last_nak_ms (161-167)
src/tests/connection_tests.rs (3)
src/utils.rs (1)
  • now_ms (15-20)
src/connection/mod.rs (3)
  • needs_keepalive (594-606)
  • nak_burst_count (670-672)
  • time_since_last_nak_ms (662-664)
src/test_helpers.rs (2)
  • create_test_connection (19-58)
  • create_test_connection (61-95)
src/sender/packet_handler.rs (2)
src/utils.rs (1)
  • now_ms (15-20)
src/sender/selection/mod.rs (1)
  • select_connection_idx (49-72)
src/tests/sender_tests.rs (4)
src/sender/selection/mod.rs (1)
  • select_connection_idx (49-72)
src/sender/sequence.rs (2)
  • new (65-70)
  • get (99-108)
src/utils.rs (1)
  • now_ms (15-20)
src/sender/selection/quality.rs (1)
  • calculate_quality_multiplier (60-62)
🪛 GitHub Check: Test (macOS)
src/connection/batch_recv.rs

[failure] 220-220:
cannot find function recvmmsg in crate libc


[failure] 187-187:
cannot find struct, variant or union type mmsghdr in crate libc


[failure] 151-151:
cannot find type mmsghdr in crate libc

🪛 GitHub Check: Test (Windows)
src/sender/housekeeping.rs

[failure] 6-6:
unresolved import tracing


[failure] 5-5:
failed to resolve: use of unresolved module or unlinked crate tokio


[failure] 4-4:
failed to resolve: use of unresolved module or unlinked crate tokio

src/connection/batch_recv.rs

[failure] 331-331:
failed to resolve: use of unresolved module or unlinked crate tokio

🪛 LanguageTool
docs/PERFORMANCE_OPTIMIZATION_PLAN.md

[uncategorized] ~54-~54: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...- Medium impact, good ROI - Minor - Low impact but good practice - Architectural -...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[grammar] ~287-~287: Ensure spelling is correct
Context: ...ion, recalculate periodically (every 50-100ms). ```rust pub struct CachedQuality { ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test
🔇 Additional comments (47)
src/sender/selection/classic.rs (1)

22-23: LGTM!

The #[inline(always)] hint is appropriate for this hot-path selection function. The function is compact and called frequently during packet processing, making it a good candidate for forced inlining.

docs/PERFORMANCE_OPTIMIZATION_PLAN.md (1)

1-7: Well-structured optimization plan.

This documentation provides excellent guidance for the performance improvements, with clear prioritization, impact estimates, and code sketches. The phased approach and measurement plan will help track progress effectively.

src/connection/congestion/enhanced.rs (3)

85-98: Good fix for no-NAK connection recovery.

Using u64::MAX as a sentinel for connections without NAK history elegantly ensures they receive aggressive recovery. The comment clearly explains the rationale—connections shouldn't get stuck at low windows just because they've been healthy.


132-138: Verify integer division ordering for slow/minimal recovery rates.

The calculations WINDOW_INCR * fast_mode_bonus / 2 and WINDOW_INCR * fast_mode_bonus / 4 perform multiplication before division, which is correct. However, when fast_mode_bonus = 1:

  • Line 134: 30 * 1 / 2 = 15
  • Line 137: 30 * 1 / 4 = 7

The ordering is correct and avoids truncation issues. The implementation looks good.


229-264: Good test coverage for no-NAK history scenario.

The test validates that connections without NAK history receive aggressive recovery (200% rate = WINDOW_INCR * 2), preventing the stuck-window issue described in the comments.

src/toggles.rs (2)

11-18: Well-designed snapshot struct for hot-path optimization.

The ToggleSnapshot struct with Copy trait enables zero-cost passing through the packet processing pipeline. Good documentation explains the purpose.


49-60: LGTM!

The snapshot() method correctly uses Ordering::Relaxed since these toggles are independent configuration flags without cross-field synchronization requirements. The #[inline] hint is appropriate for this hot-path accessor.

src/sender/uplink.rs (2)

36-59: Clean batch receive implementation.

The batch receive pattern correctly:

  • Allocates the buffer once outside the loop
  • Processes all received packets before the next batch
  • Handles empty data defensively
  • Properly exits on channel closure

Good optimization that reduces syscall overhead on Unix platforms.


64-80: Appropriate error backoff strategy.

The 100ms sleep on receive errors prevents CPU-intensive tight loops while allowing quick recovery. The comment clearly explains the rationale for this specific duration.

src/tests/connection_tests.rs (3)

35-46: Tests correctly updated for new register_packet signature.

The tests properly capture current_time = now_ms() and pass it to register_packet. The HashMap-based packet_log verification using contains_key and get aligns with the new data structure.


356-366: Keepalive test correctly uses last_keepalive_sent.

The test properly exercises the new last_keepalive_sent field for keepalive timing logic, matching the updated needs_keepalive() implementation.


547-567: Good coverage for HashMap-based packet log behavior.

This test verifies that the HashMap grows dynamically beyond PKT_LOG_SIZE (unlike the previous fixed-size array) and that cumulative ACKs correctly remove acknowledged packets from the log.

src/sender/selection/mod.rs (1)

48-72: LGTM! Clean API evolution with appropriate inlining.

The signature change to accept &mut [SrtlaConnection] enables in-place quality cache updates in enhanced mode. The #[inline(always)] is appropriate for this thin dispatch function, and the boolean flags provide clear mode control.

src/sender/selection/enhanced.rs (1)

155-178: Good use of #[cold] and #[inline(never)] to optimize the hot path.

Moving debug logging to a separate cold function is an effective optimization. The compiler will place this code in a separate section, keeping the hot path compact.

src/connection/batch_send.rs (1)

1-11: Good documentation with clear performance expectations.

The module-level documentation clearly explains the batching strategy, references the Moblin implementation, and provides concrete performance numbers. This helps future maintainers understand the design rationale.

src/sender/housekeeping.rs (1)

137-138: Good documentation of the design change.

The comment clearly explains why periodic cleanup is no longer needed with the ring buffer approach. This helps future maintainers understand the architectural decision.

src/sender/sequence.rs (2)

76-90: Well-designed O(1) insertion with appropriate documentation.

The bitwise mask for modulo is correct since SEQ_TRACKING_SIZE is validated as power-of-two in tests. The count field tracking is appropriately documented as "approximate, for logging only" since it only tracks insertions into empty slots, not overwrites.


45-55: Good design: heap-allocated array avoids stack overflow.

Using Box<[SequenceTrackingEntry; SEQ_TRACKING_SIZE]> (~320KB) on the heap instead of a stack array is the correct choice. The #[allow(clippy::len_without_is_empty)] is appropriate since an "empty" concept doesn't apply to this fixed-size ring buffer.

src/sender/selection/quality.rs (2)

59-62: Clean API design with explicit timestamp parameter.

Accepting current_time_ms as a parameter rather than calling now_ms() internally is a good performance optimization. When processing multiple connections in a loop, the caller can obtain the timestamp once and reuse it, avoiding repeated system calls.


115-139: Well-documented RTT bonus calculation.

The inline documentation with concrete RTT examples (50ms, 100ms, 200ms, 400ms) makes the behavior clear. The two-stage clamping (line 135 caps the bonus, line 138 ensures no penalty) is defensive and correct.

src/tests/sender_tests.rs (6)

1-14: LGTM - Imports and test setup are correct.

The imports correctly bring in the new SequenceTracker via the crate::sender::* glob import, and the test helpers are properly used for connection creation.


16-28: LGTM - Classic mode test correctly uses updated API.

The test properly validates that classic mode selects the connection with highest score (lowest in-flight packets).


30-240: LGTM - Connection selection and dampening tests are comprehensive.

The tests properly exercise:

  • Quality scoring with NAK penalties (lines 30-52)
  • Burst NAK penalties (lines 54-75)
  • Time-based switch dampening within cooldown (lines 77-106)
  • Dampening bypass after cooldown (lines 108-137)
  • Immediate switch when current connection is invalid (lines 139-173)
  • Exploration blocking during cooldown (lines 175-206)
  • Classic mode ignoring dampening (lines 208-240)

242-279: LGTM - NAK attribution test validates correct connection tracking.

The test properly verifies that NAKs increment the count only for the connection that originally sent the packet.


316-354: LGTM - Sequence tracking tests validate ring buffer behavior.

The tests correctly validate:

  • Cleanup of sequence entries when connections are removed (lines 316-354)
  • Ring buffer collision behavior where newer sequences overwrite older ones at the same index (lines 396-417)

Also applies to: 396-417


461-533: LGTM - Quality multiplier test is comprehensive.

The test thoroughly validates the exponential decay formula at multiple time points and covers edge cases like no-NAK bonus (1.1x) and burst NAK penalties (0.7x additional multiplier).

src/sender/mod.rs (5)

114-122: LGTM - Batch flush timer configuration is appropriate.

The 15ms interval with MissedTickBehavior::Skip is a good choice - it allows packet batching while maintaining low latency, and skipping missed ticks prevents burst processing under load.


162-174: LGTM - Toggle snapshot caching is a good optimization.

Creating the snapshot once per select! iteration rather than per-packet reduces atomic load overhead significantly on the hot path.


434-489: LGTM - Connection changes properly clean up sequence tracker.

The function correctly:

  1. Collects connection IDs before removal
  2. Removes stale connections from the vector
  3. Cleans up corresponding sequence tracker entries
  4. Resets selection state when connections change

524-527: LGTM - Early exit optimization for logging.

The tracing::enabled! check prevents all computation when INFO logging is disabled, which is excellent for production environments where verbose logging may be turned off.


299-415: LGTM - Non-Unix loop is consistent with Unix loop.

The non-Unix implementation correctly mirrors the Unix loop's handling of packet reception, uplink events, housekeeping, and batch flushing, with SIGHUP handling appropriately omitted.

src/sender/packet_handler.rs (6)

10-15: LGTM - Imports correctly reference new types.

The imports for SequenceTracker and ToggleSnapshot are appropriate for the refactored packet handling.


70-90: LGTM - NAK handling with O(1) sequence lookup.

The NAK processing correctly:

  1. Captures time once for batch processing
  2. Uses O(1) ring buffer lookup for sequence → connection mapping
  3. Falls back to linear connection scan if entry expired (edge case handling)

The O(n) find by conn_id is acceptable since connections are typically few (3-4).


152-190: LGTM - Bounded packet queue draining.

The MAX_DRAIN_PACKETS = 64 limit effectively prevents CPU spikes from large accumulated queues while ensuring remaining packets are processed on subsequent event loop iterations.


313-324: LGTM - Batch flush on connection switch ensures ordering.

Flushing the previous connection's queued packets before switching is critical for maintaining packet ordering across connections. The error handling with warn! is appropriate.


346-350: LGTM - Sequence tracking at queue time is correct.

Tracking packets immediately when queued (not when flushed) ensures accurate NAK attribution even if NAKs arrive during the batch interval.


365-388: LGTM - Two-pass flush optimization.

The two-pass design is intentional and correct:

  1. First pass (read-only) enables early exit when no work is needed
  2. Second pass (mutable) performs actual flushes

This avoids acquiring mutable references when traffic is idle, which is the common case on the 15ms timer.

src/connection/batch_recv.rs (3)

212-236: LGTM - recvmmsg syscall implementation.

The implementation correctly:

  • Uses MSG_DONTWAIT for non-blocking operation
  • Returns std::io::Error::last_os_error() on failure
  • Stores the packet count for iterator access

295-319: LGTM - Socket address conversion is correct.

The function properly handles both IPv4 and IPv6 address families using standard pointer casts to interpret the kernel-filled sockaddr_storage.


326-454: LGTM - Non-Unix fallback provides compatible API.

The fallback implementation correctly provides the same API surface with single-packet receives, which is the expected behavior on platforms without recvmmsg.

src/connection/mod.rs (7)

1-2: LGTM - Module structure and re-exports.

The new batch modules are properly declared and their public types are re-exported for use by other parts of the codebase.

Also applies to: 15-16


35-55: LGTM - Quality multiplier caching.

Caching the quality multiplier with 50ms granularity reduces expensive exp() calculations from every packet to ~20 times per second, which is a significant optimization on the hot path.


86-97: LGTM - FxHashMap for packet log with highest_acked_seq optimization.

The combination of:

  1. FxHashMap<i32, u64> for O(1) insert/remove instead of O(256) array scan
  2. highest_acked_seq to skip redundant ACK processing

...significantly improves performance for packet tracking operations.


474-512: LGTM - Optimized cumulative ACK handling.

The implementation cleverly:

  1. Skips duplicate/out-of-order ACKs via highest_acked_seq
  2. Uses targeted removal for small ranges (O(k) where k ≤ 64)
  3. Falls back to retain() for large gaps (e.g., after reconnect)

This significantly reduces work for the common case of sequential ACKs.


410-421: LGTM - ACK fast path optimization.

Trying synchronous try_send_to before falling back to the channel avoids unnecessary task context switches for the common case where the socket is ready.


246-264: LGTM - Batch flush with packet registration.

The implementation correctly:

  1. Early-exits when no packets are queued
  2. Flushes via batch sender
  3. Registers all sent packets for in-flight tracking
  4. Updates last_sent timestamp

133-168: LGTM - Connection initialization with batching infrastructure.

The Unix connect_from_ip properly initializes:

  • BatchUdpSocket via socket2::Socket
  • FxHashMap with pre-allocated capacity
  • highest_acked_seq to i32::MIN
  • batch_sender and quality_cache

Copy link
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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @src/connection/batch_recv.rs:
- Line 318: Remove the unused std::sync::Arc import from the fallback_impl
module: locate the import statement referencing Arc in the top of the
fallback_impl module (the `use std::sync::Arc;` line) and delete it so the
module no longer contains the unused Arc import.
🧹 Nitpick comments (1)
src/connection/batch_recv.rs (1)

154-157: Enhance the safety documentation for unsafe impl Send.

The unsafe impl Send is necessary because libc::iovec and libc::mmsghdr contain raw pointers that don't automatically implement Send. However, the current comment could be more explicit about why this is safe.

📝 Suggested documentation improvement
-    // Safety: All fields are either Copy types or raw pointers that point
-    // to data within this struct. The struct is self-contained.
+    // Safety: This struct contains raw pointers in `iov` and `mmsghdr` fields,
+    // which don't auto-implement Send. However, these pointers are self-referential
+    // (they only point to data within `addr_storage` and `buffers` in the same struct).
+    // Since the struct is always heap-allocated (Box) and the pointers are internal,
+    // it's safe to move across thread boundaries. The struct is self-contained and
+    // moving it doesn't invalidate the internal pointer relationships.
     unsafe impl Send for RecvMmsgBuffer {}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a35a7c1 and 0b00369.

📒 Files selected for processing (3)
  • Cargo.toml
  • src/connection/batch_recv.rs
  • src/connection/batch_send.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • Cargo.toml
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: All Rust code must pass cargo fmt --all -- --check
All Rust code must pass clippy with -D warnings (no warnings allowed)
Use anyhow::Result for error propagation
Group imports as std → external → crate with module-level granularity (imports_granularity = "Module", group_imports = "StdExternalCrate")
Follow naming conventions: constants SCREAMING_SNAKE_CASE
Follow naming conventions: structs PascalCase
Follow naming conventions: functions snake_case
Follow naming conventions: modules snake_case (file/module names)
Use tracing macros (debug!, info!, warn!, etc.) for logging
Use Tokio for async runtime (net, time, io, signal) and async operations
Keep code self-documenting; add doc comments for public API when necessary

Files:

  • src/connection/batch_send.rs
  • src/connection/batch_recv.rs
src/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Expose internal fields for testing only behind #[cfg(feature = "test-internals")]

Files:

  • src/connection/batch_send.rs
  • src/connection/batch_recv.rs
🧠 Learnings (1)
📚 Learning: 2025-10-15T14:24:40.523Z
Learnt from: CR
Repo: irlserver/srtla_send PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-15T14:24:40.523Z
Learning: Applies to **/*.rs : Use Tokio for async runtime (net, time, io, signal) and async operations

Applied to files:

  • src/connection/batch_recv.rs
🧬 Code graph analysis (2)
src/connection/batch_send.rs (1)
src/connection/mod.rs (1)
  • has_queued_packets (239-241)
src/connection/batch_recv.rs (3)
src/sender/mod.rs (1)
  • tokio (93-93)
src/connection/batch_send.rs (2)
  • new (50-57)
  • default (43-45)
src/connection/mod.rs (1)
  • default (49-54)
🪛 GitHub Actions: CI
src/connection/batch_recv.rs

[error] 318-318: unused import: std::sync::Arc

🪛 GitHub Check: Test (macOS)
src/connection/batch_recv.rs

[failure] 318-318:
unused import: std::sync::Arc

🪛 GitHub Check: Test (Windows)
src/connection/batch_recv.rs

[failure] 318-318:
unused import: std::sync::Arc

🔇 Additional comments (13)
src/connection/batch_recv.rs (7)

1-16: LGTM!

The module documentation clearly explains the batch receive optimization and fallback strategy, and the batch size constant (32) is well-chosen for balancing syscall reduction with memory usage.


42-138: LGTM!

The BatchUdpSocket implementation correctly wraps socket2::Socket in AsyncFd for async readiness polling. The poll_recv_batch, recv_batch, and send methods properly handle WouldBlock errors and follow tokio's async patterns. The unsafe transmute in try_recv (line 123) is sound for the socket2 API.


256-308: LGTM!

The iterator implementation correctly traverses the received packets, and the sockaddr_storage_to_socket_addr converter properly handles both IPv4 and IPv6 addresses with correct byte-order conversions. The unsafe code is standard FFI pattern for working with libc socket structures.


416-425: LGTM!

The Default implementation for the fallback RecvMmsgBuffer correctly initializes all fields with safe values. This addresses the issue from an earlier version that used unsafe { std::mem::zeroed() } with uninitialized pointers.


445-449: LGTM!

The conditional re-exports correctly use target_os = "linux" instead of the broader unix cfg, properly addressing the platform compatibility issue from earlier reviews where macOS builds would incorrectly reference Linux-only syscalls.


451-471: LGTM!

The tests appropriately validate buffer initialization and size constraints. The Linux-specific buffer size test ensures the batch buffer stays within reasonable memory bounds.


320-320: Tokio is unavailable on non-Unix platforms, causing Windows build failures.

According to the pipeline failures and past review comments, tokio is declared only under [target.'cfg(unix)'.dependencies] in Cargo.toml, making it unavailable on Windows. However, the non-Linux fallback (line 320) imports and uses tokio::net::UdpSocket, which causes compilation failures on Windows as confirmed by the Test (Windows) pipeline failure.

The past review comment suggested two solutions:

  1. Move tokio to the main [dependencies] section to make it available on all platforms, or
  2. Implement the Windows fallback without tokio using a platform-specific approach

Since this is a duplicate of an existing unfixed issue, please prioritize addressing this to unblock Windows builds.

⛔ Skipped due to learnings
Learnt from: CR
Repo: irlserver/srtla_send PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-15T14:24:40.523Z
Learning: Applies to **/*.rs : Use Tokio for async runtime (net, time, io, signal) and async operations
src/connection/batch_send.rs (6)

1-19: LGTM!

The module documentation clearly explains the batching optimization with concrete performance metrics (15x syscall reduction), and all imports are properly utilized throughout the code.


20-40: LGTM!

The constants align with the documented Moblin strategy, and the parallel vector design efficiently tracks per-packet metadata. The use of SmallVec<u8, 1500> is well-chosen to avoid heap allocations for typical MTU-sized packets.


42-82: LGTM!

The initialization and queueing methods are well-implemented. Pre-allocation in new() avoids repeated reallocations, and queue_packet efficiently uses SmallVec::from_slice_copy. The needs_time_flush logic correctly combines both emptiness and timing checks.


88-133: LGTM! Partial send failure handling is now correct.

The flush method now properly handles partial send failures (lines 97-112). The sent_count tracker ensures that if socket.send() fails mid-batch, only the successfully sent packets are removed from the queue via drain() (lines 106-108), while unsent packets remain for the next retry. This correctly addresses the issue from the previous review where partial failures could cause duplicate sends and sequence tracking inconsistencies.

The decision not to update last_flush_time on partial failure (line 126 only updates on success) is intentional and correct—it allows needs_time_flush() to trigger retry attempts soon.


135-141: LGTM!

The reset method correctly clears all internal state and resets the flush timer, which is appropriate for reconnection scenarios.


144-189: LGTM!

The tests comprehensively validate the key batching behaviors: threshold-based flushing, time-based flushing, and state reset. All test logic is correct and covers the essential functionality.

Copy link
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.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/connection/mod.rs (1)

467-511: LGTM! Excellent cumulative ACK optimization.

The handle_srt_ack implementation is well-optimized:

  • Tracks highest_acked_seq to skip redundant work on duplicate/old ACKs
  • Safe math: i64 cast prevents overflow in range calculation
  • Adaptive strategy: small ranges use targeted removal (O(k)), large gaps use retain (O(n))
  • Initial state (i32::MIN) correctly handled by taking retain path
  • RTT calculation preserved with proper timing capture

The optimization significantly reduces work when ACKs arrive in order.

💡 Optional: Document the magic number 64

Consider adding a comment explaining why 64 is the threshold for switching strategies:

 // For small ranges, use targeted removal (O(k) where k = range size)
 // For large gaps (e.g., after reconnect), fall back to retain (O(n))
 let range_size = (ack as i64 - old_highest as i64).unsigned_abs();
+// Threshold of 64 balances iteration cost vs retain() overhead on typical packet logs
 if range_size <= 64 && old_highest != i32::MIN {

This helps future maintainers understand the performance trade-off.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ad4fa6 and 76b970f.

📒 Files selected for processing (3)
  • CLAUDE.md
  • src/connection/mod.rs
  • src/test_helpers.rs
✅ Files skipped from review due to trivial changes (1)
  • CLAUDE.md
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: All Rust code must pass cargo fmt --all -- --check
All Rust code must pass clippy with -D warnings (no warnings allowed)
Use anyhow::Result for error propagation
Group imports as std → external → crate with module-level granularity (imports_granularity = "Module", group_imports = "StdExternalCrate")
Follow naming conventions: constants SCREAMING_SNAKE_CASE
Follow naming conventions: structs PascalCase
Follow naming conventions: functions snake_case
Follow naming conventions: modules snake_case (file/module names)
Use tracing macros (debug!, info!, warn!, etc.) for logging
Use Tokio for async runtime (net, time, io, signal) and async operations
Keep code self-documenting; add doc comments for public API when necessary

Files:

  • src/test_helpers.rs
  • src/connection/mod.rs
src/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Expose internal fields for testing only behind #[cfg(feature = "test-internals")]

Files:

  • src/test_helpers.rs
  • src/connection/mod.rs
🧠 Learnings (2)
📚 Learning: 2025-10-15T14:24:40.523Z
Learnt from: CR
Repo: irlserver/srtla_send PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-15T14:24:40.523Z
Learning: Applies to src/**/*.rs : Expose internal fields for testing only behind #[cfg(feature = "test-internals")]

Applied to files:

  • src/test_helpers.rs
  • src/connection/mod.rs
📚 Learning: 2025-10-15T14:24:40.523Z
Learnt from: CR
Repo: irlserver/srtla_send PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-15T14:24:40.523Z
Learning: Applies to **/*.rs : Use Tokio for async runtime (net, time, io, signal) and async operations

Applied to files:

  • src/connection/mod.rs
🧬 Code graph analysis (1)
src/connection/mod.rs (7)
src/connection/batch_send.rs (3)
  • default (43-45)
  • new (50-57)
  • has_queued_packets (80-82)
src/connection/batch_recv.rs (7)
  • default (423-430)
  • new (51-55)
  • new (163-200)
  • new (336-342)
  • new (397-404)
  • std (36-36)
  • std (472-472)
src/connection/rtt.rs (1)
  • default (22-35)
src/connection/bitrate.rs (1)
  • default (13-20)
src/connection/socket.rs (2)
  • resolve_remote (41-48)
  • bind_from_ip (7-39)
src/utils.rs (1)
  • now_ms (15-20)
src/sender/selection/quality.rs (1)
  • calculate_quality_multiplier (60-62)
🔇 Additional comments (17)
src/test_helpers.rs (4)

7-16: LGTM! Imports properly updated for batching infrastructure.

The new imports (FxHashMap, BatchSender, BatchUdpSocket, CachedQuality) align correctly with the refactored connection structure and follow the import grouping conventions.


18-58: LGTM! Test helper properly refactored for batching.

The unix implementation correctly:

  • Uses socket2 for cross-platform UDP socket creation
  • Wraps socket in BatchUdpSocket
  • Initializes all new fields (packet_log as FxHashMap, highest_acked_seq, last_keepalive_sent, quality_cache, batch_sender)
  • Matches the production connection structure in mod.rs

60-100: LGTM! Non-unix test helper correctly implemented.

The non-unix implementation mirrors the unix version with appropriate platform-specific socket initialization. The code duplication between platforms is acceptable for test helpers where clarity and platform safety are priorities.


102-198: LGTM! Bulk connection creation properly implemented.

Both platform variants correctly create multiple test connections with unique addressing (incrementing remote ports and local IPs) and consistent field initialization. The structure aligns well with the single-connection helpers.

src/connection/mod.rs (13)

1-31: LGTM! Batching infrastructure properly exposed.

The new modules (batch_recv, batch_send) are correctly declared as public and their key types (BatchUdpSocket, BatchSender) are re-exported for use throughout the crate. Import grouping follows the coding guidelines (std → external → crate).


35-55: LGTM! Quality caching well-designed and documented.

The CachedQuality struct provides a clean optimization to reduce expensive quality multiplier calculations from per-packet to ~20 times per second (50ms interval). The documentation clearly explains the rationale and the Default implementation is sensible.


57-131: LGTM! Connection fields properly refactored for batching.

The field updates are well-structured:

  • Socket type updated to BatchUdpSocket for batched I/O
  • packet_log migrated to FxHashMap for O(1) operations (vs O(256) linear scan)
  • New tracking fields (highest_acked_seq, last_keepalive_sent, quality_cache, batch_sender) properly documented
  • Test-internal visibility correctly applied per coding guidelines

134-204: LGTM! Connection initialization properly updated for batching.

Both platform variants correctly integrate the BatchUdpSocket wrapper and initialize all new fields (packet_log as FxHashMap, highest_acked_seq at i32::MIN, quality_cache, batch_sender). The minimal code duplication between unix and non-unix paths is acceptable for platform-specific initialization.


218-263: LGTM! Batching API well-designed and correctly implemented.

The batching methods provide a clean interface:

  • queue_data_packet() returns true when flush needed (intuitive API)
  • flush_batch() correctly registers packets only after successful send
  • Bitrate tracking happens at queue time (appropriate for throughput measurement)
  • Inline attributes optimize hot paths

265-289: LGTM! Keepalive tracking enhanced for telemetry.

The addition of last_keepalive_sent tracking enables separate timing for periodic keepalives (every 1s) vs general packet sending, which is used correctly in the needs_keepalive() method. The extended keepalive packet provides useful telemetry to the receiver.


409-420: LGTM! Smart optimization for ACK forwarding.

The try_send_to optimization is well-implemented:

  • Fast path: synchronous send when socket ready (avoids context switch)
  • Slow path: channel fallback on WouldBlock (prevents packet loss)
  • Error handling preserved (silent drop matches original behavior)

This micro-optimization can significantly reduce latency for ACK forwarding.


460-465: LGTM! Packet registration simplified to O(1).

The migration to FxHashMap enables true O(1) packet registration, replacing the previous O(256) array scan approach.


513-523: LGTM! NAK handling simplified with O(1) removal.

The HashMap migration enables efficient O(1) NAK processing, replacing the previous linear scan.


525-548: LGTM! SRTLA ACK handling properly optimized.

The O(1) removal with mode-specific congestion handling (classic vs enhanced) is correctly implemented.


593-605: LGTM! Keepalive timing correctly separated.

Using last_keepalive_sent instead of last_sent ensures keepalives are sent every 1s regardless of data traffic, providing consistent telemetry to the receiver.


677-692: LGTM! Quality caching efficiently implemented.

The lazy recalculation pattern is well-implemented:

  • Checks staleness threshold (50ms) before recalculating
  • Updates cache atomically (value + timestamp)
  • inline(always) on hot path reduces call overhead
  • Successfully reduces expensive exp() calls from per-packet to ~20/sec

716-776: LGTM! Reconnection properly resets batching state.

Both platform variants correctly:

  • Create new BatchUdpSocket with fresh socket
  • Clear packet tracking state (packet_log, highest_acked_seq)
  • Reset batch sender state
  • Use encapsulated reset methods for submodules (clean design)

Copy link
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.

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
src/test_helpers.rs (1)

22-98: Minor: Socket setup order differs between unix and non-unix variants.

The socket initialization order is inconsistent between platforms:

  • Unix (lines 26-30): create → set_nonblocking(true)bind()
  • Non-unix (lines 65-69): create → bind()set_nonblocking(true)

While this likely doesn't cause issues in practice, maintaining consistent ordering across platforms would improve code clarity and reduce cognitive load for maintainers.

📝 Suggested standardization

Consider standardizing to the Unix order (set_nonblocking before bind) for consistency:

 let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP)).unwrap();
-socket
-    .bind(&"127.0.0.1:0".parse::<SocketAddr>().unwrap().into())
-    .unwrap();
 socket.set_nonblocking(true).unwrap();
+socket
+    .bind(&"127.0.0.1:0".parse::<SocketAddr>().unwrap().into())
+    .unwrap();
🤖 Fix all issues with AI agents
In @src/test_helpers.rs:
- Line 165: The local IPv4 construction in src/test_helpers.rs uses `10 + i as
u8` which can overflow when `count >= 246`; update the `local_ip` assignment
(the IpAddr::V4 / Ipv4Addr::new call) to wrap the index safely by using `i as u8
% 240` (i.e., replace the `10 + i as u8` expression with `10 + (i as u8 % 240)`)
the same way you fixed the unix variant so addresses stay in range.
- Line 119: The local IP construction (the local_ip assignment using IpAddr::V4
and Ipv4Addr::new) can overflow when computing 10 + i as u8 if count >= 246;
update the calculation to prevent u8 wrapping by either clamping or using a safe
arithmetic strategy (e.g., saturating addition, modulo into a 0..240 range, or
validating/limiting the incoming count parameter) so the fourth octet never
wraps past 255, or document and assert the maximum allowed count; change the
local_ip expression accordingly and/or add an explicit check on the count
parameter.
🧹 Nitpick comments (1)
src/test_helpers.rs (1)

22-190: Optional: Consider reducing code duplication.

The SrtlaConnection initialization is duplicated across all four function variants (unix/non-unix × single/multiple). While the current structure is maintainable, extracting the common initialization logic into a helper function would reduce duplication and make future field additions easier to maintain.

💡 Example refactoring approach

You could extract a helper function like:

fn build_test_connection(
    conn_id: u64,
    socket: BatchUdpSocket,
    remote: SocketAddr,
    local_ip: IpAddr,
    label: String,
) -> SrtlaConnection {
    SrtlaConnection {
        conn_id,
        socket: Arc::new(socket),
        remote,
        local_ip,
        label,
        connected: true,
        window: WINDOW_DEF * WINDOW_MULT,
        in_flight_packets: 0,
        packet_log: FxHashMap::with_capacity_and_hasher(PKT_LOG_SIZE, Default::default()),
        highest_acked_seq: i32::MIN,
        last_received: Some(Instant::now()),
        last_sent: None,
        last_keepalive_sent: None,
        rtt: RttTracker::default(),
        congestion: CongestionControl::default(),
        bitrate: BitrateTracker::default(),
        reconnection: ReconnectionState {
            connection_established_ms: now_ms(),
            startup_grace_deadline_ms: now_ms(),
            ..Default::default()
        },
        quality_cache: CachedQuality::default(),
        batch_sender: BatchSender::new(),
    }
}

Then reuse it in all four variants.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 76b970f and d49943a.

📒 Files selected for processing (2)
  • .gitignore
  • src/test_helpers.rs
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: All Rust code must pass cargo fmt --all -- --check
All Rust code must pass clippy with -D warnings (no warnings allowed)
Use anyhow::Result for error propagation
Group imports as std → external → crate with module-level granularity (imports_granularity = "Module", group_imports = "StdExternalCrate")
Follow naming conventions: constants SCREAMING_SNAKE_CASE
Follow naming conventions: structs PascalCase
Follow naming conventions: functions snake_case
Follow naming conventions: modules snake_case (file/module names)
Use tracing macros (debug!, info!, warn!, etc.) for logging
Use Tokio for async runtime (net, time, io, signal) and async operations
Keep code self-documenting; add doc comments for public API when necessary

Files:

  • src/test_helpers.rs
src/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Expose internal fields for testing only behind #[cfg(feature = "test-internals")]

Files:

  • src/test_helpers.rs
🧬 Code graph analysis (1)
src/test_helpers.rs (3)
src/utils.rs (1)
  • now_ms (15-20)
src/connection/batch_send.rs (2)
  • new (50-57)
  • default (43-45)
src/connection/mod.rs (1)
  • default (49-54)
🔇 Additional comments (3)
.gitignore (1)

24-25: Sensible housekeeping entries.

Re-adding moblin/ and introducing /.claude to exclude Claude-related tooling outputs aligns well with the PR's introduction of AI-generated and tooling-related files. These entries follow the established pattern of excluding generated/temporary artifacts from version control.

src/test_helpers.rs (2)

4-17: LGTM: Import organization follows guidelines.

The imports are correctly grouped as std → external → crate, and the additions (AtomicU64, FxHashMap, BatchSender, BatchUdpSocket, CachedQuality) align with the new batching and quality caching infrastructure.


19-20: LGTM: Atomic counter for unique test IDs.

The static atomic counter ensures unique connection IDs across test invocations, and the starting value of 1000 avoids potential conflicts with production ID ranges.

conn_id: NEXT_TEST_CONN_ID.fetch_add(1, Ordering::Relaxed),
socket: Arc::new(batch_socket),
remote,
local_ip: IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10 + i as u8)),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor: Potential u8 overflow in local IP calculation.

Line 119 computes the local IP as 10 + i as u8, which will overflow and wrap if count >= 246. While typical test scenarios likely use small connection counts (the SmallVec suggests ≤4), nothing constrains the count parameter.

Consider adding a bounds check or using a safer calculation:

local_ip: IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10 + (i as u8 % 240))),

Or document the constraint that count should be kept below 246 for meaningful test IPs.

🤖 Prompt for AI Agents
In @src/test_helpers.rs at line 119, The local IP construction (the local_ip
assignment using IpAddr::V4 and Ipv4Addr::new) can overflow when computing 10 +
i as u8 if count >= 246; update the calculation to prevent u8 wrapping by either
clamping or using a safe arithmetic strategy (e.g., saturating addition, modulo
into a 0..240 range, or validating/limiting the incoming count parameter) so the
fourth octet never wraps past 255, or document and assert the maximum allowed
count; change the local_ip expression accordingly and/or add an explicit check
on the count parameter.

socket: Arc::new(tokio_socket),
socket: Arc::new(batch_socket),
remote,
local_ip: IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10 + i as u8)),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor: Potential u8 overflow in local IP calculation (same as unix variant).

Line 165 has the same issue as line 119: 10 + i as u8 will overflow if count >= 246.

Apply the same fix as suggested for the unix variant:

local_ip: IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10 + (i as u8 % 240))),
🤖 Prompt for AI Agents
In @src/test_helpers.rs at line 165, The local IPv4 construction in
src/test_helpers.rs uses `10 + i as u8` which can overflow when `count >= 246`;
update the `local_ip` assignment (the IpAddr::V4 / Ipv4Addr::new call) to wrap
the index safely by using `i as u8 % 240` (i.e., replace the `10 + i as u8`
expression with `10 + (i as u8 % 240)`) the same way you fixed the unix variant
so addresses stay in range.

Copy link
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.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/connection/mod.rs (2)

640-655: Consider: Use #[inline] instead of #[inline(always).

Line 644 uses #[inline(always)], which forces inlining even when it may not be beneficial. The compiler's heuristics are usually better. Consider using #[inline] to allow the compiler to decide, unless profiling shows always is necessary.

Suggested change
-    #[inline(always)]
+    #[inline]
     pub fn get_cached_quality_multiplier(&mut self, current_time_ms: u64) -> f64 {

181-191: Clarify bitrate tracking naming and intent.

The method bitrate.update_on_send() is called at queue time (line 189), not at actual send time in flush_batch(). While the comment documents this choice and the design matches the Android C implementation, the method name update_on_send() is misleading. Consider renaming to update_on_queue() or updating the doc comment in bitrate.rs to explicitly explain that this method is intentionally called at queue time for bitrate calculation purposes.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d49943a and 66adf0f.

📒 Files selected for processing (1)
  • src/connection/mod.rs
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: All Rust code must pass cargo fmt --all -- --check
All Rust code must pass clippy with -D warnings (no warnings allowed)
Use anyhow::Result for error propagation
Group imports as std → external → crate with module-level granularity (imports_granularity = "Module", group_imports = "StdExternalCrate")
Follow naming conventions: constants SCREAMING_SNAKE_CASE
Follow naming conventions: structs PascalCase
Follow naming conventions: functions snake_case
Follow naming conventions: modules snake_case (file/module names)
Use tracing macros (debug!, info!, warn!, etc.) for logging
Use Tokio for async runtime (net, time, io, signal) and async operations
Keep code self-documenting; add doc comments for public API when necessary

Files:

  • src/connection/mod.rs
src/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Expose internal fields for testing only behind #[cfg(feature = "test-internals")]

Files:

  • src/connection/mod.rs
🧠 Learnings (2)
📚 Learning: 2025-10-15T14:24:40.523Z
Learnt from: CR
Repo: irlserver/srtla_send PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-15T14:24:40.523Z
Learning: Applies to **/*.rs : Use Tokio for async runtime (net, time, io, signal) and async operations

Applied to files:

  • src/connection/mod.rs
📚 Learning: 2025-10-15T14:24:40.523Z
Learnt from: CR
Repo: irlserver/srtla_send PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-15T14:24:40.523Z
Learning: Applies to src/**/*.rs : Expose internal fields for testing only behind #[cfg(feature = "test-internals")]

Applied to files:

  • src/connection/mod.rs
🧬 Code graph analysis (1)
src/connection/mod.rs (3)
src/connection/batch_send.rs (3)
  • default (43-45)
  • new (50-57)
  • has_queued_packets (80-82)
src/utils.rs (1)
  • now_ms (15-20)
src/sender/selection/quality.rs (1)
  • calculate_quality_multiplier (60-62)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test
🔇 Additional comments (12)
src/connection/mod.rs (12)

1-2: LGTM: Clean module organization and re-exports.

The public batch modules and re-exports establish a clear API surface for the batching infrastructure. Using FxHashMap is appropriate for integer sequence keys.

Also applies to: 15-16, 22-22


35-55: LGTM: Well-documented quality caching mechanism.

The 50ms cache interval (20 Hz) balances responsiveness with performance. The default values are safe: multiplier of 1.0 is neutral, and timestamp of 0 ensures immediate calculation on first use.


60-62: LGTM: Well-designed field additions for batching and optimization.

The changes are architecturally sound:

  • Arc<BatchUdpSocket> enables shared ownership for batched I/O
  • FxHashMap provides O(1) insert/remove for packet tracking (improvement over linear scans)
  • highest_acked_seq optimization avoids redundant ACK processing
  • last_keepalive_sent enables proper telemetry timing
  • Initialization values are appropriate (i32::MIN for highest_acked_seq ensures first ACK is processed)

Also applies to: 86-97, 107-107, 125-130, 152-166


134-167: LGTM: Proper socket initialization for batched operations.

The set_nonblocking(true) call is essential for try_recv operations in drain_incoming, and wrapping in Arc enables safe sharing across async contexts.


208-226: Verify: Batch queue state on flush failure.

When flush returns an error at Line 224, the packets remain in the batch queue (not cleared). On the next flush_batch call, these packets will be sent again, potentially causing duplicates.

Verify this is the intended retry behavior. If not, consider clearing the queue on error:

Potential fix if retry is not intended
         match self.batch_sender.flush(&self.socket).await {
             Ok(tracking_info) => {
                 // Register all sent packets for in-flight tracking
                 for (seq, send_time_ms) in tracking_info {
                     if let Some(s) = seq {
                         self.register_packet(s as i32, send_time_ms);
                     }
                 }
                 self.last_sent = Some(Instant::now());
                 Ok(())
             }
-            Err(e) => Err(anyhow::anyhow!("batch flush failed: {}", e)),
+            Err(e) => {
+                self.batch_sender.reset();  // Clear queue to prevent duplicates
+                Err(anyhow::anyhow!("batch flush failed: {}", e))
+            }
         }

228-252: LGTM: Proper keepalive telemetry timing.

Setting both last_sent and last_keepalive_sent enables separate tracking of general activity vs. keepalive-specific timing, which supports the extended telemetry feature mentioned in the comments.


268-326: LGTM: Clean parameter threading for ACK fast-path.

The local_listener parameter enables direct synchronous ACK forwarding (avoiding async overhead), which is documented at Lines 372-383.


372-383: LGTM: Efficient ACK fast-path with fallback.

The optimization attempts synchronous send first (avoiding task overhead), falling back to the channel only when the socket would block. Silent error dropping (Line 381) matches previous behavior.


436-474: LGTM: Well-optimized cumulative ACK handling.

The refactored logic includes several smart optimizations:

  • Early return for non-advancing ACKs (Lines 439-441)
  • Captures send time before removal for RTT calculation (Line 444)
  • Adaptive removal strategy: O(k) targeted removal for small ranges (≤64), O(n) retain for large gaps (Lines 454-463)

The threshold of 64 is reasonable, though it could be tuned based on profiling. The old_highest != i32::MIN check (Line 455) correctly handles the initial state.


476-511: LGTM: Consistent O(1) packet removal pattern.

Both methods follow a clear pattern: remove from HashMap (O(1)), update in_flight_packets from map length, then trigger congestion handling. The mode-based branching in handle_srtla_ack_specific is well-structured.


556-568: LGTM: More precise keepalive timing.

Using last_keepalive_sent instead of last_sent ensures keepalives are sent at proper intervals regardless of data packet activity. This is more correct than the previous implementation.


679-714: LGTM: Comprehensive state reset for reconnection.

The reset_state method thoroughly clears all stateful tracking (packet log, counters, submodules, batch queue). The reconnect method properly replaces the socket with a fresh BatchUdpSocket and resets state. Not resetting connection_established_ms is intentional per the comment at Line 709.

Copy link
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.

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
src/connection/mod.rs (1)

334-353: Reconnect/reset: reset_state() should also clear keepalive timestamps and cached quality.

reset_state() is used by reconnect(), but it doesn’t reset last_sent, last_keepalive_sent, or quality_cache. That can cause stale timers/multiplier to bleed across reconnections.

Proposed fix
 fn reset_state(&mut self) {
     self.connected = false;
     self.last_received = None;
+    self.last_sent = None;
+    self.last_keepalive_sent = None;
     self.window = WINDOW_DEF * WINDOW_MULT;
     self.in_flight_packets = 0;
     self.packet_log.clear();
     self.highest_acked_seq = i32::MIN;
 
     // Reset submodule state
     self.congestion.reset();
     self.rtt.reset();
     self.bitrate.reset();
     self.batch_sender.reset();
+
+    // Reset cached quality so the next call recalculates immediately
+    self.quality_cache = CachedQuality::default();
 
     // Reset reconnection tracking
     self.reconnection.last_reconnect_attempt_ms = now_ms();
     self.reconnection.reconnect_failure_count = 0;
 }

As per coding guidelines.

Also applies to: 409-444

🤖 Fix all issues with AI agents
In @src/connection/mod.rs:
- Around line 32-53: The CachedQuality struct and its fields are overly public;
make CachedQuality and its fields internal to the crate by changing visibility
to pub(crate) (or make the struct non-pub and expose only what’s needed) and, if
tests need access, gate wider visibility behind the test-internals feature
(e.g., #[cfg(feature = "test-internals")]) so that CachedQuality, its fields
multiplier and last_calculated_ms, and any related items (retain
QUALITY_CACHE_INTERVAL_MS as public only if intentionally part of the public
API) follow the internal-fields-for-tests guideline.

In @src/protocol/mod.rs:
- Around line 71-98: The test test_extended_keepalive_backwards_compat compares
wall-clock timestamps from create_keepalive_packet_ext and
create_keepalive_packet and can flake if CI stalls; update the test to avoid
fragile timing by either injecting a fixed timestamp into
create_keepalive_packet_ext/create_keepalive_packet (preferred) or loosening the
assertion threshold (e.g., 2000–5000 ms) in the diff check that uses
extract_keepalive_timestamp; target the change inside
test_extended_keepalive_backwards_compat and ensure calls to
create_keepalive_packet_ext, create_keepalive_packet, and
extract_keepalive_timestamp still validate timestamp presence but use the stable
comparison approach.
🧹 Nitpick comments (8)
src/sender/status.rs (1)

66-72: Consider using ToggleSnapshot for toggle reads.

The AI summary mentions that ToggleSnapshot was introduced to minimize atomic loads. Currently, each toggle is read separately with Ordering::Relaxed. If multiple toggles need to be read consistently, consider using a snapshot pattern to reduce atomic operations on hot paths.

However, since this is a logging function with early exit when INFO is disabled, the current approach is acceptable for non-critical status reporting.

src/protocol/builders.rs (2)

20-29: Consider using to_be_bytes() for timestamp encoding consistency.

The manual byte-shifting loop (lines 25-27) works correctly but differs from create_keepalive_packet_ext which uses to_be_bytes() (line 54). Using consistent encoding methods improves readability.

♻️ Suggested refactor for consistency
 pub fn create_keepalive_packet() -> [u8; 10] {
     let mut pkt = [0u8; 10];
     pkt[0..2].copy_from_slice(&SRTLA_TYPE_KEEPALIVE.to_be_bytes());
     let ts = chrono::Utc::now().timestamp_millis() as u64;
-    for i in 0..8 {
-        pkt[2 + i] = ((ts >> (56 - i * 8)) & 0xff) as u8;
-    }
+    pkt[2..10].copy_from_slice(&ts.to_be_bytes());
     pkt
 }

69-82: Consider SmallVec::with_capacity() instead of from_vec().

Using SmallVec::from_vec(vec![0u8; ...]) allocates a Vec first, then converts. For better efficiency, use SmallVec::from_elem() or manual initialization.

♻️ Suggested optimization
 pub fn create_ack_packet(acks: &[u32]) -> SmallVec<u8, 64> {
-    // Create packets that match the actual SRTLA receiver format (4-byte header)
-    let mut pkt = SmallVec::from_vec(vec![0u8; 4 + 4 * acks.len()]);
+    let len = 4 + 4 * acks.len();
+    let mut pkt = SmallVec::from_elem(0u8, len);
     pkt[0..2].copy_from_slice(&SRTLA_TYPE_ACK.to_be_bytes());
src/protocol/parsers.rs (1)

6-18: Consider using u64::from_be_bytes() for consistency with builders.

The manual byte-shifting loop works correctly, but using u64::from_be_bytes() would be more idiomatic and consistent with how extract_keepalive_conn_info parses other fields.

♻️ Suggested refactor
 pub fn extract_keepalive_timestamp(buf: &[u8]) -> Option<u64> {
     if buf.len() < 10 {
         return None;
     }
     if get_packet_type(buf)? != SRTLA_TYPE_KEEPALIVE {
         return None;
     }
-    let mut ts: u64 = 0;
-    for i in 0..8 {
-        ts = (ts << 8) | (buf[2 + i] as u64);
-    }
-    Some(ts)
+    Some(u64::from_be_bytes([
+        buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9]
+    ]))
 }
src/protocol/mod.rs (1)

1-28: Re-exports are fine; consider removing redundant #[allow(unused_imports)].

src/sender/mod.rs (1)

214-265: Housekeeping: consider using one ToggleSnapshot + remove duplicate sync_readers() call.

You already snapshot toggles for drain; you can reuse it for classic (consistency + one fewer atomic load), and avoid calling sync_readers() twice in the same housekeeping tick when no connection changes occurred.

Also applies to: 355-407

src/connection/mod.rs (2)

178-223: flush_batch(): error wrapping can preserve the original error.

Consider map_err/Context to avoid stringifying errors and to keep sources intact.


370-386: Avoid crate::sender::* dependency from crate::connection::* for quality calculation.

get_cached_quality_multiplier() importing crate::sender::calculate_quality_multiplier makes connection depend on sender. Consider moving the calculation into connection (or a shared module) and have sender call into it instead.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66adf0f and c795119.

📒 Files selected for processing (14)
  • src/connection/ack_nak.rs
  • src/connection/mod.rs
  • src/connection/packet_io.rs
  • src/protocol.rs
  • src/protocol/builders.rs
  • src/protocol/constants.rs
  • src/protocol/mod.rs
  • src/protocol/parsers.rs
  • src/protocol/types.rs
  • src/registration/mod.rs
  • src/registration/probing.rs
  • src/sender/connections.rs
  • src/sender/mod.rs
  • src/sender/status.rs
💤 Files with no reviewable changes (1)
  • src/protocol.rs
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: All Rust code must pass cargo fmt --all -- --check
All Rust code must pass clippy with -D warnings (no warnings allowed)
Use anyhow::Result for error propagation
Group imports as std → external → crate with module-level granularity (imports_granularity = "Module", group_imports = "StdExternalCrate")
Follow naming conventions: constants SCREAMING_SNAKE_CASE
Follow naming conventions: structs PascalCase
Follow naming conventions: functions snake_case
Follow naming conventions: modules snake_case (file/module names)
Use tracing macros (debug!, info!, warn!, etc.) for logging
Use Tokio for async runtime (net, time, io, signal) and async operations
Keep code self-documenting; add doc comments for public API when necessary

Files:

  • src/sender/status.rs
  • src/protocol/parsers.rs
  • src/connection/packet_io.rs
  • src/connection/ack_nak.rs
  • src/protocol/builders.rs
  • src/protocol/types.rs
  • src/registration/probing.rs
  • src/protocol/mod.rs
  • src/sender/connections.rs
  • src/sender/mod.rs
  • src/protocol/constants.rs
  • src/connection/mod.rs
  • src/registration/mod.rs
src/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Expose internal fields for testing only behind #[cfg(feature = "test-internals")]

Files:

  • src/sender/status.rs
  • src/protocol/parsers.rs
  • src/connection/packet_io.rs
  • src/connection/ack_nak.rs
  • src/protocol/builders.rs
  • src/protocol/types.rs
  • src/registration/probing.rs
  • src/protocol/mod.rs
  • src/sender/connections.rs
  • src/sender/mod.rs
  • src/protocol/constants.rs
  • src/connection/mod.rs
  • src/registration/mod.rs
🧠 Learnings (2)
📚 Learning: 2025-10-15T14:24:40.523Z
Learnt from: CR
Repo: irlserver/srtla_send PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-15T14:24:40.523Z
Learning: Applies to src/tests/**/*.rs : Place integration, end-to-end, and protocol tests under src/tests/

Applied to files:

  • src/protocol/mod.rs
📚 Learning: 2025-10-15T14:24:40.523Z
Learnt from: CR
Repo: irlserver/srtla_send PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-15T14:24:40.523Z
Learning: Applies to src/**/*.rs : Expose internal fields for testing only behind #[cfg(feature = "test-internals")]

Applied to files:

  • src/protocol/mod.rs
🧬 Code graph analysis (9)
src/sender/status.rs (3)
src/utils.rs (1)
  • now_ms (15-20)
src/registration/mod.rs (1)
  • active_connections (281-283)
src/connection/mod.rs (5)
  • current_bitrate_mbps (405-407)
  • get_smooth_rtt_ms (269-271)
  • get_fast_rtt_ms (273-275)
  • get_rtt_jitter_ms (277-279)
  • is_rtt_stable (265-267)
src/protocol/parsers.rs (1)
src/protocol/types.rs (1)
  • get_packet_type (41-46)
src/connection/packet_io.rs (3)
src/protocol/types.rs (1)
  • get_packet_type (41-46)
src/utils.rs (1)
  • now_ms (15-20)
src/protocol/parsers.rs (3)
  • parse_srt_ack (67-75)
  • parse_srt_nak (78-107)
  • parse_srtla_ack (110-129)
src/connection/ack_nak.rs (1)
src/utils.rs (1)
  • now_ms (15-20)
src/registration/probing.rs (2)
src/utils.rs (1)
  • now_ms (15-20)
src/registration/mod.rs (1)
  • new (41-57)
src/protocol/mod.rs (3)
src/protocol/builders.rs (3)
  • create_ack_packet (71-82)
  • create_keepalive_packet (21-29)
  • create_keepalive_packet_ext (48-67)
src/protocol/parsers.rs (5)
  • extract_keepalive_conn_info (28-64)
  • extract_keepalive_timestamp (6-18)
  • parse_srt_ack (67-75)
  • parse_srt_nak (78-107)
  • parse_srtla_ack (110-129)
src/protocol/types.rs (7)
  • get_packet_type (41-46)
  • get_srt_sequence_number (49-59)
  • is_srt_ack (36-38)
  • is_srtla_keepalive (31-33)
  • is_srtla_reg1 (16-18)
  • is_srtla_reg2 (21-23)
  • is_srtla_reg3 (26-28)
src/sender/connections.rs (1)
src/connection/mod.rs (1)
  • connect_from_ip (131-164)
src/sender/mod.rs (7)
src/sender/connections.rs (2)
  • apply_connection_changes (16-71)
  • create_connections_from_ips (73-92)
src/sender/housekeeping.rs (1)
  • handle_housekeeping (20-141)
src/sender/packet_handler.rs (2)
  • drain_packet_queue (158-190)
  • handle_uplink_packet (102-150)
src/sender/status.rs (1)
  • log_connection_status (17-151)
src/registration/mod.rs (2)
  • new (41-57)
  • has_connected (285-287)
src/sender/sequence.rs (1)
  • new (65-70)
src/toggles.rs (2)
  • new (34-40)
  • snapshot (54-60)
src/registration/mod.rs (1)
src/registration/probing.rs (3)
  • default_probing_state (191-193)
  • new_probe_id (180-185)
  • new_probe_results (187-189)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test
🔇 Additional comments (39)
src/registration/probing.rs (8)

1-15: LGTM! Well-structured probing state types.

The ProbingState enum and imports are clean. The enum variants clearly represent the probing lifecycle states.


17-22: LGTM! ProbeResult struct is well-defined.

The struct captures the essential data for RTT probing with appropriate types. Using Option<u64> for rtt_ms correctly represents the pending/completed state.


24-71: LGTM! Solid probing initialization logic.

The start_probing method correctly:

  • Guards against re-probing when already started or connections are active
  • Clears previous results before starting
  • Handles send failures gracefully with logging
  • Falls back to first connection when no probes can be sent

One minor observation: the 2000ms timeout (line 58) is hardcoded. Consider extracting this to a named constant for clarity and easier tuning.


73-93: LGTM! Probe response handling is correct.

The method properly guards state, uses saturating_sub to avoid underflow, and only records the first response per connection (idempotent).


95-146: LGTM! Completion check with proper fallback handling.

The logic correctly handles both timeout and all-responded cases, selects the best connection by minimum RTT, and falls back gracefully when no probes respond.


148-153: LGTM! Clean state query method.

The is_probing method using matches! is idiomatic and readable.


156-177: LGTM! Test helpers are properly gated.

The #[cfg(test)] gate ensures these methods don't pollute the release build. The #[allow(dead_code)] is appropriate for test-only utilities.


179-193: LGTM! Clean initializer functions.

The helper functions provide consistent initialization and proper encapsulation. Using rand::rng().fill_bytes() aligns with rand 0.9.x API.

src/registration/mod.rs (5)

20-32: LGTM! Struct field visibility changes are appropriate.

The pub(crate) visibility for pending_timeout_at_ms, active_connections, reg1_target_idx, and reg1_next_send_at_ms enables internal access for the probing module while maintaining encapsulation from external crates.


40-57: LGTM! Constructor properly delegates to probing helpers.

The initialization correctly uses default_probing_state(), new_probe_id(), and new_probe_results() for consistent setup across the crate.


157-173: LGTM! REG_NGP handling integrates probing correctly.

The probe response handling during WaitingForProbes state is correctly prioritized before normal REG_NGP processing.


273-325: LGTM! Test accessors are comprehensive and properly gated.

The test-only impl block provides both read and write accessors needed for thorough unit testing without exposing internals in production.


1-10: The rand::RngCore import on line 4 is not unused and is required for the code to compile. On line 43, the code calls rand::rng().fill_bytes(&mut id), where fill_bytes is a method provided by the RngCore trait. For this method to be available in scope, the trait must be imported. Removing this import would result in a compilation error.

Likely an incorrect or invalid review comment.

src/sender/status.rs (5)

1-8: LGTM! Appropriate imports for status logging.

The imports are well-organized following the guideline (std → external → crate).


10-25: LGTM! Early exit optimization is effective.

The tracing::enabled! check at the start avoids all computation when INFO logging is disabled - good practice for performance-sensitive logging.


27-50: LGTM! Single-pass stats collection is efficient.

The loop collects all needed metrics in one iteration, avoiding redundant passes over connections.


91-143: LGTM! Per-connection logging is comprehensive.

The detailed per-connection output includes all relevant metrics. The Cow<'static, str> optimization for common score cases avoids unnecessary allocations.


145-151: LGTM! Warning thresholds are reasonable.

Warning when no connections are active or when less than half are active provides useful operational visibility.

src/protocol/types.rs (3)

1-12: LGTM! ConnectionInfo struct is well-defined.

The struct captures all necessary connection metrics for extended keepalive. The derives (Debug, Clone, Copy, PartialEq, Eq) are appropriate for a data transfer object.


14-38: LGTM! Packet type helper functions are correct.

The helpers combine length validation with type checking appropriately. The #[allow(dead_code)] annotations indicate these are primarily for tests, which is acceptable.


40-59: LGTM! Inline parsing utilities are efficient.

The #[inline] hints are appropriate for these small, frequently-called functions. The get_srt_sequence_number correctly masks the high bit to distinguish SRT data packets from control packets.

src/protocol/builders.rs (2)

6-18: LGTM! Registration packet builders are correct.

Both REG1 and REG2 packet builders correctly construct fixed-size packets with type header and ID payload using big-endian encoding.


31-67: LGTM! Extended keepalive builder is well-documented and correct.

The comprehensive doc comment clearly describes the packet format and backwards compatibility considerations. The implementation correctly encodes all fields in big-endian format.

src/protocol/parsers.rs (4)

20-64: LGTM! Extended keepalive parser is robust.

The parser correctly validates magic number and version before parsing fields, ensuring backwards compatibility. Using from_be_bytes for all numeric fields is consistent.


66-75: LGTM! SRT ACK parser is correct.

The parser extracts the ACK sequence number from the correct offset (bytes 16-19) per SRT protocol.


77-107: LGTM! NAK range parsing handles edge cases correctly.

The implementation:

  • Properly handles the 0x80000000 range flag
  • Uses wrapping_add for sequence increment (handles wraparound)
  • Caps output at 1000 entries to prevent unbounded allocation
  • Correctly checks buffer bounds before reading range end

The cap at 1000 entries is a reasonable safeguard against malformed packets.


109-129: LGTM! SRTLA ACK parser matches C implementation.

The comment correctly explains the 4-byte skip behavior matching the original C implementation. The parsing loop is straightforward and correct.

src/connection/packet_io.rs (6)

1-13: LGTM! Imports are well-organized.

The imports follow the std → external → crate guideline and include all necessary types for packet processing.


15-50: Consider whether n == 0 should break the loop.

Line 28-29 breaks the loop when try_recv returns 0 bytes. While rare for UDP, an empty datagram is technically valid. However, since empty packets carry no meaningful data, breaking here is reasonable. The current behavior is acceptable.


52-73: LGTM! Single packet processing wrapper is clean.

The process_packet method provides a convenient single-packet API that delegates to the internal implementation.


75-110: LGTM! Registration event handling is comprehensive.

The registration event handling correctly:

  • Updates connection state on Reg3 (connected, timestamps)
  • Clears state on RegErr
  • Triggers immediate REG1 send on RegNgp
  • Returns early after processing registration packets

114-131: Good optimization: fast-path synchronous send for ACKs.

The try_send_to fast path avoids task context switches when the socket is ready. Falling back to the channel only when WouldBlock occurs is efficient. Silent error dropping on line 128 is acceptable for UDP where packet loss is expected.


132-168: LGTM! NAK and SRTLA ACK handling is correct.

The packet type dispatching correctly routes:

  • SRT NAKs to nak_numbers and forwards to client
  • SRTLA ACKs to srtla_ack_numbers (not forwarded)
  • Keepalive to RTT handler
  • Other packets forwarded to client

Debug logging with emoji provides good visibility during troubleshooting.

src/sender/connections.rs (1)

73-92: create_connections_from_ips(): behavior looks good for partial failures.

src/connection/ack_nak.rs (2)

8-14: register_packet(): OK, but consider saturating the cast to i32.


21-59: No action required. The project's MSRV is Rust 1.87, and i64::unsigned_abs() was stabilized in Rust 1.34.0. The code is fully compatible.

Likely an incorrect or invalid review comment.

src/sender/mod.rs (2)

16-33: Public re-exports for tests look reasonable.


113-121: No action needed—flush_all_batches() already includes early-exit guards.

The implementation includes a fast read-only check (has_queued_packets() || needs_batch_flush()) with an early return when idle (lines 372–378), ensuring no I/O syscalls occur when nothing is queued. Additional per-connection checks guard the actual flush loop (line 382). The 15ms timer incurs minimal overhead on idle connections.

src/protocol/constants.rs (1)

1-50: Constants module looks clean and consistent.

Copy link
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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @src/connection/ack_nak.rs:
- Around line 21-59: In handle_srt_ack: the linear range calculation and the for
loop (using (old_highest + 1)..=ack) break if sequence numbers wrap; detect
wraparound by converting seqs to unsigned (e.g. as u32) and compute distance
with wrapping subtraction to decide small vs large range, and when removing in
the small-range path iterate with a wrapping increment (start =
old_highest.wrapping_add(1) as u32; loop removing each seq cast back to i32
until you reach ack as u32) so packets are removed correctly across the 32-bit
wrap boundary while preserving the retain() fallback for large gaps.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c795119 and 6f84513.

📒 Files selected for processing (5)
  • src/connection/ack_nak.rs
  • src/connection/mod.rs
  • src/connection/reconnection.rs
  • src/sender/mod.rs
  • src/test_helpers.rs
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: All Rust code must pass cargo fmt --all -- --check
All Rust code must pass clippy with -D warnings (no warnings allowed)
Use anyhow::Result for error propagation
Group imports as std → external → crate with module-level granularity (imports_granularity = "Module", group_imports = "StdExternalCrate")
Follow naming conventions: constants SCREAMING_SNAKE_CASE
Follow naming conventions: structs PascalCase
Follow naming conventions: functions snake_case
Follow naming conventions: modules snake_case (file/module names)
Use tracing macros (debug!, info!, warn!, etc.) for logging
Use Tokio for async runtime (net, time, io, signal) and async operations
Keep code self-documenting; add doc comments for public API when necessary

Files:

  • src/connection/ack_nak.rs
  • src/test_helpers.rs
  • src/connection/reconnection.rs
  • src/sender/mod.rs
  • src/connection/mod.rs
src/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Expose internal fields for testing only behind #[cfg(feature = "test-internals")]

Files:

  • src/connection/ack_nak.rs
  • src/test_helpers.rs
  • src/connection/reconnection.rs
  • src/sender/mod.rs
  • src/connection/mod.rs
🧠 Learnings (1)
📚 Learning: 2025-10-15T14:24:40.523Z
Learnt from: CR
Repo: irlserver/srtla_send PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-15T14:24:40.523Z
Learning: Applies to src/**/*.rs : Expose internal fields for testing only behind #[cfg(feature = "test-internals")]

Applied to files:

  • src/connection/mod.rs
🧬 Code graph analysis (5)
src/connection/ack_nak.rs (1)
src/utils.rs (1)
  • now_ms (15-20)
src/test_helpers.rs (4)
src/connection/batch_recv.rs (7)
  • std (36-36)
  • std (472-472)
  • new (51-55)
  • new (163-200)
  • new (336-342)
  • new (397-404)
  • default (423-430)
src/utils.rs (1)
  • now_ms (15-20)
src/connection/batch_send.rs (2)
  • new (50-57)
  • default (43-45)
src/connection/mod.rs (1)
  • default (46-51)
src/connection/reconnection.rs (1)
src/connection/mod.rs (1)
  • should_attempt_reconnect (391-393)
src/sender/mod.rs (6)
src/sender/connections.rs (2)
  • apply_connection_changes (16-71)
  • create_connections_from_ips (73-92)
src/sender/housekeeping.rs (1)
  • handle_housekeeping (20-141)
src/sender/packet_handler.rs (2)
  • drain_packet_queue (158-190)
  • handle_uplink_packet (102-150)
src/sender/status.rs (1)
  • log_connection_status (17-151)
src/sender/sequence.rs (1)
  • new (65-70)
src/toggles.rs (2)
  • new (34-40)
  • snapshot (54-60)
src/connection/mod.rs (4)
src/sender/mod.rs (1)
  • tokio (91-91)
src/utils.rs (1)
  • now_ms (15-20)
src/connection/batch_send.rs (3)
  • default (43-45)
  • new (50-57)
  • has_queued_packets (80-82)
src/sender/selection/quality.rs (1)
  • calculate_quality_multiplier (60-62)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test
🔇 Additional comments (17)
src/sender/mod.rs (3)

112-121: LGTM! Well-documented batch flush timer.

The 15ms flush interval with MissedTickBehavior::Skip is appropriate for batching. The comment referencing Moblin provides good context for the design choice. The skip behavior prevents backlog buildup under load.


155-272: Macro approach successfully unifies event loop logic.

The event_loop! macro elegantly eliminates code duplication between Unix and non-Unix platforms. The only difference (SIGHUP handling) is parameterized via the macro argument, which keeps the main event loop logic centralized and maintainable.

The consistent pattern of creating toggle snapshots once per event iteration is efficient—it reduces atomic loads in the hot path.


266-268: No action needed—error handling is already present and appropriate.

The flush_all_batches function in src/sender/packet_handler.rs already handles errors internally. Each connection's flush_batch() call is wrapped with if let Err(e) = ..., and errors are logged using the warn! tracing macro with context about which connection failed. The function continues processing remaining connections even if one fails, which is the correct behavior for a periodic maintenance operation that shouldn't interrupt the event loop.

src/connection/reconnection.rs (1)

20-25: Exponential backoff calculation is correct.

The backoff strategy is well-implemented:

  • Capped failures prevent excessive bit shifts
  • saturating_mul prevents overflow
  • Final min() cap ensures maximum delay of 120s

The progression (5s → 10s → 20s → 40s → 80s → 120s) provides reasonable retry behavior.

src/test_helpers.rs (3)

20-21: Thread-safe test ID generation.

Using AtomicU64 with Ordering::Relaxed (line 43) is appropriate for test connection IDs where strict ordering isn't required, only uniqueness.


23-31: Test socket creation is properly configured.

The socket is correctly set to non-blocking and bound to localhost. The use of socket2 provides cross-platform compatibility for test environments.


42-66: Comprehensive connection initialization.

All new fields are properly initialized:

  • highest_acked_seq: i32::MIN (correct sentinel for first ACK)
  • last_keepalive_sent: None (will be set on first keepalive)
  • quality_cache: CachedQuality::default() (1.0 multiplier initially)
  • batch_sender: BatchSender::new() (empty batch queue)

The use of FxHashMap::with_capacity_and_hasher for packet_log is consistent with the main implementation.

src/connection/ack_nak.rs (2)

52-58: RTT bounds check is appropriate.

The bounds 0 < rtt <= 10_000 (10 seconds) effectively filter out invalid measurements while allowing high-latency connections. This prevents corrupt timestamps from skewing RTT estimates.


98-108: Global ACK window increase logic matches C implementation.

The comment clearly documents the behavior: global ACKs increase the window by 1 (up to the max) for connected connections that have received data. The check self.last_received.is_some() correctly corresponds to the C version's last_rcvd != 0 check.

src/connection/mod.rs (8)

32-52: Quality caching design is well-documented.

The 50ms interval (QUALITY_CACHE_INTERVAL_MS) provides a good balance—reducing expensive exp() calls from thousands per second to ~20 times per second while maintaining responsiveness to network condition changes.

The documentation clearly explains the performance benefit.


83-94: Packet log documentation is excellent.

The comments clearly explain:

  1. The data structure (FxHashMap for O(1) operations)
  2. The performance benefit over linear scan
  3. The purpose of highest_acked_seq for optimization

This makes the design decisions immediately clear to future maintainers.


131-164: Socket initialization follows correct sequence.

The socket is created, bound, connected, and then set to non-blocking. The BatchUdpSocket::new() call wraps it with the batching infrastructure. The initialization is clean and error-handling uses ? appropriately.


334-356: State reset methods are well-structured.

The refactoring into reset_core_state() (shared logic) and separate mark_for_recovery() (soft reset) vs reset_state() (full reset) is clean. The comments clearly distinguish between the two reset scenarios:

  • Soft reset: preserves congestion/bitrate stats
  • Full reset: clears all state

This makes the intent clear and reduces duplication.


374-389: Quality multiplier caching is correctly implemented.

The get_cached_quality_multiplier() method:

  1. Checks if cache is stale (>50ms old)
  2. Recalculates if needed
  3. Updates cache timestamp
  4. Returns cached value

The inline(always) annotation ensures the staleness check and cache lookup are inlined into the hot path, minimizing overhead.


429-443: Reconnect now properly reinitializes batching state.

The updated reconnect() method:

  1. Creates a new socket with batching support
  2. Calls reset_state() to clear all connection state including batch queue
  3. Maintains reconnection tracking properly

The async signature is appropriate since socket operations are async in Tokio.


286-298: IDLE_TIME is properly in scope. It is defined in src/protocol/constants.rs as pub const IDLE_TIME: u64 = 1; and imported into src/connection/mod.rs via the glob import use crate::protocol::*; on line 27. No action needed.

Likely an incorrect or invalid review comment.


178-223: Batching API provides clear flush semantics.

The batching methods form a coherent API:

  • queue_data_packet() returns a signal when batch is full
  • needs_batch_flush() checks time-based flush requirement
  • has_queued_packets() queries queue state
  • flush_batch() handles the send and in-flight registration

The inline annotations on hot-path methods (queue_data_packet, needs_batch_flush, has_queued_packets) are appropriate for performance-critical code.

The conversion of sequence numbers from u32 to i32 at line 215 is safe: the SRT protocol parser (get_srt_sequence_number) enforces that only sequence numbers with the high bit unset are accepted, guaranteeing all values fit within the i32 range.

datagutt and others added 4 commits January 10, 2026 04:46
…K in ack_nack

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
… uplinks.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@datagutt datagutt merged commit ef95926 into main Jan 11, 2026
5 of 6 checks passed
@datagutt datagutt deleted the refactor/optimizations branch January 12, 2026 17:53
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