Skip to content

refactor: project restructuring#192

Open
artrixdotdev wants to merge 45 commits into
mainfrom
refactor/file-restructuring
Open

refactor: project restructuring#192
artrixdotdev wants to merge 45 commits into
mainfrom
refactor/file-restructuring

Conversation

@artrixdotdev
Copy link
Copy Markdown
Owner

@artrixdotdev artrixdotdev commented May 21, 2026

Summary by CodeRabbit

  • New Features

    • Added Torrent handle with lifecycle control methods including start(), state(), and export()
    • Added configurable piece storage strategies (in-file or disk-based caching)
    • Introduced piece scheduling system for optimized block request management
  • Bug Fixes

    • Improved error handling across peer connections, torrent operations, and tracker communication—failures now propagate gracefully instead of panicking
  • API Changes

    • Engine::start_all() and Engine::export() now return Result types for better error visibility

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Walkthrough

This PR restructures the libtortillas library's error handling and architecture. It replaces panic paths across actor implementations with error-aware control flow; reorganizes the monolithic torrent module into focused submodules with a new public Torrent handle; introduces a complete tracker actor system with model layer and statistics; and implements piece-level download infrastructure with scheduler and persistent storage.

Changes

Refactored Actor Error Handling, Torrent Module Restructuring, and Tracker Implementation

Layer / File(s) Summary
Architecture documentation
crates/libtortillas/src/ARCHITECTURE.md
Introduces documentation describing the actor hierarchy, each actor's responsibilities, and module organization conventions.
Engine actor error hardening
crates/libtortillas/src/engine/messages.rs, crates/libtortillas/src/engine/mod.rs
Replaces panic/expect paths with non-panicking error handling: peer handshake reading wrapped in 10s timeout with warning logs; start_all() and export() converted to return Result with actor communication failures mapped to EngineError.
Peer actor error handling and block requests
crates/libtortillas/src/peer/actor.rs
Converts pending_block_requests from shared Arc<DashSet> to local HashSet; replaces unwrap/expect paths with error-aware control flow for handshake, Bitfield retrieval, keepalive, disconnect, and piece handling; adds notify_ready and reject_piece_request helpers.
Torrent core types and storage
crates/libtortillas/src/torrent/block.rs, crates/libtortillas/src/torrent/state.rs, crates/libtortillas/src/torrent/storage.rs, crates/libtortillas/src/torrent/export.rs
Introduces BlockMap/BLOCK_SIZE, TorrentState lifecycle enum, PieceStorageStrategy with InFile/Disk variants, and TorrentExport serializable snapshot.
Piece infrastructure
crates/libtortillas/src/pieces/mod.rs, crates/libtortillas/src/pieces/piece_scheduler.rs, crates/libtortillas/src/pieces/piece_store.rs, crates/libtortillas/src/pieces/piece_manager.rs
Adds StreamedPiece struct, PieceScheduler for block tracking and peer-specific request generation, PieceStoreActor for block I/O, and updates FilePieceManager with error propagation.
Torrent handle and message contracts
crates/libtortillas/src/torrent/handle.rs, crates/libtortillas/src/torrent/messages.rs
Introduces public Torrent handle with async configuration and query methods; updates message contracts adding PeerId to IncomingPiece, PeerRejectedRequest variant, and implements TorrentRequest handlers.
Torrent actor internals and piece flow
crates/libtortillas/src/torrent/actor.rs, crates/libtortillas/src/torrent/piece_flow.rs, crates/libtortillas/src/torrent/swarm.rs
Refactors TorrentActor to use owned collections instead of Arc<DashMap>; implements complete piece download flow (receive, validate, persist, complete, dispatch); adds peer connection setup and broadcast helpers.
Torrent module reorganization
crates/libtortillas/src/torrent/mod.rs
Restructures from monolithic file into module barrel declaring submodules and re-exporting public surface.
Tracker model and telemetry
crates/libtortillas/src/tracker/model.rs, crates/libtortillas/src/tracker/stats.rs
Introduces Tracker enum, TrackerBase trait, TrackerInstance wrapper, and TrackerStats with atomic counters and timing.
Tracker actor and lifecycle
crates/libtortillas/src/tracker/actor.rs, crates/libtortillas/src/tracker/messages.rs
Implements TrackerActor managing announce scheduling and supervisor communication; introduces TrackerMessage enum.
Tracker integration and utilities
crates/libtortillas/src/tracker/mod.rs, crates/libtortillas/src/tracker/http.rs, crates/libtortillas/src/tracker/udp.rs, crates/libtortillas/src/protocol/stream.rs, crates/libtortillas/src/torrent/util.rs, crates/libtortillas/src/lib.rs
Refactors tracker module into barrel; updates import paths; adds error propagation to PeerStream; updates documentation markers; simplifies lib.rs fixture imports.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • artrixdotdev/tortillas#151: The main PR's torrent piece completion flow (torrent/piece_flow.rs) dispatching validated blocks to a piece_manager directly builds on the PieceManager trait introduced in that PR.
  • artrixdotdev/tortillas#106: The main PR's torrent/handle.rs introducing public Torrent handle is related to the effort to expose Torrent public methods.
  • artrixdotdev/tortillas#134: The main PR's refactoring of PieceStorageStrategy::Disk piece I/O in torrent/piece_flow.rs and torrent/util.rs builds directly on the initial disk piece storage implementation.

