Skip to content

feat: add live frontend API - #255

Merged
artrixdotdev merged 77 commits into
mainfrom
feat/add-live-frontend-api
Jul 27, 2026
Merged

feat: add live frontend API#255
artrixdotdev merged 77 commits into
mainfrom
feat/add-live-frontend-api

Conversation

@artrixdotdev

@artrixdotdev artrixdotdev commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Summary

  • add reusable LivePublisher<V, E>, EventListener<V, E>, and EventSubscription<E> primitives with futures::Stream and Tokio broadcast semantics
  • expose independent live listeners directly on Engine, Torrent, PeerHandle, and TrackerHandle
  • project one canonical TorrentEventKind hierarchy through engine events instead of maintaining duplicate engine/torrent/peer/tracker vocabularies
  • carry public Torrent, PeerHandle, and TrackerHandle values so applications can descend into scoped detail only when needed
  • keep direct Engine and Torrent methods as the sole command API; no duplicate command enums or generic send methods
  • use weak actor/handle back-references, typed scope registries, and irreversible terminal states to prevent ownership cycles and late-update resurrection
  • give trackers opaque identities so credential-free display endpoints can safely collide
  • aggregate live peer transfer rates and torrent ETA, and model tracker health as an explicit lifecycle
  • reserve versioned Serde-compatible snapshots for persistence, with centralized validation and actor-transactional engine restore
  • document the contract and include a compiling live frontend/persistence example

API boundary

Live rendering uses listeners, subscriptions, and coherent current views. Each scope owns a bounded publisher; lagging consumers receive EventStreamError::Lagged, redraw from listener.view(), and continue. Terminal scope events close their stream, and late actor updates are rejected.

Snapshots are not display models. They contain the complete resumable torrent state, while the application chooses the Serde format and storage location. Engine restore validates the complete snapshot and applies it as one authoritative actor operation.

Internal actor messages remain private. Public operations use direct methods such as engine.add_torrent(...), engine.remove_torrent(...), torrent.start(), and torrent.pause().

Issue coverage

Closes #162
Closes #170
Closes #211
Closes #227
Closes #239
Closes #240

Advances #230, #231, and #238.
Part of #221.

Validation

  • cargo fmt --all -- --check
  • cargo nextest run --workspace --all-features — 143 passed, 8 skipped
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps

Summary by CodeRabbit

  • New Features

    • Added a live observation model with typed, sequenced engine/torrent/peer/tracker events and current-state views (subscribe/view/listener).
    • Added versioned engine/torrent persistence snapshots with restoration verification options (torrent snapshot v2).
  • Bug Fixes

    • Improved restore/cleanup correctness with stricter snapshot validation and safer torrent/engine lifecycle handling.
    • Enhanced peer request scheduling reliability and typed error reporting for invalid snapshots and actor communication.
  • Documentation

    • Updated README and runtime guidance to prefer live listeners over polling persistence snapshots.
  • Tests

    • Expanded live, persistence, and compatibility tests; added actor-only CI coverage.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The PR adds typed live event and view APIs, hub-backed actor projections, versioned snapshot validation and restoration, byte-based metrics, availability-aware scheduling, and listener-driven examples and tests.

Changes

Live observation and persistence integration

Layer / File(s) Summary
Live contracts and projections
crates/libtortillas/src/live/*, crates/libtortillas/src/metrics.rs
Adds typed events, listeners, subscriptions, views, scoped handles, hub registries, and sampled transfer metrics.
Engine and torrent runtime wiring
crates/libtortillas/src/engine/*, crates/libtortillas/src/torrent/*, crates/libtortillas/src/errors.rs
Routes commands through typed actor APIs, publishes lifecycle and health events, handles restart states, and maps communication failures to typed errors.
Persistence and restoration
crates/libtortillas/src/engine/snapshot.rs, crates/libtortillas/src/torrent/snapshot.rs, crates/libtortillas/tests/persistence.rs
Adds version 2 snapshots, legacy migration, validation, storage reconciliation, verification modes, rollback, and persistence coverage.
Piece, peer, tracker, and transport flow
crates/libtortillas/src/pieces/*, crates/libtortillas/src/peer/*, crates/libtortillas/src/tracker/*, crates/libtortillas/src/protocol/*
Adds availability-aware request scheduling, byte accounting, canonical bitfield encoding, tracker metrics, and buffered transport handling.
Examples, documentation, and CI
README.md, crates/libtortillas/examples/live.rs, crates/libtortillas/src/ARCHITECTURE.md, .github/workflows/checks.yml, crates/libtortillas/tests/*
Documents the listener-based runtime contract, adds a live example, updates integration tests and fixtures, and checks actor-only builds.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: enhancement, high prio, refactor

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding a new live frontend API.
Linked Issues check ✅ Passed Issues #162/#170/#211/#227/#239/#240 are addressed by snapshots/restore, scheduler fixes, live events, example, and docs.
Out of Scope Changes check ✅ Passed I don't see material changes outside the live API, persistence, scheduler, example, and docs work described by the linked issues.
Docstring Coverage ✅ Passed Docstring coverage is 82.69% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/add-live-frontend-api

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.

@artrixdotdev

Copy link
Copy Markdown
Owner Author

Completed the full self-review follow-up in 12 ordered commits. The update replaces strong frontend/actor cycles with weak back-references, makes torrent/peer/tracker terminal states irreversible, closes subscriptions correctly, uses opaque tracker IDs, unifies engine fanout around TorrentEventKind, aggregates transfer metrics, centralizes snapshot validation, makes engine restore transactional, reconciles removal even when actor shutdown fails, and preserves typed actor communication errors. Added focused regressions for hub deallocation, stream closure, late updates, tracker collisions, terminal reconciliation, authoritative restore state, and live metrics.

Validation on the pushed head (e7bc20d): 143 nextest tests passed with 8 skipped; strict all-target/all-feature clippy passed; formatting passed; rustdoc with warnings denied passed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
crates/libtortillas/src/peer/actor.rs (1)

658-686: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Frontend update fires on every peer wire message, not just state-relevant ones.

self.frontend.update(...) at line 685 runs unconditionally after the whole match msg { ... } block, so KeepAlive, Request, Cancel, and Extended messages — none of which change any PeerView field — still trigger a full PeerView rebuild plus a chain through FrontendPublisher::peer_updatedtorrent_view() (clones the whole EngineView.torrents Vec) → publish_torrent → the shared engine-wide LivePublisher lock/broadcast. During active piece exchange this is the highest-frequency code path per peer, and it now funnels through one shared lock per engine on every single message across every peer/torrent.

Move the update into only the match arms that actually change PeerView fields (Choke, Unchoke, Interested, NotInterested, Have, Piece, Bitfield), and drop it for KeepAlive/Request/Cancel/Extended/unexpected Handshake.

♻️ Proposed fix
          PeerMessages::Choke => {
             self.peer.set_am_choked(true);
             trace!("Peer choked us");
+            self.frontend.update(PeerView::from_peer(&self.peer, true));
          }
          PeerMessages::Unchoke => {
             self.peer.update_last_optimistic_unchoke();
             self.peer.set_am_choked(false);
             self.flush_queue().await;
             self.flush_block_requests().await;
             self.notify_ready().await;
             trace!("Peer unchoked us");
+            self.frontend.update(PeerView::from_peer(&self.peer, true));
          }
          PeerMessages::Interested => {
             self.peer.set_interested(true);
             trace!("Peer is interested in our pieces");
+            self.frontend.update(PeerView::from_peer(&self.peer, true));
          }
          PeerMessages::NotInterested => {
             self.peer.set_interested(false);
             trace!("Peer is not interested in our pieces");
+            self.frontend.update(PeerView::from_peer(&self.peer, true));
          }
          ...
          PeerMessages::Piece(index, offset, data) => {
             ...
+            self.frontend.update(PeerView::from_peer(&self.peer, true));
          }
          ...
          PeerMessages::Bitfield(bitfield) => {
             self.peer.pieces = bitfield;
             self.determine_interest().await;
             self.notify_ready().await;
+            self.frontend.update(PeerView::from_peer(&self.peer, true));
          }
          PeerMessages::Handshake(_) => {
             warn!("Received unexpected handshake from peer");
          }
       }
-      self.frontend.update(PeerView::from_peer(&self.peer, true));
    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/libtortillas/src/peer/actor.rs` around lines 658 - 686, Move the
frontend update out of the unconditional post-match call in the peer message
handler and invoke it only in the state-changing arms: Choke, Unchoke,
Interested, NotInterested, Have, Piece, and Bitfield. Do not update the frontend
for KeepAlive, Request, Cancel, Extended, or unexpected Handshake messages.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/libtortillas/src/frontend/publisher.rs`:
- Around line 124-131: The weak FrontendHub upgrade in FrontendPublisher::hub
can panic when TorrentActor accesses the frontend after hub shutdown. Update
TorrentActor’s frontend handling to use an Option<FrontendPublisher>, matching
PeerHandle and TrackerHandle, and guard startup and linked-child failure
frontend calls when the publisher is unavailable; alternatively provide
TorrentActor a strongly owned frontend handle or subscription.

---

Outside diff comments:
In `@crates/libtortillas/src/peer/actor.rs`:
- Around line 658-686: Move the frontend update out of the unconditional
post-match call in the peer message handler and invoke it only in the
state-changing arms: Choke, Unchoke, Interested, NotInterested, Have, Piece, and
Bitfield. Do not update the frontend for KeepAlive, Request, Cancel, Extended,
or unexpected Handshake messages.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2c3bc4b5-9f92-4c6b-bec6-90fa5360b035

📥 Commits

Reviewing files that changed from the base of the PR and between 6e15184 and e7bc20d.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (33)
  • README.md
  • crates/libtortillas/Cargo.toml
  • crates/libtortillas/examples/live_frontend.rs
  • crates/libtortillas/src/ARCHITECTURE.md
  • crates/libtortillas/src/engine/messages.rs
  • crates/libtortillas/src/engine/mod.rs
  • crates/libtortillas/src/engine/snapshot.rs
  • crates/libtortillas/src/errors.rs
  • crates/libtortillas/src/facade.rs
  • crates/libtortillas/src/frontend/event.rs
  • crates/libtortillas/src/frontend/handle.rs
  • crates/libtortillas/src/frontend/listener.rs
  • crates/libtortillas/src/frontend/live.rs
  • crates/libtortillas/src/frontend/mod.rs
  • crates/libtortillas/src/frontend/publisher.rs
  • crates/libtortillas/src/frontend/subscription.rs
  • crates/libtortillas/src/frontend/view.rs
  • crates/libtortillas/src/lib.rs
  • crates/libtortillas/src/peer/actor.rs
  • crates/libtortillas/src/torrent/actor.rs
  • crates/libtortillas/src/torrent/handle.rs
  • crates/libtortillas/src/torrent/messages.rs
  • crates/libtortillas/src/torrent/mod.rs
  • crates/libtortillas/src/torrent/snapshot.rs
  • crates/libtortillas/src/torrent/storage.rs
  • crates/libtortillas/src/torrent/swarm.rs
  • crates/libtortillas/src/tracker/actor.rs
  • crates/libtortillas/src/tracker/model.rs
  • crates/libtortillas/tests/engine_lifecycle.rs
  • crates/libtortillas/tests/facade.rs
  • crates/libtortillas/tests/live_frontend.rs
  • crates/libtortillas/tests/persistence.rs
  • docs/frontend-integration.md
🚧 Files skipped from review as they are similar to previous changes (7)
  • crates/libtortillas/src/ARCHITECTURE.md
  • crates/libtortillas/examples/live_frontend.rs
  • crates/libtortillas/src/tracker/model.rs
  • crates/libtortillas/src/lib.rs
  • crates/libtortillas/tests/persistence.rs
  • README.md
  • crates/libtortillas/src/engine/messages.rs

Comment thread crates/libtortillas/src/frontend/publisher.rs Outdated
@artrixdotdev

Copy link
Copy Markdown
Owner Author

WARNING

this is one of the first fully vibe coded PRs on this repo, mostly a thought experiment

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (11)
crates/libtortillas/src/live/stream.rs (2)

76-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse EventSubscription::closed() here.

Lines 79-84 duplicate the private constructor defined at Lines 222-227.

♻️ Proposed cleanup
       let state = mutex_lock(&self.state);
       if state.closed {
-         return {
-            let (sender, receiver) = broadcast::channel(1);
-            let weak = sender.downgrade();
-            drop(sender);
-            EventSubscription::from_receiver(receiver, weak)
-         };
+         return EventSubscription::closed();
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/libtortillas/src/live/stream.rs` around lines 76 - 92, Update the
closed branch in subscribe to return the existing EventSubscription::closed()
constructor instead of recreating the broadcast channel, downgrade, and receiver
setup inline; leave the active subscription path unchanged.

129-172: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Publicly exported LivePublisher gives applications the write side of the live contract.

replace_view, emit_without_view_change, and close_with_terminal_event are pub and re-exported through facade.rs, so consumers can fabricate events or permanently close a stream they only obtained a handle to. If the intent is "observation only" for applications, consider making the mutation methods pub(crate) (or keeping the type crate-internal) and exposing only listeners/subscriptions publicly.

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

In `@crates/libtortillas/src/live/stream.rs` around lines 129 - 172, Restrict the
mutation methods on the publicly exported LivePublisher so applications cannot
alter or close live streams through an acquired handle. Change replace_view,
replace_view_and_emit, emit_without_view_change, and close_with_terminal_event
to crate-visible access (or make LivePublisher crate-internal), while preserving
public listener/subscription APIs and internal call sites.
crates/libtortillas/src/engine/messages.rs (1)

38-53: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename discard_restored_torrent — it now discards newly created torrents too.

GetLiveView failure (line 295-304) triggers this cleanup for CreateTorrentRequest::New just as much as for ::Restore. The name implies restore-only scope, which could mislead future edits that add restore-specific behavior here.

♻️ Suggested rename
-      async fn discard_restored_torrent(
+      async fn discard_created_torrent(
          &mut self, info_hash: InfoHash, torrent: &ActorRef<TorrentActor>,
       ) {

(and update the 4 call sites accordingly)

Also applies to: 238-258, 289-304

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

In `@crates/libtortillas/src/engine/messages.rs` around lines 38 - 53, Rename
EngineActor::discard_restored_torrent to a creation/restoration-neutral name
reflecting that it cleans up both newly created and restored torrents, then
update all four call sites, including the GetLiveView failure path for
CreateTorrentRequest::New. Preserve the existing unregister, graceful-stop, and
scope-removal behavior.
crates/libtortillas/src/torrent/actor.rs (2)

524-549: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Tracker wire bytes are folded into the torrent's content transfer metrics.

metric_sources chains tracker views into the same aggregate that produces metrics.traffic.totals/rates, so announce overhead shows up as torrent download/upload speed and totals in every frontend (the test at Line 1926 encodes this: 50_000 peer + 250 tracker). Consider aggregating peers only for TransferMetrics, and exposing tracker overhead separately if it's needed.

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

In `@crates/libtortillas/src/torrent/actor.rs` around lines 524 - 549, Update the
metrics aggregation in the torrent metrics construction to use only peer sources
for TransferMetrics totals and rates; remove trackers from the metric_sources
chain used by TransferRates::aggregate and the totals fold. Preserve tracker
metrics separately only if an existing dedicated field or API supports exposing
their overhead.

220-232: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Transient storage errors permanently wedge the torrent.

pre_start failures set TorrentState::Failed, and can_start() only accepts Added | ResolvingMetadata | Ready | Paused, so a recoverable cause (missing mount, transient permission error) leaves no way to retry start() after the user fixes it. Consider allowing recovery from Failed (e.g. via set_output_folder/set_piece_storage resetting to Paused) or documenting that Failed is final for the session.

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

In `@crates/libtortillas/src/torrent/actor.rs` around lines 220 - 232, Ensure
recoverable pre_start failures do not permanently block restarting the torrent:
update the failure recovery flow around TorrentState::Failed and can_start() so
changing storage configuration through set_output_folder or set_piece_storage
resets the torrent to Paused, or otherwise permits a subsequent start().
Preserve Failed for unrecoverable errors and keep the existing pre_start error
reporting.
crates/libtortillas/src/torrent/snapshot.rs (2)

182-202: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

block_map duplicate piece_index entries pass validation.

Nothing rejects two entries with the same piece_index; restore_snapshot then calls restore_piece_blocks twice for that index and the last one silently wins. A cheap uniqueness check here keeps the validated type an actual invariant.

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

In `@crates/libtortillas/src/torrent/snapshot.rs` around lines 182 - 202, The
block_map validation loop must reject duplicate piece_index values before
accepting the snapshot. Update the validation surrounding block_map to track
each validated index and return self.invalid(...) when an index appears more
than once, while preserving the existing range, bitfield, metadata, and
block-count checks.

318-342: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Inconsistent piece_length error handling between storage arms.

The Disk + FileMetadata arm swallows the error with unwrap_or(usize::MAX) (making every piece look invalid), while the InFile arm propagates it with ?. Since piece_length only fails for a structurally broken snapshot, propagate in both places so the caller gets the typed InvalidSnapshot rather than a silent full demotion.

♻️ Suggested change
                   RestoreVerification::FileMetadata => {
+                     let expected = u64::try_from(piece_length(&info, index)?).unwrap_or(u64::MAX);
                      fs::metadata(path).await.is_ok_and(|metadata| {
-                        metadata.len()
-                           >= u64::try_from(piece_length(&info, index).unwrap_or(usize::MAX))
-                              .unwrap_or(u64::MAX)
+                        metadata.len() >= expected
                      })
                   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/libtortillas/src/torrent/snapshot.rs` around lines 318 - 342, Update
the Disk branch’s RestoreVerification::FileMetadata handling to call
piece_length with ? instead of converting errors via unwrap_or and u64::try_from
fallbacks. Preserve the existing metadata length comparison while propagating
the typed InvalidSnapshot error consistently with the InFile branch.
crates/libtortillas/src/torrent/piece_flow.rs (1)

155-168: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

fill_peer_request_window isn't state-gated, unlike fill_all_peer_request_windows.

Callers such as peer_rejected_request (torrent/messages.rs Line 98) and piece_completed invoke it without checking state == Downloading && is_ready(), so a torrent that was just paused can still emit new block requests. Consider moving the guard into fill_peer_request_window_to so every path shares it.

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

In `@crates/libtortillas/src/torrent/piece_flow.rs` around lines 155 - 168, Add
the same state and readiness guard used by fill_all_peer_request_windows to
fill_peer_request_window_to, returning without issuing requests unless the
torrent is Downloading and is_ready(). Keep fill_peer_request_window and all
callers unchanged so every request-window refill path enforces the guard
centrally.
crates/libtortillas/src/protocol/stream.rs (1)

234-249: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

split() panics instead of surfacing a recoverable error.

The assertion turns a caller-sequencing mistake (any prior PeerRecv::recv() leaves bytes in read_buffer) into a task abort in library code. Either return Result<(PeerReader, PeerWriter), Self> or hand the buffered bytes to PeerReader so the split is always safe.

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

In `@crates/libtortillas/src/protocol/stream.rs` around lines 234 - 249, Update
PeerStream::split to avoid asserting on non-empty read_buffer and triggering a
panic. Make split recoverable by returning Result<(PeerReader, PeerWriter),
Self> and returning the original PeerStream when buffered data exists, or
transfer the buffered data into PeerReader so splitting remains safe; preserve
the existing transport-specific reader/writer construction for empty buffers.
crates/libtortillas/src/pieces/piece_manager.rs (1)

127-128: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Apply the same safe conversion to the sibling length casts in this function.

Good defensive fix for the multi-file file.length conversion, but piece_to_paths still has two raw as usize casts of the same risk class: the single-file branch's let file_len = *length as usize; (line 110) and let piece_len = info.piece_length as usize; (line 86). On platforms where usize is narrower than the source type, these would silently truncate rather than error, producing an incorrect piece-to-file mapping instead of a clear failure.

♻️ Suggested consistency fix
-      let piece_len = info.piece_length as usize;
+      let piece_len = usize::try_from(info.piece_length)
+         .context("piece length cannot be represented on this platform")?;
...
-            let file_len = *length as usize;
+            let file_len = usize::try_from(*length)
+               .context("file length cannot be represented on this platform")?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/libtortillas/src/pieces/piece_manager.rs` around lines 127 - 128, In
piece_to_paths, replace the raw usize casts for info.piece_length and the
single-file length with checked usize::try_from conversions, adding clear
context to each fallible conversion and propagating errors consistently with the
existing file.length conversion. Preserve the existing piece-to-file mapping
behavior when values fit.
crates/libtortillas/src/torrent/swarm.rs (1)

170-179: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Publish Updated once per broadcast_to_peers call, not per removed peer.

The live-view publish now runs inside the dead_peers loop, so a single call can emit N redundant Updated events if several peers die together (e.g. a network blip). This churns the bounded broadcast channel and can trigger avoidable Lagged conditions for slow listeners, contrary to the "coalesced aggregate" design used elsewhere in this file.

♻️ Proposed fix
+      let had_dead_peers = !dead_peers.is_empty();
       for id in dead_peers {
          self.peers.remove(&id);
-         self.publish_live_view(|_| crate::live::TorrentEventKind::Updated);
       }
+      if had_dead_peers {
+         self.publish_live_view(|_| crate::live::TorrentEventKind::Updated);
+      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/libtortillas/src/torrent/swarm.rs` around lines 170 - 179, Update the
dead-peer cleanup in broadcast_to_peers so publish_live_view emits a single
Updated event after all dead peers have been removed, rather than once inside
the removal loop. Preserve cleanup for every entry in dead_peers and avoid
publishing when no peers were removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/libtortillas/src/engine/actor.rs`:
- Line 21: Update EngineActor::on_start to publish a terminal failed engine
status through Hub before returning any TCP/uTP/UDP bind or optional DHT spawn
error. Reuse the existing Hub/LiveHealthLevel status-reporting API and preserve
the normal engine_started path on successful startup, ensuring failed startup
transitions consumers out of Starting even though on_stop is not invoked.

In `@crates/libtortillas/src/engine/messages.rs`:
- Around line 295-310: The restored-torrent initialization around GetLiveView
and Torrent::new_with_hub must avoid overwriting a newer view published between
the ask and registration. Change the initialization flow to atomically install
the fetched initial_view only when the torrent scope has no current live view,
or use an equivalent latest-writer-safe initialization mechanism before
register_torrent_scope re-emits the scope state.

In `@crates/libtortillas/src/live/handle.rs`:
- Around line 310-330: Ensure stopped tracker scopes are removed from the hub
before they can be re-registered: add and invoke a tracker-specific hub cleanup
operation from PeerHandle::stopped and close_without_parent_event, before
emitting any parent event, or otherwise make Hub::register_tracker_scope replace
an existing scope whose publisher is closed. Preserve current terminal-event
behavior while ensuring subsequent registration receives an active publisher.

In `@crates/libtortillas/src/live/hub.rs`:
- Around line 335-344: Update replace_torrent_view_and_emit so it applies the
new torrent view to the existing scope before checking torrent_handle. If no
handle is registered, return only after the scope view has been replaced;
otherwise continue with the existing engine fan-out and event emission path.
- Around line 239-246: Make HubReference::inner fallible instead of panicking
when a weak hub cannot be upgraded, returning None for an expired hub. Update
all publication paths that call inner() to return early when no HubInner is
available, matching LiveScope::hub()’s no-op behavior during shutdown while
preserving strong-reference behavior.

In `@crates/libtortillas/src/torrent/actor.rs`:
- Around line 871-876: Update on_link_died so the health event is emitted at
LiveHealthLevel::Error only for unexpected child termination; inspect the linked
child’s reason and suppress the health error for normal shutdown causes such as
idle disconnects or remote closes, while preserving the existing error log.

In `@crates/libtortillas/src/torrent/messages.rs`:
- Around line 195-216: Update set_piece_storage to reject switching away from
PieceStorageStrategy::Disk when a custom piece manager is installed, matching
the guard in set_piece_manager. Return the existing invalid-operation error
before creating directories or mutating piece_storage, while preserving normal
storage changes when no custom manager is active.

In `@crates/libtortillas/tests/dht_network.rs`:
- Line 52: Update the polling logic around listener.recv() to distinguish
EventStreamError::Closed from lag and timeout outcomes. Preserve continued
polling for lag or timeout results, but fail the test immediately when the
torrent event stream closes instead of discarding the result and waiting for
DOWNLOAD_TIMEOUT.

In `@crates/libtortillas/tests/persistence.rs`:
- Around line 447-460: Update
engine_snapshot_golden_fixtures_migrate_and_round_trip so the serialized
migrated snapshot is compared directly with the expected value parsed from
ENGINE_SNAPSHOT_V2. Remove the separately parsed current snapshot and serialize
migrated after validating its version, ensuring the test verifies the v1-to-v2
migration output.

---

Nitpick comments:
In `@crates/libtortillas/src/engine/messages.rs`:
- Around line 38-53: Rename EngineActor::discard_restored_torrent to a
creation/restoration-neutral name reflecting that it cleans up both newly
created and restored torrents, then update all four call sites, including the
GetLiveView failure path for CreateTorrentRequest::New. Preserve the existing
unregister, graceful-stop, and scope-removal behavior.

In `@crates/libtortillas/src/live/stream.rs`:
- Around line 76-92: Update the closed branch in subscribe to return the
existing EventSubscription::closed() constructor instead of recreating the
broadcast channel, downgrade, and receiver setup inline; leave the active
subscription path unchanged.
- Around line 129-172: Restrict the mutation methods on the publicly exported
LivePublisher so applications cannot alter or close live streams through an
acquired handle. Change replace_view, replace_view_and_emit,
emit_without_view_change, and close_with_terminal_event to crate-visible access
(or make LivePublisher crate-internal), while preserving public
listener/subscription APIs and internal call sites.

In `@crates/libtortillas/src/pieces/piece_manager.rs`:
- Around line 127-128: In piece_to_paths, replace the raw usize casts for
info.piece_length and the single-file length with checked usize::try_from
conversions, adding clear context to each fallible conversion and propagating
errors consistently with the existing file.length conversion. Preserve the
existing piece-to-file mapping behavior when values fit.

In `@crates/libtortillas/src/protocol/stream.rs`:
- Around line 234-249: Update PeerStream::split to avoid asserting on non-empty
read_buffer and triggering a panic. Make split recoverable by returning
Result<(PeerReader, PeerWriter), Self> and returning the original PeerStream
when buffered data exists, or transfer the buffered data into PeerReader so
splitting remains safe; preserve the existing transport-specific reader/writer
construction for empty buffers.

In `@crates/libtortillas/src/torrent/actor.rs`:
- Around line 524-549: Update the metrics aggregation in the torrent metrics
construction to use only peer sources for TransferMetrics totals and rates;
remove trackers from the metric_sources chain used by TransferRates::aggregate
and the totals fold. Preserve tracker metrics separately only if an existing
dedicated field or API supports exposing their overhead.
- Around line 220-232: Ensure recoverable pre_start failures do not permanently
block restarting the torrent: update the failure recovery flow around
TorrentState::Failed and can_start() so changing storage configuration through
set_output_folder or set_piece_storage resets the torrent to Paused, or
otherwise permits a subsequent start(). Preserve Failed for unrecoverable errors
and keep the existing pre_start error reporting.

In `@crates/libtortillas/src/torrent/piece_flow.rs`:
- Around line 155-168: Add the same state and readiness guard used by
fill_all_peer_request_windows to fill_peer_request_window_to, returning without
issuing requests unless the torrent is Downloading and is_ready(). Keep
fill_peer_request_window and all callers unchanged so every request-window
refill path enforces the guard centrally.

In `@crates/libtortillas/src/torrent/snapshot.rs`:
- Around line 182-202: The block_map validation loop must reject duplicate
piece_index values before accepting the snapshot. Update the validation
surrounding block_map to track each validated index and return self.invalid(...)
when an index appears more than once, while preserving the existing range,
bitfield, metadata, and block-count checks.
- Around line 318-342: Update the Disk branch’s
RestoreVerification::FileMetadata handling to call piece_length with ? instead
of converting errors via unwrap_or and u64::try_from fallbacks. Preserve the
existing metadata length comparison while propagating the typed InvalidSnapshot
error consistently with the InFile branch.

In `@crates/libtortillas/src/torrent/swarm.rs`:
- Around line 170-179: Update the dead-peer cleanup in broadcast_to_peers so
publish_live_view emits a single Updated event after all dead peers have been
removed, rather than once inside the removal loop. Preserve cleanup for every
entry in dead_peers and avoid publishing when no peers were removed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 67619d3d-e988-47d5-8c32-fa72c7c0e522

📥 Commits

Reviewing files that changed from the base of the PR and between e7bc20d and f2e4950.

📒 Files selected for processing (51)
  • README.md
  • crates/libtortillas/examples/live.rs
  • crates/libtortillas/src/ARCHITECTURE.md
  • crates/libtortillas/src/engine/actor.rs
  • crates/libtortillas/src/engine/messages.rs
  • crates/libtortillas/src/engine/mod.rs
  • crates/libtortillas/src/engine/snapshot.rs
  • crates/libtortillas/src/engine/source.rs
  • crates/libtortillas/src/errors.rs
  • crates/libtortillas/src/facade.rs
  • crates/libtortillas/src/lib.rs
  • crates/libtortillas/src/live/event.rs
  • crates/libtortillas/src/live/handle.rs
  • crates/libtortillas/src/live/hub.rs
  • crates/libtortillas/src/live/mod.rs
  • crates/libtortillas/src/live/stream.rs
  • crates/libtortillas/src/live/view.rs
  • crates/libtortillas/src/metainfo/file.rs
  • crates/libtortillas/src/metrics.rs
  • crates/libtortillas/src/peer/actor.rs
  • crates/libtortillas/src/peer/mod.rs
  • crates/libtortillas/src/peer/state.rs
  • crates/libtortillas/src/pieces/piece_manager.rs
  • crates/libtortillas/src/pieces/piece_scheduler.rs
  • crates/libtortillas/src/protocol/messages.rs
  • crates/libtortillas/src/protocol/stream.rs
  • crates/libtortillas/src/settings.rs
  • crates/libtortillas/src/torrent/actor.rs
  • crates/libtortillas/src/torrent/choking.rs
  • crates/libtortillas/src/torrent/choking_flow.rs
  • crates/libtortillas/src/torrent/handle.rs
  • crates/libtortillas/src/torrent/messages.rs
  • crates/libtortillas/src/torrent/mod.rs
  • crates/libtortillas/src/torrent/piece_flow.rs
  • crates/libtortillas/src/torrent/snapshot.rs
  • crates/libtortillas/src/torrent/state.rs
  • crates/libtortillas/src/torrent/swarm.rs
  • crates/libtortillas/src/tracker/actor.rs
  • crates/libtortillas/src/tracker/http.rs
  • crates/libtortillas/src/tracker/model.rs
  • crates/libtortillas/src/tracker/stats.rs
  • crates/libtortillas/src/tracker/udp.rs
  • crates/libtortillas/tests/dht_network.rs
  • crates/libtortillas/tests/engine_lifecycle.rs
  • crates/libtortillas/tests/facade.rs
  • crates/libtortillas/tests/fixtures/engine-snapshot-v1.json
  • crates/libtortillas/tests/fixtures/engine-snapshot-v2.json
  • crates/libtortillas/tests/fixtures/torrent-snapshot-v1.json
  • crates/libtortillas/tests/fixtures/torrent-snapshot-v2.json
  • crates/libtortillas/tests/live.rs
  • crates/libtortillas/tests/persistence.rs
💤 Files with no reviewable changes (1)
  • crates/libtortillas/src/ARCHITECTURE.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/libtortillas/tests/facade.rs
  • crates/libtortillas/src/tracker/model.rs

Comment thread crates/libtortillas/src/engine/actor.rs Outdated
Comment thread crates/libtortillas/src/engine/messages.rs Outdated
Comment thread crates/libtortillas/src/live/handle.rs
Comment thread crates/libtortillas/src/live/hub.rs Outdated
Comment thread crates/libtortillas/src/live/hub.rs
Comment thread crates/libtortillas/src/torrent/actor.rs Outdated
Comment thread crates/libtortillas/src/torrent/messages.rs
Comment thread crates/libtortillas/tests/dht_network.rs Outdated
Comment thread crates/libtortillas/tests/persistence.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 (2)
crates/libtortillas/src/settings.rs (1)

159-161: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Document that stale request expiry is ticked on rechoke.

release_stale_requests only runs inside rechoke_peers, and rechoke is scheduled at rechoke_interval (default 10s). Together with the 15s peer_request_timeout, an unanswered request can remain unassignable for up to nearly 20s. Either expire requests independently or update the docs/test to clarify this polling granularity.

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

In `@crates/libtortillas/src/settings.rs` around lines 159 - 161, Update the
documentation for peer_request_timeout to state that stale requests are expired
during rechoke_peers polling, which runs at rechoke_interval, so effective
reassignment may be delayed beyond the configured timeout. Add or adjust the
relevant test to validate this documented polling granularity.
crates/libtortillas/src/torrent/choking.rs (1)

141-178: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Gate the choking tests behind feature = "live" or provide legacy PeerStats helpers.

PeerStats has a live metrics-based shape and a legacy {interested, choked, download_rate, upload_rate} shape, while this module still constructs the live shape unconditionally. Without --features live, this test module and its callsites fail to compile.

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

In `@crates/libtortillas/src/torrent/choking.rs` around lines 141 - 178, Make the
choking test helpers stats and with_rates compatible with the non-live build by
gating the relevant tests behind feature = "live" or providing equivalent legacy
PeerStats construction for builds without that feature. Ensure all helper
callsites, including selector_only_includes_interested_peers, compile under both
configurations while preserving their existing test behavior.
🧹 Nitpick comments (1)
crates/libtortillas/src/engine/mod.rs (1)

314-323: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Three copies of the same let _ = &torrent_ref; unused-binding workaround.

Consider binding the ask result as _torrent_ref (or gating the whole let with #[cfg(not(feature = "live"))]) so the suppression line disappears in all three methods.

Also applies to: 358-367, 434-443

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

In `@crates/libtortillas/src/engine/mod.rs` around lines 314 - 323, Update the
three affected methods to bind the ask result as _torrent_ref, or conditionally
bind torrent_ref only for non-live builds, so the repeated let _ = &torrent_ref
workaround is removed. Preserve the existing live torrent_handle and non-live
Torrent::new behavior in each method.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/libtortillas/src/live/hub.rs`:
- Around line 565-580: Update mark_peer_disconnected to remove the peer via
ScopeRegistry::remove_value using handle pointer identity, so a stale PeerHandle
cannot remove a replacement registered under the same PeerId. Also update the
membership check in emit_peer_connected to use the same ptr-identity value
check, preserving events and live membership for the currently registered
handle.

In `@crates/libtortillas/src/pieces/piece_manager.rs`:
- Around line 86-87: The scheduler currently uses an unchecked
`Info::piece_length` conversion before request generation, while `PieceManager`
validates it later. Convert `piece_length` with a checked `usize::try_from` at
the `PieceScheduler` boundary, propagate that validated value through
`requests_for_peer` and the `piece_flow` request-building path, and reuse it for
storage validation instead of recasting the original value.

---

Outside diff comments:
In `@crates/libtortillas/src/settings.rs`:
- Around line 159-161: Update the documentation for peer_request_timeout to
state that stale requests are expired during rechoke_peers polling, which runs
at rechoke_interval, so effective reassignment may be delayed beyond the
configured timeout. Add or adjust the relevant test to validate this documented
polling granularity.

In `@crates/libtortillas/src/torrent/choking.rs`:
- Around line 141-178: Make the choking test helpers stats and with_rates
compatible with the non-live build by gating the relevant tests behind feature =
"live" or providing equivalent legacy PeerStats construction for builds without
that feature. Ensure all helper callsites, including
selector_only_includes_interested_peers, compile under both configurations while
preserving their existing test behavior.

---

Nitpick comments:
In `@crates/libtortillas/src/engine/mod.rs`:
- Around line 314-323: Update the three affected methods to bind the ask result
as _torrent_ref, or conditionally bind torrent_ref only for non-live builds, so
the repeated let _ = &torrent_ref workaround is removed. Preserve the existing
live torrent_handle and non-live Torrent::new behavior in each method.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d6b8050e-e921-4096-8643-c88c0f7194ce

📥 Commits

Reviewing files that changed from the base of the PR and between f2e4950 and 99d2538.

📒 Files selected for processing (37)
  • .github/workflows/checks.yml
  • README.md
  • crates/libtortillas/Cargo.toml
  • crates/libtortillas/src/engine/actor.rs
  • crates/libtortillas/src/engine/messages.rs
  • crates/libtortillas/src/engine/mod.rs
  • crates/libtortillas/src/engine/snapshot.rs
  • crates/libtortillas/src/facade.rs
  • crates/libtortillas/src/lib.rs
  • crates/libtortillas/src/live/event.rs
  • crates/libtortillas/src/live/handle.rs
  • crates/libtortillas/src/live/hub.rs
  • crates/libtortillas/src/live/mod.rs
  • crates/libtortillas/src/live/stream.rs
  • crates/libtortillas/src/live/view.rs
  • crates/libtortillas/src/metrics.rs
  • crates/libtortillas/src/peer/actor.rs
  • crates/libtortillas/src/peer/state.rs
  • crates/libtortillas/src/pieces/piece_manager.rs
  • crates/libtortillas/src/protocol/stream.rs
  • crates/libtortillas/src/settings.rs
  • crates/libtortillas/src/torrent/actor.rs
  • crates/libtortillas/src/torrent/choking.rs
  • crates/libtortillas/src/torrent/choking_flow.rs
  • crates/libtortillas/src/torrent/handle.rs
  • crates/libtortillas/src/torrent/messages.rs
  • crates/libtortillas/src/torrent/mod.rs
  • crates/libtortillas/src/torrent/piece_flow.rs
  • crates/libtortillas/src/torrent/snapshot.rs
  • crates/libtortillas/src/torrent/swarm.rs
  • crates/libtortillas/src/tracker/actor.rs
  • crates/libtortillas/src/tracker/model.rs
  • crates/libtortillas/src/tracker/stats.rs
  • crates/libtortillas/tests/dht_network.rs
  • crates/libtortillas/tests/facade.rs
  • crates/libtortillas/tests/live.rs
  • crates/libtortillas/tests/persistence.rs
🚧 Files skipped from review as they are similar to previous changes (26)
  • crates/libtortillas/src/torrent/mod.rs
  • crates/libtortillas/src/lib.rs
  • crates/libtortillas/src/live/event.rs
  • crates/libtortillas/tests/dht_network.rs
  • crates/libtortillas/src/engine/actor.rs
  • crates/libtortillas/src/tracker/model.rs
  • README.md
  • crates/libtortillas/src/facade.rs
  • crates/libtortillas/src/torrent/choking_flow.rs
  • crates/libtortillas/src/live/mod.rs
  • crates/libtortillas/src/live/handle.rs
  • crates/libtortillas/src/engine/snapshot.rs
  • crates/libtortillas/src/protocol/stream.rs
  • crates/libtortillas/src/live/stream.rs
  • crates/libtortillas/src/tracker/actor.rs
  • crates/libtortillas/src/torrent/swarm.rs
  • crates/libtortillas/src/engine/messages.rs
  • crates/libtortillas/tests/persistence.rs
  • crates/libtortillas/src/torrent/snapshot.rs
  • crates/libtortillas/src/torrent/messages.rs
  • crates/libtortillas/src/live/view.rs
  • crates/libtortillas/src/peer/actor.rs
  • crates/libtortillas/src/peer/state.rs
  • crates/libtortillas/src/torrent/handle.rs
  • crates/libtortillas/src/torrent/piece_flow.rs
  • crates/libtortillas/src/torrent/actor.rs

Comment on lines +565 to +580
pub(crate) fn mark_peer_disconnected(&self, peer: &PeerHandle) {
let Some(inner) = self.inner() else {
return;
};
let Some(scope) = inner.torrents.get(&peer.torrent()) else {
return;
};
if scope.peers.remove(&peer.id()).is_none() {
return;
}
Self::emit_without_torrent_view_change(
&inner,
&scope,
TorrentEventKind::PeerDisconnected(peer.clone()),
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Peer removal keys on PeerId, so a stale handle can evict its replacement.

If the same PeerId reconnects (new PeerHandle inserted over the old key in register_peer_scope) and the old handle's disconnected() fires afterwards, scope.peers.remove(&peer.id()) drops the new registration and emits PeerDisconnected for the stale handle — the live view then loses an actually-connected peer. ScopeRegistry::remove_value already provides ptr-identity removal and is used for trackers; the same treatment fits here (and for the membership check in emit_peer_connected).

🔧 Suggested change
-      if scope.peers.remove(&peer.id()).is_none() {
+      if !scope.peers.remove_value(&peer.inner) {
          return;
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub(crate) fn mark_peer_disconnected(&self, peer: &PeerHandle) {
let Some(inner) = self.inner() else {
return;
};
let Some(scope) = inner.torrents.get(&peer.torrent()) else {
return;
};
if scope.peers.remove(&peer.id()).is_none() {
return;
}
Self::emit_without_torrent_view_change(
&inner,
&scope,
TorrentEventKind::PeerDisconnected(peer.clone()),
);
}
pub(crate) fn mark_peer_disconnected(&self, peer: &PeerHandle) {
let Some(inner) = self.inner() else {
return;
};
let Some(scope) = inner.torrents.get(&peer.torrent()) else {
return;
};
if !scope.peers.remove_value(&peer.inner) {
return;
}
Self::emit_without_torrent_view_change(
&inner,
&scope,
TorrentEventKind::PeerDisconnected(peer.clone()),
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/libtortillas/src/live/hub.rs` around lines 565 - 580, Update
mark_peer_disconnected to remove the peer via ScopeRegistry::remove_value using
handle pointer identity, so a stale PeerHandle cannot remove a replacement
registered under the same PeerId. Also update the membership check in
emit_peer_connected to use the same ptr-identity value check, preserving events
and live membership for the currently registered handle.

Comment on lines +86 to +87
let piece_len = usize::try_from(info.piece_length)
.context("piece length cannot be represented on this platform")?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C3 'piece_length\s+as\s+usize|file\.length\s+as\s+usize|as\s+usize' \
  crates/libtortillas/src/torrent/piece_flow.rs \
  crates/libtortillas/src/pieces

Repository: artrixdotdev/tortillas

Length of output: 1280


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== outline piece_flow.rs =="
ast-grep outline crates/libtortillas/src/torrent/piece_flow.rs --view expanded | sed -n '1,220p' || true

echo "== outline piece_manager.rs =="
ast-grep outline crates/libtortillas/src/pieces/piece_manager.rs --view expanded | sed -n '1,260p' || true

echo "== relevant piece_flow slices =="
sed -n '1,80p' crates/libtortillas/src/torrent/piece_flow.rs
sed -n '150,230p' crates/libtortillas/src/torrent/piece_flow.rs

echo "== relevant piece_manager slices =="
sed -n '60,110p' crates/libtortillas/src/pieces/piece_manager.rs

echo "== request scheduling references =="
rg -n 'requests_for_peer|piece_to_paths|piece_length|total_length|piece_count|Path' crates/libtortillas/src/torrent crates/libtortillas/src/pieces

Repository: artrixdotdev/tortillas

Length of output: 28883


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
# Behavioral probe for integer coercion semantics under the relevant casts:
# piece_length: u32 -> usize (platform usize)
for wasm32_usize_bits in (32,):
    for piece_length_bits in (32, 40, 60, 64):
        piece_length = 1 << (piece_length_bits - 1)
        usize_value = piece_length & ((1 << wasm32_usize_bits) - 1)
        checked = piece_length if piece_length < (1 << wasm32_usize_bits) else "overflow"
        print(f"wasm32 piece_length_bits={piece_length_bits}: original={piece_length:`#x`} cast_usize={usize_value:`#x`} checked={checked}")
print("on_u8_target")
u8_value = 256 & 255
checked = "overflow"
print(f"u8=256: original=256 cast_u8={u8_value} checked={checked}")
PY

Repository: artrixdotdev/tortillas

Length of output: 571


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== piece_scheduler.rs outline and length math =="
sed -n '100,260p' crates/libtortillas/src/pieces/piece_scheduler.rs

echo "== metainfo info types =="
rg -n -C4 'pub struct Info|struct Info|piece_length|total_length|piece_count|file:' crates/libtortillas/src -g '*.rs'

Repository: artrixdotdev/tortillas

Length of output: 50378


Validate piece_length before scheduling requests.

file_length casts Info::piece_length to usize at crates/libtortillas/src/torrent/piece_flow.rs:190, which truncates values that do not fit on 32-bit targets. PieceScheduler::requests_for_peer then builds block ranges from that width, while storage validation rejects the overflow later in piece_manager.rs:86. Convert once at the scheduler boundary and propagate the checked value into request generation/storage validation.

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

In `@crates/libtortillas/src/pieces/piece_manager.rs` around lines 86 - 87, The
scheduler currently uses an unchecked `Info::piece_length` conversion before
request generation, while `PieceManager` validates it later. Convert
`piece_length` with a checked `usize::try_from` at the `PieceScheduler`
boundary, propagate that validated value through `requests_for_peer` and the
`piece_flow` request-building path, and reuse it for storage validation instead
of recasting the original value.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
crates/libtortillas/src/torrent/choking.rs (1)

69-73: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid cloning full PeerStats during selection.

cloned() copies each interested peer, including live transfer sample history, on every rechoke. Collect borrowed references (or a lightweight (PeerId, rate) projection) instead; the selector only sorts peers and extracts IDs.

Suggested change
   let mut candidates: Vec<_> = peers
      .iter()
      .filter(|peer| peer.interested)
-     .cloned()
      .collect();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/libtortillas/src/torrent/choking.rs` around lines 69 - 73, Update the
candidate collection in the rechoke selection flow to avoid cloning full
PeerStats: collect references to interested peers, or project only the PeerId
and rate fields needed by the sorting and ID-extraction logic. Preserve the
existing filtering, ordering, and selection behavior while removing the per-peer
cloned() allocation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/libtortillas/src/torrent/choking.rs`:
- Around line 69-73: Update the candidate collection in the rechoke selection
flow to avoid cloning full PeerStats: collect references to interested peers, or
project only the PeerId and rate fields needed by the sorting and ID-extraction
logic. Preserve the existing filtering, ordering, and selection behavior while
removing the per-peer cloned() allocation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 974c4d2e-50a0-4be5-91ce-367f4c628f11

📥 Commits

Reviewing files that changed from the base of the PR and between 99d2538 and ee773aa.

📒 Files selected for processing (25)
  • crates/libtortillas/src/engine/actor.rs
  • crates/libtortillas/src/engine/messages.rs
  • crates/libtortillas/src/engine/mod.rs
  • crates/libtortillas/src/facade.rs
  • crates/libtortillas/src/lib.rs
  • crates/libtortillas/src/live/event.rs
  • crates/libtortillas/src/live/mod.rs
  • crates/libtortillas/src/live/stream.rs
  • crates/libtortillas/src/live/view.rs
  • crates/libtortillas/src/peer/actor.rs
  • crates/libtortillas/src/peer/state.rs
  • crates/libtortillas/src/pieces/piece_scheduler.rs
  • crates/libtortillas/src/protocol/stream.rs
  • crates/libtortillas/src/torrent/actor.rs
  • crates/libtortillas/src/torrent/choking.rs
  • crates/libtortillas/src/torrent/choking_flow.rs
  • crates/libtortillas/src/torrent/handle.rs
  • crates/libtortillas/src/torrent/messages.rs
  • crates/libtortillas/src/torrent/mod.rs
  • crates/libtortillas/src/torrent/piece_flow.rs
  • crates/libtortillas/src/torrent/swarm.rs
  • crates/libtortillas/src/tracker/actor.rs
  • crates/libtortillas/src/tracker/http.rs
  • crates/libtortillas/src/tracker/model.rs
  • crates/libtortillas/src/tracker/stats.rs
💤 Files with no reviewable changes (1)
  • crates/libtortillas/src/live/event.rs
🚧 Files skipped from review as they are similar to previous changes (23)
  • crates/libtortillas/src/tracker/model.rs
  • crates/libtortillas/src/torrent/choking_flow.rs
  • crates/libtortillas/src/tracker/http.rs
  • crates/libtortillas/src/engine/actor.rs
  • crates/libtortillas/src/torrent/mod.rs
  • crates/libtortillas/src/live/view.rs
  • crates/libtortillas/src/live/mod.rs
  • crates/libtortillas/src/lib.rs
  • crates/libtortillas/src/torrent/handle.rs
  • crates/libtortillas/src/torrent/swarm.rs
  • crates/libtortillas/src/pieces/piece_scheduler.rs
  • crates/libtortillas/src/tracker/actor.rs
  • crates/libtortillas/src/live/stream.rs
  • crates/libtortillas/src/peer/actor.rs
  • crates/libtortillas/src/protocol/stream.rs
  • crates/libtortillas/src/engine/messages.rs
  • crates/libtortillas/src/peer/state.rs
  • crates/libtortillas/src/tracker/stats.rs
  • crates/libtortillas/src/torrent/messages.rs
  • crates/libtortillas/src/torrent/piece_flow.rs
  • crates/libtortillas/src/engine/mod.rs
  • crates/libtortillas/src/torrent/actor.rs
  • crates/libtortillas/src/facade.rs

@artrixdotdev

Copy link
Copy Markdown
Owner Author

Fully reviewed all the code, was pretty in the loop for this so i dont feel to bad. Looks good though

@artrixdotdev
artrixdotdev merged commit 5df24ca into main Jul 27, 2026
3 checks passed
@artrixdotdev
artrixdotdev deleted the feat/add-live-frontend-api branch July 27, 2026 18:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request high prio GET DONE ASAP!! refactor Neither fixes a bug nor adds a feature

Projects

None yet

1 participant