Suggested reviewers

  • kurealnum
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'refactor: project restructuring' is vague and generic, using non-descriptive terms that don't convey the specific nature or scope of the changes made in this substantial refactor. Consider a more specific title such as 'refactor: restructure torrent/piece handling and extract swarm/scheduler logic' to better communicate the primary changes to reviewers.
✅ Passed checks (4 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%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/file-restructuring

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.

@coderabbitai coderabbitai Bot added med prio Medium Priority refactor Neither fixes a bug nor adds a feature labels May 21, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

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

513-520: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not apply peer Cancel to our download request tracking.

Line 518–520 removes entries from pending_block_requests, but that set tracks requests we sent to the peer. A remote Cancel is for requests they sent to us, so this can incorrectly discard our in-flight download bookkeeping.

Suggested fix
- self
-    .pending_block_requests
-    .remove(&(index as usize, offset as usize, length as usize));
+ trace!(
+    piece_index = index,
+    offset,
+    length,
+    "Ignoring peer cancel: no outbound upload queue tracking yet"
+ );

If upload-side cancellation is needed, track it in a separate upload request queue/state.

🤖 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 513 - 520, The
PeerMessages::Cancel match arm is incorrectly mutating pending_block_requests
(which tracks downloads we initiated); stop removing entries from
pending_block_requests inside the PeerMessages::Cancel handler and instead
record cancels in a separate upload-side structure (e.g.,
pending_upload_requests or upload_cancelled set) that tracks requests the peer
made to us; update the PeerMessages::Cancel arm to only update that new
upload-tracking state (and/or mark the corresponding upload request as
cancelled) and leave pending_block_requests untouched.
crates/libtortillas/src/engine/messages.rs (1)

157-161: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove panic from torrent export fan-out.

Line 160 still panics on a single failed ask, which can crash engine export handling. This should be logged and propagated/skipped instead of expect.

Suggested direction
- match torrent
-    .ask(TorrentRequest::Export)
-    .await
-    .expect("Failed to get torrent export")
- {
-    TorrentResponse::Export(export) => *export,
-    _ => unreachable!(),
- }
+ match torrent.ask(TorrentRequest::Export).await {
+    Ok(TorrentResponse::Export(export)) => Some(*export),
+    Ok(_) => {
+       warn!("Unexpected response while exporting torrent");
+       None
+    }
+    Err(err) => {
+       warn!(error = %err, "Failed to get torrent export");
+       None
+    }
+ }

Then collect/filter Option<TorrentExport> before building EngineExport.

🤖 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 157 - 161, The code
currently uses .expect("Failed to get torrent export") on the result of
torrent.ask(TorrentRequest::Export) which can panic; change this to handle the
error instead by mapping the Result to an Option or Result (log the error via
the engine logger and skip that torrent's export on failure), then collect and
filter the resulting Option<TorrentExport> values before constructing
EngineExport so a single failed ask doesn't crash the export fan-out; update the
code around torrent.ask(TorrentRequest::Export), TorrentRequest::Export,
TorrentExport and EngineExport to propagate or skip failed exports rather than
calling expect.
crates/libtortillas/src/torrent/actor.rs (1)

354-379: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore retry state on all validation failure exits.

piece_completed removes the piece’s block state before calling this function. On Line 354–357 and Line 374–379, failures return false without restoring block state or scheduling a re-request, which can stall the piece permanently.

🤖 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 354 - 379, The
early-return paths in piece_completed (when get_piece_path fails, when
util::validate_piece_file fails, and when fs::read fails) do not restore the
piece's block/retry state or re-schedule the piece, which leaves the piece
permanently stalled; before returning false in those branches, call the routines
that restore the piece's block state and enqueue a re-request (e.g., a method on
self such as restore_piece_blocks_for(index) and requeue_piece_request(index) or
whatever restore/schedule helpers exist in this module) so the piece's blocks
are re-added to the retry queue and the piece will be re-requested. Ensure these
calls are made immediately prior to each return false in the get_piece_path,
validate_piece_file, and fs::read error branches (referencing the get_piece_path
call, util::validate_piece_file check, and the fs::read match) so state is
consistently restored after validation failures.
crates/libtortillas/src/torrent/piece_manager.rs (1)

166-193: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Guard piece buffer bounds before slicing data.

recv can still panic if data is shorter than expected (data_offset + len out of range). Return an error instead of panicking on malformed peer payloads.

Proposed fix
   for (path, file_offset, len) in piece_bounds {
+     ensure!(
+        data_offset
+           .checked_add(len)
+           .is_some_and(|end| end <= data.len()),
+        "piece payload too short for index {index}: need at least {} bytes, got {}",
+        data_offset + len,
+        data.len()
+     );
+
      // Ensure parent directories exist
      let full_path = base_path.join(&path);
      if let Some(parent) = full_path.parent() {
         tokio::fs::create_dir_all(parent).await?;
      }
🤖 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_manager.rs` around lines 166 - 193, In
recv, guard against slicing past the provided piece buffer by checking that
data.len().saturating_sub(data_offset) >= len before doing
&data[data_offset..data_offset + len]; if the check fails return an
anyhow::Error (e.g. anyhow::anyhow!("peer sent short piece payload for index {}:
expected {} more bytes", index, len)) rather than panicking; update the loop
that iterates piece_bounds and the use of data_offset (increment it only after a
successful write) so recv, data_offset, and the write_all call safely handle
malformed payloads.
🧹 Nitpick comments (3)
crates/libtortillas/src/tracker/actor.rs (1)

97-100: ⚡ Quick win

Log tracker stop failures in addition to timeout.

Current code only logs timeout errors; if self.tracker.stop() returns Err, it is silently ignored.

Suggested fix
-      let _ = timeout(Duration::from_secs(5), self.tracker.stop())
-         .await
-         .inspect_err(|e| warn!(e = %e.to_string(), "Tracker stop timed out"));
+      match timeout(Duration::from_secs(5), self.tracker.stop()).await {
+         Ok(Ok(())) => {}
+         Ok(Err(e)) => warn!(error = %e, "Tracker stop failed"),
+         Err(e) => warn!(error = %e, "Tracker stop timed out"),
+      }
🤖 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/tracker/actor.rs` around lines 97 - 100, The code
currently only logs timeout errors from timeout(...).await but ignores an Err
returned by self.tracker.stop(); replace the one-liner with handling of both
outcomes: await the timeout result, log the timeout case (existing warn!), and
if the timeout completes (Ok) inspect the inner Result from self.tracker.stop()
and log any Err from that call (e.g., "Tracker stop failed" with the error).
Refer to timeout(Duration::from_secs(5), self.tracker.stop()).await and handle
both Err (timeout) and Ok(Err(...)) branches to emit warn! for each failure.
crates/libtortillas/src/torrent/messages.rs (2)

1-1: ⚡ Quick win

Remove unused import.

The core::panic import is unused. The panic! macro on line 224 doesn't require explicit import.

🧹 Remove unused import
-use core::panic;
 use std::{
    fmt,
    path::PathBuf,
🤖 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/messages.rs` at line 1, Remove the unused
import of core::panic at the top of messages.rs; the panic! macro used in this
file (e.g., the invocation around line 224) is a macro and does not require
importing core::panic, so simply delete the `use core::panic;` statement to
eliminate the unused import warning.

17-21: ⚡ Quick win

Use consistent import style for sibling modules.

The imports mix relative (super::) and absolute (crate::torrent::) paths when referring to sibling modules. For consistency and maintainability, prefer relative imports throughout.

♻️ Align import style
 use super::{
    PieceStorageStrategy, TorrentActor, TorrentExport, TorrentState,
    actor::{PieceManagerProxy, ReadyHook},
    util,
 };
 use crate::{
    actor_request_response,
    hashes::InfoHash,
    metainfo::Info,
    peer::{Peer, PeerId, PeerTell},
    protocol::stream::PeerStream,
-   torrent::piece_manager::PieceManager,
+};
+use super::piece_manager::PieceManager;
+use crate::{
    tracker::Tracker,
 };

Also applies to: 28-28

🤖 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/messages.rs` around lines 17 - 21, The import
list in messages.rs mixes absolute and relative paths; make them consistent by
converting the absolute sibling imports to the same relative style used here
(use super::). Update any occurrences that reference crate::torrent::... to use
super:: (e.g., ensure PieceStorageStrategy, TorrentActor, TorrentExport,
TorrentState, actor::{PieceManagerProxy, ReadyHook}, and util are all imported
via super:: or the same relative form) so all sibling module imports in this
file use the same relative import style.
🤖 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/torrent/actor.rs`:
- Around line 495-503: The call to piece_manager.pre_start(...) can fail after
start() has already transitioned/announced the download, leaving the actor in a
half-started state; update the error path in the start() flow (around pre_start,
piece_manager, and the surrounding start() logic in actor.rs) so that on
Err(err) you either roll back the start transition (undo any state/announcement
changes) or stop the actor cleanly (terminate/shutdown the actor and its
resources) instead of merely logging and returning; ensure the rollback/stop
code reverses any flags/announcements set by start() and cleans up started
subsystems to restore a consistent state.

In `@crates/libtortillas/src/torrent/handle.rs`:
- Around line 84-87: The match arm that uses unreachable!() after awaiting
self.actor().ask(msg).await? should instead return a proper error so unexpected
actor responses don't panic; replace the unreachable!() in the method that
matches TorrentResponse::GetState (and the similar pattern at the other
occurrence matching TorrentResponse::GetInfo at lines 93-96) with returning an
Err containing a descriptive error variant (use the crate's existing error type
or convert to anyhow::Error) that includes the actual unexpected response value;
locate the code around the actor().ask(msg).await? call and change the fallback
arm to return Err(format!(...)) or construct the appropriate
Error::UnexpectedActorResponse(response) rather than panicking.

In `@crates/libtortillas/src/torrent/mod.rs`:
- Around line 12-18: The exposed type TorrentExport is public but it re-exports
BlockMap only as pub(crate), leaking a non-public type via a public API; make
BlockMap (and any other items it needs such as BLOCK_SIZE if used publicly)
publicly reachable by changing the re-export to public (i.e., export
block::{BlockMap, BLOCK_SIZE} as pub) and, if necessary, update the block
module's declaration of BlockMap to be pub so the type is truly public;
alternatively, if you prefer to keep BlockMap internal, make TorrentExport not
expose BlockMap in its public API.

In `@crates/libtortillas/src/tracker/actor.rs`:
- Around line 49-52: The match on torrent_response currently uses unreachable!()
which can panic; change it to propagate a typed error by returning a
TrackerActorError when the variant is unexpected. Replace the fallback arm in
the match that binds info_hash from TorrentResponse::InfoHash(...) so it returns
Err(TrackerActorError::UnexpectedResponse(...)) (or constructs the actor's error
type) instead of calling unreachable!, and update the surrounding function to
return a Result with TrackerActorError so callers receive the typed error;
reference symbols: torrent_response, TorrentResponse::InfoHash, info_hash, and
TrackerActorError.

In `@crates/libtortillas/src/tracker/stats.rs`:
- Around line 77-118: The increment methods (increment_announce_attempts,
increment_announce_successes, increment_total_peers_received,
increment_bytes_sent, increment_bytes_received) currently do load + store which
is racy under concurrent writers; change each to use the Atomic::fetch_add(x,
Ordering::AcqRel) (or Ordering::Relaxed if you prefer weaker ordering) to
perform an atomic read-modify-write instead of load/store so updates can't be
lost, keeping the existing get_* methods unchanged.

---

Outside diff comments:
In `@crates/libtortillas/src/engine/messages.rs`:
- Around line 157-161: The code currently uses .expect("Failed to get torrent
export") on the result of torrent.ask(TorrentRequest::Export) which can panic;
change this to handle the error instead by mapping the Result to an Option or
Result (log the error via the engine logger and skip that torrent's export on
failure), then collect and filter the resulting Option<TorrentExport> values
before constructing EngineExport so a single failed ask doesn't crash the export
fan-out; update the code around torrent.ask(TorrentRequest::Export),
TorrentRequest::Export, TorrentExport and EngineExport to propagate or skip
failed exports rather than calling expect.

In `@crates/libtortillas/src/peer/actor.rs`:
- Around line 513-520: The PeerMessages::Cancel match arm is incorrectly
mutating pending_block_requests (which tracks downloads we initiated); stop
removing entries from pending_block_requests inside the PeerMessages::Cancel
handler and instead record cancels in a separate upload-side structure (e.g.,
pending_upload_requests or upload_cancelled set) that tracks requests the peer
made to us; update the PeerMessages::Cancel arm to only update that new
upload-tracking state (and/or mark the corresponding upload request as
cancelled) and leave pending_block_requests untouched.

In `@crates/libtortillas/src/torrent/actor.rs`:
- Around line 354-379: The early-return paths in piece_completed (when
get_piece_path fails, when util::validate_piece_file fails, and when fs::read
fails) do not restore the piece's block/retry state or re-schedule the piece,
which leaves the piece permanently stalled; before returning false in those
branches, call the routines that restore the piece's block state and enqueue a
re-request (e.g., a method on self such as restore_piece_blocks_for(index) and
requeue_piece_request(index) or whatever restore/schedule helpers exist in this
module) so the piece's blocks are re-added to the retry queue and the piece will
be re-requested. Ensure these calls are made immediately prior to each return
false in the get_piece_path, validate_piece_file, and fs::read error branches
(referencing the get_piece_path call, util::validate_piece_file check, and the
fs::read match) so state is consistently restored after validation failures.

In `@crates/libtortillas/src/torrent/piece_manager.rs`:
- Around line 166-193: In recv, guard against slicing past the provided piece
buffer by checking that data.len().saturating_sub(data_offset) >= len before
doing &data[data_offset..data_offset + len]; if the check fails return an
anyhow::Error (e.g. anyhow::anyhow!("peer sent short piece payload for index {}:
expected {} more bytes", index, len)) rather than panicking; update the loop
that iterates piece_bounds and the use of data_offset (increment it only after a
successful write) so recv, data_offset, and the write_all call safely handle
malformed payloads.

---

Nitpick comments:
In `@crates/libtortillas/src/torrent/messages.rs`:
- Line 1: Remove the unused import of core::panic at the top of messages.rs; the
panic! macro used in this file (e.g., the invocation around line 224) is a macro
and does not require importing core::panic, so simply delete the `use
core::panic;` statement to eliminate the unused import warning.
- Around line 17-21: The import list in messages.rs mixes absolute and relative
paths; make them consistent by converting the absolute sibling imports to the
same relative style used here (use super::). Update any occurrences that
reference crate::torrent::... to use super:: (e.g., ensure PieceStorageStrategy,
TorrentActor, TorrentExport, TorrentState, actor::{PieceManagerProxy,
ReadyHook}, and util are all imported via super:: or the same relative form) so
all sibling module imports in this file use the same relative import style.

In `@crates/libtortillas/src/tracker/actor.rs`:
- Around line 97-100: The code currently only logs timeout errors from
timeout(...).await but ignores an Err returned by self.tracker.stop(); replace
the one-liner with handling of both outcomes: await the timeout result, log the
timeout case (existing warn!), and if the timeout completes (Ok) inspect the
inner Result from self.tracker.stop() and log any Err from that call (e.g.,
"Tracker stop failed" with the error). Refer to timeout(Duration::from_secs(5),
self.tracker.stop()).await and handle both Err (timeout) and Ok(Err(...))
branches to emit warn! for each failure.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3a76bd82-c3ae-48ec-a878-262405e17387

📥 Commits

Reviewing files that changed from the base of the PR and between 36a6263 and f9e2e36.

📒 Files selected for processing (26)
  • crates/libtortillas/src/ARCHITECTURE.md
  • crates/libtortillas/src/engine/messages.rs
  • crates/libtortillas/src/engine/mod.rs
  • crates/libtortillas/src/peer/actor.rs
  • crates/libtortillas/src/peer/state.rs
  • crates/libtortillas/src/protocol/commands.rs
  • crates/libtortillas/src/protocol/messages.rs
  • crates/libtortillas/src/torrent/actor.rs
  • crates/libtortillas/src/torrent/block.rs
  • crates/libtortillas/src/torrent/export.rs
  • crates/libtortillas/src/torrent/handle.rs
  • crates/libtortillas/src/torrent/messages.rs
  • crates/libtortillas/src/torrent/mod.rs
  • crates/libtortillas/src/torrent/piece.rs
  • crates/libtortillas/src/torrent/piece_manager.rs
  • crates/libtortillas/src/torrent/state.rs
  • crates/libtortillas/src/torrent/storage.rs
  • crates/libtortillas/src/torrent/util.rs
  • crates/libtortillas/src/tracker/actor.rs
  • crates/libtortillas/src/tracker/http.rs
  • crates/libtortillas/src/tracker/messages.rs
  • crates/libtortillas/src/tracker/mod.rs
  • crates/libtortillas/src/tracker/model.rs
  • crates/libtortillas/src/tracker/stats.rs
  • crates/libtortillas/src/tracker/udp.rs
  • crates/libtortillas/src/util.rs

Comment thread crates/libtortillas/src/torrent/actor.rs Outdated
Comment thread crates/libtortillas/src/torrent/handle.rs
Comment thread crates/libtortillas/src/torrent/mod.rs Outdated
Comment thread crates/libtortillas/src/tracker/actor.rs
Comment thread crates/libtortillas/src/tracker/stats.rs
Introduce a dedicated PieceScheduler to own block-completion tracking, in-flight request ownership, and next-piece progression. This replaces the ad-hoc DashMap<usize, BitVec> + next_piece fields that were previously spread across TorrentActor.
Create a lightweight actor that serialises block writes and piece validation/reads through its own mailbox, keeping expensive filesystem operations off the TorrentActor's critical path.
Relocate the PieceManager trait and FilePieceManager struct from torrent/piece_manager.rs to pieces/piece_manager.rs. The trait is a domain-level abstraction that belongs alongside the other piece-domain types.
Register the new top-level pieces module alongside the existing peer, protocol and torrent modules.
Expose the new pieces module at the crate root so the rest of the codebase can import from crate::pieces.
Move incoming_piece, request_blocks_from_peer, write_block_to_storage, piece_completed, validate_and_send_piece, is_duplicate_block, is_piece_complete, get_piece_path and read_piece_block from TorrentActor into a new piece_flow module. This keeps the actor file focused on lifecycle while co-locating the piece pipeline.
Move append_peer, insert_peer, broadcast_to_peers, update_trackers and broadcast_to_trackers from TorrentActor into a new swarm module. This separates peer/tracker lifecycle from the torrent's core download logic.
Replace the removed piece and piece_manager modules with the new piece_flow and swarm modules. Drop the re-exports of StreamedPiece and PieceManager since they now live in the pieces crate module.
PieceManager now lives under crate::pieces, so update the import in handle.rs accordingly.
- Replace DashMap with HashMap for peers and trackers
- Replace Arc<BitVec<AtomicU8>> with plain BitVec<AtomicU8>
- Add PieceScheduler to own block tracking and in-flight requests
- Add PieceStoreActor to offload disk I/O from the critical path
- Implement on_stop lifecycle hook that kills children on shutdown
- Remove methods extracted to piece_flow.rs (incoming_piece,
  request_blocks_from_peer, write_block_to_storage, piece_completed,
  validate_and_send_piece, is_duplicate_block, is_piece_complete,
  get_piece_path, read_piece_block)
- Remove methods extracted to swarm.rs (append_peer, insert_peer,
  broadcast_to_peers, update_trackers, broadcast_to_trackers)
- Simplify start, total_bytes_downloaded and export to use PieceScheduler
Add a PeerConnected message so that async connection tasks can deliver fully-handshaked peers back to the torrent actor, which then inserts them via insert_peer (rather than the old inline append_peer path).
Allow peers to signal that they could not fulfill a block request (e.g. missing piece). The torrent actor releases the in-flight scheduler slot so another peer can pick it up.
Include the originating peer's ID in the IncomingPiece message so the piece flow can credit the peer for the block and request replacements from the same peer.
These request types were never fully implemented and had only stub handlers that panicked with unimplemented!(). Drop them to clean up the TorrentRequest enum.
Replace the unimplemented!() stub in Message<TorrentRequest> for the Request variant with real logic that checks the bitfield and reads the piece block from storage. Returns empty Bytes if the piece is not found.
Clear scheduler state when a peer disconnects so in-flight requests are not orphaned. Replace the single-block PeerReady dispatch with a request_blocks_from_peer call that fills the full request window.
artrixdotdev and others added 16 commits May 21, 2026 23:22
Bitfield is now a plain BitVec<AtomicU8> instead of Arc<BitVec>. Wrap the clone in Arc::new() when sending to peers (PeerTell::HaveInfoDict) and when responding to TorrentRequest::Bitfield.
Notify the torrent actor when a peer actor stops so it can clean up scheduler state and remove the peer from its peer map.
…ctor

Extract reusable methods for notifying the torrent actor that a peer is ready to receive requests, and for notifying it when a request is rejected. These will be called from multiple points in the message handler.
Remove the complex message-queueing logic from send_message. Request messages are now queued separately via pending_block_requests, and the choked check simply drops outgoing requests rather than buffering them in a deque.
When the peer is choking us, queue the block request in pending_block_requests instead of silently dropping it. Reject the request back to the scheduler when the send fails so another peer can pick it up.
…rting

Guard against missing peer IDs when forwarding piece data. Add the peer_id to the IncomingPiece message so the torrent actor can credit the peer. Handle send failures with a warn! log instead of silently discarding errors.
…tfield

Call notify_ready() after an unchoke or bitfield message so the torrent actor can start filling the peer's request window. Without this, the torrent only dispatched requests on initial connection.
Use the propagating ? operator instead of unwrap() on TcpStream::peer_addr(). Fall back to a '<disconnected>' label in the Display impl when the address cannot be resolved.
When a tracker rejects an announce with a 'connection id' error, reset the ready state and reconnect before retrying once. This handles the common case where the connection ID expires between connects.
StreamedPiece has been relocated to pieces/mod.rs alongside the other piece-domain types.
PieceManager trait and FilePieceManager have been relocated to pieces/piece_manager.rs. All remaining imports now reference crate::pieces.
decreases cloning and is more memory efficient
Fixed 6 file(s) based on 7 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
@artrixdotdev artrixdotdev changed the title refactor: file restructuring refactor: project restructuring May 22, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 5 file(s) based on 5 unresolved review comments.

Files modified:

  • crates/libtortillas/src/torrent/actor.rs
  • crates/libtortillas/src/torrent/handle.rs
  • crates/libtortillas/src/torrent/mod.rs
  • crates/libtortillas/src/tracker/actor.rs
  • crates/libtortillas/src/tracker/stats.rs

Commit: 0cde487009a0d3e0c4435a115aecc362f5c045c1

The changes have been pushed to the refactor/file-restructuring branch.

Time taken: 5m 17s

Fixed 5 file(s) based on 5 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

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/torrent/messages.rs (1)

281-294: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Queue all readiness waiters, not just the last one.

self.ready_hook = Some(hook) drops any earlier waiter. If two callers hit Torrent::poll_ready() before the torrent becomes ready, the first receiver is orphaned and can wait forever. Store a collection of hooks and drain it when readiness is reached.

🤖 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/messages.rs` around lines 281 - 294, The
ReadyHook handling currently overwrites self.ready_hook, orphaning earlier
waiters; change ready_hook from an Option to a collection (e.g., Vec or
VecDeque) and push the incoming hook in TorrentMessage::ReadyHook handling
instead of assigning, ensure any paths that send immediate readiness (the early
self.state != TorrentState::Inactive branch and the is_ready && !self.autostart
branch) send to all queued hooks by draining the collection, and also drain/send
the queued hooks when the torrent transitions to ready (wherever state change
logic lives, e.g., in autostart() completion or state transition handlers) so
every waiter receives the readiness signal.
♻️ Duplicate comments (1)
crates/libtortillas/src/torrent/actor.rs (1)

183-197: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Roll back startup when pre_start fails.

This still returns after logging, but SetState(Downloading) has already moved the actor out of Inactive before start() runs. A pre_start failure now leaves the torrent stuck in Downloading even though tracker announce/request flow never started, and poll_ready() will observe the wrong lifecycle state. Keep the old state until startup succeeds, or explicitly revert it here.

🤖 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 183 - 197, start()
currently allows SetState(Downloading) to be applied before calling
piece_manager.pre_start, so a pre_start failure leaves the actor in Downloading;
change the startup flow to keep or restore the previous state on failure: either
move the SetState(Downloading) transition to occur after
piece_manager.pre_start(info.clone()).await succeeds, or on Err(err) call the
state setter to revert to the previous state (e.g., SetState(Inactive)) before
returning; ensure you reference the existing start method, the
piece_manager.pre_start call, and the SetState(Downloading)/poll_ready lifecycle
so the actor's state is consistent when pre_start fails.
🤖 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/peer/actor.rs`:
- Around line 192-201: Replace the direct unwrap() on the actor request to the
supervisor in the interest computation: call
self.supervisor.ask(TorrentRequest::InterestingPieces(their_bitfield)).await and
explicitly match on the Result to handle Err by returning early (or logging and
returning) instead of panicking, then inside Ok(...) match the TorrentResponse
and handle unexpected variants by returning early rather than using
unreachable!(); assign has_interesting_pieces and interesting_piece_count only
when you get TorrentResponse::InterestingPieces(has, count).

In `@crates/libtortillas/src/pieces/piece_scheduler.rs`:
- Around line 56-58: The incoming BitVec is being cleared before storing, which
erases restored completed state; in restore_piece_blocks (function
restore_piece_blocks) remove the line that calls blocks.fill(false) so the
provided bitmap is stored intact into self.completed_blocks.insert(index,
blocks) (or otherwise insert a clone of the original BitVec if you must avoid
taking ownership), ensuring restored blocks are preserved.

In `@crates/libtortillas/src/pieces/piece_store.rs`:
- Around line 62-67: The ValidateAndRead handler currently wraps logic in an
immediately-awaited async block (async { util::validate_piece_file(path.clone(),
hash).await?; Ok(read(&path).await?.into()) }.await); remove that wrapper and
inline the awaited calls directly in the handler body so the handler awaits
util::validate_piece_file(...) and read(&path).await? and returns the result
without the redundant async { ... }.await; adjust error propagation as before so
the function returns the same Result type and drop the
clippy::redundant_async_block justification.

In `@crates/libtortillas/src/torrent/messages.rs`:
- Around line 326-348: Validate incoming TorrentRequest::Request bounds before
calling read_piece_block: check that length is <= BLOCK_SIZE and that offset and
length do not exceed the actual piece length for the given index (use whatever
function/field provides piece length) and reject the request early (return
TorrentResponse::Request with None or an error) if any check fails; do this in
the match arm handling TorrentRequest::Request before allocating/awaiting
read_piece_block so read_piece_block(index, offset, length) is only invoked for
well-formed requests.

In `@crates/libtortillas/src/torrent/piece_flow.rs`:
- Around line 228-275: The get_piece_path() failure branch and the
PieceStoreRequest::ValidateAndRead Err branch currently return false without
restoring previous_blocks or re-queueing work; update validate_and_send_piece
to, on every early-failure path (the else for get_piece_path and the Err arm of
the piece_store.ask(...).await), call
self.piece_scheduler.restore_piece_blocks(index, previous_blocks.clone_or_ref?)
when previous_blocks.is_some() and then call
self.request_blocks_from_peer(peer_id, 1).await before returning false so the
piece bitmap is restored and work is re-requested; keep the existing warn! logs
and mirror the same restore+request logic used in the piece_manager.recv() Err
branch.
- Around line 99-109: write_block_to_storage currently swallows errors, so
mark_block_complete and broadcast_to_peers are called even when the disk write
failed; change write_block_to_storage to return a Result (or bool) and update
its callers in piece_flow.rs to check the result and early-return on failure so
you do not call piece_scheduler.mark_block_complete(...) or
broadcast_to_peers(PeerTell::CancelPiece(...)) when the write failed; apply the
same change to the other occurrence (the block handling around lines 151–183) so
both code paths only mark blocks complete and send CancelPiece after a
successful write.

In `@crates/libtortillas/src/tracker/actor.rs`:
- Around line 51-53: The code returns TrackerActorError::InvalidResponse using
format!("Expected InfoHash response, got unexpected variant") which triggers
clippy::useless_format; locate the match arm that constructs
TrackerActorError::InvalidResponse (the `_ => { return
Err(TrackerActorError::InvalidResponse { reason: format!(...),` branch) and
replace the format! call with a direct String allocation such as
String::from("Expected InfoHash response, got unexpected variant") or "Expected
InfoHash response, got unexpected variant".to_string() to silence the lint and
fix CI.

---

Outside diff comments:
In `@crates/libtortillas/src/torrent/messages.rs`:
- Around line 281-294: The ReadyHook handling currently overwrites
self.ready_hook, orphaning earlier waiters; change ready_hook from an Option to
a collection (e.g., Vec or VecDeque) and push the incoming hook in
TorrentMessage::ReadyHook handling instead of assigning, ensure any paths that
send immediate readiness (the early self.state != TorrentState::Inactive branch
and the is_ready && !self.autostart branch) send to all queued hooks by draining
the collection, and also drain/send the queued hooks when the torrent
transitions to ready (wherever state change logic lives, e.g., in autostart()
completion or state transition handlers) so every waiter receives the readiness
signal.

---

Duplicate comments:
In `@crates/libtortillas/src/torrent/actor.rs`:
- Around line 183-197: start() currently allows SetState(Downloading) to be
applied before calling piece_manager.pre_start, so a pre_start failure leaves
the actor in Downloading; change the startup flow to keep or restore the
previous state on failure: either move the SetState(Downloading) transition to
occur after piece_manager.pre_start(info.clone()).await succeeds, or on Err(err)
call the state setter to revert to the previous state (e.g., SetState(Inactive))
before returning; ensure you reference the existing start method, the
piece_manager.pre_start call, and the SetState(Downloading)/poll_ready lifecycle
so the actor's state is consistent when pre_start fails.
🪄 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

Run ID: c4190b3d-0242-4103-a8f3-d359d894d21e

📥 Commits

Reviewing files that changed from the base of the PR and between f9e2e36 and 0cde487.

📒 Files selected for processing (18)
  • crates/libtortillas/src/engine/messages.rs
  • crates/libtortillas/src/lib.rs
  • crates/libtortillas/src/peer/actor.rs
  • crates/libtortillas/src/pieces/mod.rs
  • crates/libtortillas/src/pieces/piece_manager.rs
  • crates/libtortillas/src/pieces/piece_scheduler.rs
  • crates/libtortillas/src/pieces/piece_store.rs
  • crates/libtortillas/src/protocol/stream.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/piece_flow.rs
  • crates/libtortillas/src/torrent/swarm.rs
  • crates/libtortillas/src/torrent/util.rs
  • crates/libtortillas/src/tracker/actor.rs
  • crates/libtortillas/src/tracker/stats.rs
  • crates/libtortillas/src/tracker/udp.rs

Comment on lines +192 to 201
let (has_interesting_pieces, interesting_piece_count) = match self
.supervisor
.ask(TorrentRequest::InterestingPieces(their_bitfield))
.await
.unwrap()
{
TorrentResponse::InterestingPieces(has_interesting_pieces, count) => {
(has_interesting_pieces, count)
}
_ => unreachable!("Unexpected response from supervisor"),
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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Avoid panic on supervisor call in interest computation.

Line 196 uses unwrap() on an actor request, which can panic and stop the peer actor during transient supervisor failures. Handle Err/unexpected responses explicitly and return early.

💡 Proposed fix
-      let (has_interesting_pieces, interesting_piece_count) = match self
-         .supervisor
-         .ask(TorrentRequest::InterestingPieces(their_bitfield))
-         .await
-         .unwrap()
-      {
-         TorrentResponse::InterestingPieces(has_interesting_pieces, count) => {
-            (has_interesting_pieces, count)
-         }
-         _ => unreachable!("Unexpected response from supervisor"),
-      };
+      let (has_interesting_pieces, interesting_piece_count) = match self
+         .supervisor
+         .ask(TorrentRequest::InterestingPieces(their_bitfield))
+         .await
+      {
+         Ok(TorrentResponse::InterestingPieces(has_interesting_pieces, count)) => {
+            (has_interesting_pieces, count)
+         }
+         Ok(other) => {
+            warn!(response = ?other, "Unexpected response for InterestingPieces");
+            return;
+         }
+         Err(err) => {
+            warn!(error = %err, "Failed to query interesting pieces from supervisor");
+            return;
+         }
+      };
🧰 Tools
🪛 GitHub Actions: ✅ Checks / lint

[error] Build failed: could not compile libtortillas (lib and lib test) due to previous clippy error.

🤖 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 192 - 201, Replace the
direct unwrap() on the actor request to the supervisor in the interest
computation: call
self.supervisor.ask(TorrentRequest::InterestingPieces(their_bitfield)).await and
explicitly match on the Result to handle Err by returning early (or logging and
returning) instead of panicking, then inside Ok(...) match the TorrentResponse
and handle unexpected variants by returning early rather than using
unreachable!(); assign has_interesting_pieces and interesting_piece_count only
when you get TorrentResponse::InterestingPieces(has, count).

Comment on lines +56 to +58
pub(crate) fn restore_piece_blocks(&mut self, index: usize, mut blocks: BitVec) {
blocks.fill(false);
self.completed_blocks.insert(index, blocks);
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

restore_piece_blocks currently erases restored state.

On Line 57, blocks.fill(false) clears the incoming bitmap before storing it, so previously completed blocks are lost during restore.

Proposed fix
   pub(crate) fn restore_piece_blocks(&mut self, index: usize, mut blocks: BitVec) {
-      blocks.fill(false);
       self.completed_blocks.insert(index, blocks);
   }
📝 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 restore_piece_blocks(&mut self, index: usize, mut blocks: BitVec) {
blocks.fill(false);
self.completed_blocks.insert(index, blocks);
pub(crate) fn restore_piece_blocks(&mut self, index: usize, mut blocks: BitVec) {
self.completed_blocks.insert(index, blocks);
🧰 Tools
🪛 GitHub Actions: ✅ Checks / lint

[error] Build failed: could not compile libtortillas (lib and lib test) due to previous clippy error.

🤖 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_scheduler.rs` around lines 56 - 58, The
incoming BitVec is being cleared before storing, which erases restored completed
state; in restore_piece_blocks (function restore_piece_blocks) remove the line
that calls blocks.fill(false) so the provided bitmap is stored intact into
self.completed_blocks.insert(index, blocks) (or otherwise insert a clone of the
original BitVec if you must avoid taking ownership), ensuring restored blocks
are preserved.

Comment on lines +62 to +67
async {
util::validate_piece_file(path.clone(), hash).await?;
Ok(read(&path).await?.into())
}
.await
}
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the immediately-awaited async { ... }.await wrapper in the ValidateAndRead handler.

The handler can be simplified by inlining the async body; the prior justification for clippy::redundant_async_block is likely inaccurate here since the block contains multiple .awaits.

Proposed fix
         PieceStoreRequest::ValidateAndRead { path, hash } =&gt; {
-            async {
-               util::validate_piece_file(path.clone(), hash).await?;
-               Ok(read(&amp;path).await?.into())
-            }
-            .await
+            util::validate_piece_file(path.clone(), hash).await?;
+            Ok(read(&amp;path).await?.into())
         }
📝 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
async {
util::validate_piece_file(path.clone(), hash).await?;
Ok(read(&path).await?.into())
}
.await
}
util::validate_piece_file(path.clone(), hash).await?;
Ok(read(&path).await?.into())
}
🧰 Tools
🪛 GitHub Actions: ✅ Checks / lint

[error] Build failed: could not compile libtortillas (lib and lib test) due to previous clippy error.

🤖 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_store.rs` around lines 62 - 67, The
ValidateAndRead handler currently wraps logic in an immediately-awaited async
block (async { util::validate_piece_file(path.clone(), hash).await?;
Ok(read(&path).await?.into()) }.await); remove that wrapper and inline the
awaited calls directly in the handler body so the handler awaits
util::validate_piece_file(...) and read(&path).await? and returns the result
without the redundant async { ... }.await; adjust error propagation as before so
the function returns the same Result type and drop the
clippy::redundant_async_block justification.

Comment on lines +326 to +348
TorrentRequest::Request(index, offset, length) => {
let data = if self
.bitfield
.get(index)
.as_deref()
.copied()
.unwrap_or(false)
{
match self.read_piece_block(index, offset, length).await {
Ok(data) => Some(data),
Err(err) => {
warn!(
?err,
index, offset, length, "Failed to read requested piece block"
);
None
}
}
} else {
warn!(index, offset, length, "Peer requested piece we do not have");
None
};
TorrentResponse::Request(index, offset, data)
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate peer request bounds before reading from disk.

index, offset, and especially length come from a remote peer and are passed straight into read_piece_block(), which allocates vec![0; length]. A malicious request can force oversized allocations or probe past the concrete piece boundary. Reject requests that exceed BLOCK_SIZE or the actual piece length before attempting the read.

🧰 Tools
🪛 GitHub Actions: ✅ Checks / lint

[error] Build failed: could not compile libtortillas (lib and lib test) due to previous clippy error.

🤖 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/messages.rs` around lines 326 - 348, Validate
incoming TorrentRequest::Request bounds before calling read_piece_block: check
that length is <= BLOCK_SIZE and that offset and length do not exceed the actual
piece length for the given index (use whatever function/field provides piece
length) and reject the request early (return TorrentResponse::Request with None
or an error) if any check fails; do this in the match arm handling
TorrentRequest::Request before allocating/awaiting read_piece_block so
read_piece_block(index, offset, length) is only invoked for well-formed
requests.

Comment on lines +99 to +109
let block_len = block.len();
self.write_block_to_storage(index, offset, block).await;

// Only mark block complete after successful write
self
.piece_scheduler
.mark_block_complete(index, block_index, expected_blocks);

self
.broadcast_to_peers(PeerTell::CancelPiece(index, offset, block_len))
.await;
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not mark a block complete when the write failed.

write_block_to_storage() only logs and returns (), so this path always calls mark_block_complete() and broadcasts CancelPiece(...) even if nothing was persisted. After a disk error, the scheduler can believe the block is done and stop re-requesting it. Make write_block_to_storage() return a Result/bool and bail out here on failure.

Also applies to: 151-183

🧰 Tools
🪛 GitHub Actions: ✅ Checks / lint

[error] Build failed: could not compile libtortillas (lib and lib test) due to previous clippy error.

🤖 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 99 - 109,
write_block_to_storage currently swallows errors, so mark_block_complete and
broadcast_to_peers are called even when the disk write failed; change
write_block_to_storage to return a Result (or bool) and update its callers in
piece_flow.rs to check the result and early-return on failure so you do not call
piece_scheduler.mark_block_complete(...) or
broadcast_to_peers(PeerTell::CancelPiece(...)) when the write failed; apply the
same change to the other occurrence (the block handling around lines 151–183) so
both code paths only mark blocks complete and send CancelPiece after a
successful write.

Comment on lines +228 to +275
async fn validate_and_send_piece(
&mut self, peer_id: crate::peer::PeerId, index: usize,
previous_blocks: Option<bitvec::vec::BitVec>,
) -> bool {
let info_dict = self
.info_dict()
.expect("Can't receive piece without info dict");

match &self.piece_storage {
PieceStorageStrategy::Disk(_) => {
let Ok(path) = self.get_piece_path(index) else {
warn!(index, "Failed to get piece path; re-requesting");
return false;
};

let data = match self
.piece_store
.ask(PieceStoreRequest::ValidateAndRead {
path: path.clone(),
hash: info_dict.pieces[index],
})
.await
{
Ok(data) => data,
Err(err) => {
warn!(?err, index, path = %path.display(), "Failed to validate piece through piece store actor; re-requesting");
return false;
}
};
if let Err(err) = self.piece_manager.recv(index, data).await {
warn!(?err, index, path = %path.display(), "Piece manager rejected piece; re-requesting");
if let Some(blocks) = previous_blocks {
self.piece_scheduler.restore_piece_blocks(index, blocks);
}
self.request_blocks_from_peer(peer_id, 1).await;
return false;
}
}
PieceStorageStrategy::InFile => {
warn!(
index,
"In-file piece validation is not implemented yet; re-requesting"
);
return false;
}
}
true
}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore the piece bitmap on every validation/read failure path.

piece_completed() removes the block map before entering this function. On the get_piece_path() and ValidateAndRead error branches, this returns false without restoring previous_blocks or re-queueing work, so a transient I/O/validation failure can drop all progress for that piece and leave it stalled. The restore/retry logic should run for every early-failure branch, not just piece_manager.recv().

🧰 Tools
🪛 GitHub Actions: ✅ Checks / lint

[error] Build failed: could not compile libtortillas (lib and lib test) due to previous clippy error.

🤖 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 228 - 275, The
get_piece_path() failure branch and the PieceStoreRequest::ValidateAndRead Err
branch currently return false without restoring previous_blocks or re-queueing
work; update validate_and_send_piece to, on every early-failure path (the else
for get_piece_path and the Err arm of the piece_store.ask(...).await), call
self.piece_scheduler.restore_piece_blocks(index, previous_blocks.clone_or_ref?)
when previous_blocks.is_some() and then call
self.request_blocks_from_peer(peer_id, 1).await before returning false so the
piece bitmap is restored and work is re-requested; keep the existing warn! logs
and mirror the same restore+request logic used in the piece_manager.recv() Err
branch.

Comment on lines +51 to +53
_ => {
return Err(TrackerActorError::InvalidResponse {
reason: format!("Expected InfoHash response, got unexpected variant"),
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix the clippy-blocking format! call.

Line 53 wraps a string literal in format!, which triggers clippy::useless_format and is already failing CI. Replace it with .to_string() or String::from(...).

Suggested fix
          _ => {
             return Err(TrackerActorError::InvalidResponse {
-               reason: format!("Expected InfoHash response, got unexpected variant"),
+               reason: "Expected InfoHash response, got unexpected variant".to_string(),
             })
          }
📝 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
_ => {
return Err(TrackerActorError::InvalidResponse {
reason: format!("Expected InfoHash response, got unexpected variant"),
_ => {
return Err(TrackerActorError::InvalidResponse {
reason: "Expected InfoHash response, got unexpected variant".to_string(),
})
}
🧰 Tools
🪛 GitHub Actions: ✅ Checks / 0_lint.txt

[error] 53-53: clippy (useless_format / clippy::useless-format): useless use of format!. Consider using .to_string(): "Expected InfoHash response, got unexpected variant".to_string().

🪛 GitHub Actions: ✅ Checks / lint

[error] 53-53: clippy failed: useless use of format! (clippy::useless_format). Consider using .to_string() for the string literal.


[error] Build failed: could not compile libtortillas (lib and lib test) due to previous clippy error.

🤖 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/tracker/actor.rs` around lines 51 - 53, The code
returns TrackerActorError::InvalidResponse using format!("Expected InfoHash
response, got unexpected variant") which triggers clippy::useless_format; locate
the match arm that constructs TrackerActorError::InvalidResponse (the `_ => {
return Err(TrackerActorError::InvalidResponse { reason: format!(...),` branch)
and replace the format! call with a direct String allocation such as
String::from("Expected InfoHash response, got unexpected variant") or "Expected
InfoHash response, got unexpected variant".to_string() to silence the lint and
fix CI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

med prio Medium Priority refactor Neither fixes a bug nor adds a feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant