Skip to content

feat: Update trackers with current torrent state#180

Merged
artrixdotdev merged 16 commits intomainfrom
feat/send-trackers-torrent-state
Nov 8, 2025
Merged

feat: Update trackers with current torrent state#180
artrixdotdev merged 16 commits intomainfrom
feat/send-trackers-torrent-state

Conversation

@artrixdotdev
Copy link
Owner

@artrixdotdev artrixdotdev commented Nov 6, 2025

#174
#175

Summary by CodeRabbit

  • New Features

    • Background tracker announcements and stats queries for more timely peer discovery and health reporting.
    • Improved piece storage path handling to ensure reliable file placement.
  • Refactor

    • Concurrent tracker coordination with cleanup of dead trackers for more reliable connections.
    • Adjusted start/seeding flow to emit correct tracker events (start/announce/completed) and more accurate "left" reporting.
    • Omitted empty event parameter from HTTP tracker requests for cleaner queries.

@artrixdotdev artrixdotdev linked an issue Nov 6, 2025 that may be closed by this pull request
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 6, 2025

Walkthrough

Adds tracker coordination and bookkeeping to TorrentActor: new tracker message/update types, async helpers to broadcast updates/announces, total-bytes calculation and piece-path resolution, and sequences Started/Announce/Empty/Completed events during start and piece-completion flows.

Changes

Cohort / File(s) Summary
Torrent actor — tracker helpers & utilities
crates/libtortillas/src/torrent/actor.rs
Added total_bytes_downloaded(&self) -> Option<usize>, pub(super) async fn update_trackers(&self, message: TrackerUpdate), pub(super) async fn broadcast_to_trackers(&self, message: TrackerMessage), and pub(super) fn get_piece_path(&self, index: usize) -> anyhow::Result<PathBuf); updated imports to include Event, TrackerMessage, TrackerUpdate; adjusted Start flow to emit Started/Announce/Empty and to emit Completed on seeding.
Piece completion & messaging
crates/libtortillas/src/torrent/messages.rs
On piece completion, capture total_length from info dict, compute total/remaining bytes and send TrackerUpdate::Left(remaining); on final piece, transition to Seeding, emit Event::Completed, and broadcast an announce to trackers.
Tracker actor types & handling
crates/libtortillas/src/tracker/mod.rs
Added pub(crate) enum TrackerMessage { Announce, GetStats } and impl Message<TrackerMessage> for TrackerActor; introduced TrackerUpdate variants (Uploaded, Downloaded, Left, Event(Event)); made Event::Empty the default; changed TrackerActor::next signature to accept actor_ref and handle TrackerMessage::Announce/GetStats.
Tracker HTTP request formatting
crates/libtortillas/src/tracker/http.rs
TrackerRequest::to_string() now omits the event query parameter when Event::Empty, changing URL composition for empty events.

Sequence Diagram(s)

sequenceDiagram
    participant TA as TorrentActor
    participant Trackers as Tracker Actors
    participant Disk as Disk Storage
    participant Peers as Peer Actors

    rect rgba(200,230,255,0.3)
    TA->>Trackers: update_trackers(TrackerUpdate::Event(Event::Started))
    TA->>Trackers: broadcast_to_trackers(TrackerMessage::Announce)
    TA->>Trackers: update_trackers(TrackerUpdate::Event(Event::Empty))
    end

    Peers->>TA: IncomingPiece(blocks)
    TA->>Disk: write blocks
    TA->>Peers: broadcast Have

    alt final piece completed
        TA->>TA: total_bytes_downloaded()
        TA->>Trackers: update_trackers(TrackerUpdate::Left(0))
        TA->>Trackers: update_trackers(TrackerUpdate::Event(Event::Completed))
        TA->>Trackers: broadcast_to_trackers(TrackerMessage::Announce)
        TA->>TA: set state = Seeding
    else regular piece completed
        TA->>TA: compute remaining bytes
        TA->>Trackers: update_trackers(TrackerUpdate::Left(remaining))
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas requiring extra attention:

  • Concurrency, error handling and cleanup in update_trackers() / broadcast_to_trackers() (per-tracker alive checks, concurrent awaits).
  • Correctness and edge cases in total_bytes_downloaded() (info dict presence, piece bitfield, last-piece length, block accounting).
  • Start/announce sequencing and state transitions (ordering of Started → Announce → Empty and Completed on seeding).
  • TrackerActor::next signature change and TrackerMessage handling affecting scheduling/interval recalibration.

Possibly related PRs

Suggested reviewers

  • kurealnum

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective of the pull request: updating trackers with current torrent state through new TrackerUpdate messaging and state synchronization flows.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/send-trackers-torrent-state

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31037cc and 3dc87d2.

📒 Files selected for processing (2)
  • crates/libtortillas/src/torrent/actor.rs (4 hunks)
  • crates/libtortillas/src/tracker/mod.rs (4 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-18T18:25:24.831Z
Learnt from: kurealnum
Repo: artrixdotdev/tortillas PR: 99
File: crates/libtortillas/src/engine/mod.rs:153-155
Timestamp: 2025-08-18T18:25:24.831Z
Learning: The peer stream handling logic in the Engine's EngineMessage::IncomingPeer handler in crates/libtortillas/src/engine/mod.rs is temporary and planned to be migrated to separate code in the future, so extensive refactoring may not be worthwhile in this location.

Applied to files:

  • crates/libtortillas/src/tracker/mod.rs
  • crates/libtortillas/src/torrent/actor.rs
📚 Learning: 2025-08-28T06:33:16.003Z
Learnt from: artrixdotdev
Repo: artrixdotdev/tortillas PR: 125
File: crates/libtortillas/src/torrent/actor.rs:151-171
Timestamp: 2025-08-28T06:33:16.003Z
Learning: In the torrent actor's append_peer function in crates/libtortillas/src/torrent/actor.rs, when a PeerStream is provided, the peer has already been pre-handshaked by the engine. The engine handles the initial handshake validation, info hash verification, and peer ID extraction before passing the stream to the torrent actor. Therefore, the peer.id should already be populated when a stream is provided.

Applied to files:

  • crates/libtortillas/src/torrent/actor.rs
📚 Learning: 2025-10-14T04:03:55.852Z
Learnt from: artrixdotdev
Repo: artrixdotdev/tortillas PR: 160
File: crates/libtortillas/src/peer/actor.rs:76-76
Timestamp: 2025-10-14T04:03:55.852Z
Learning: In BitTorrent P2P networking (libtortillas crate), peer network errors (I/O errors, connection resets, unexpected EOF) occur at very high frequency as normal protocol behavior. These should be logged at trace level to avoid excessive log volume, not at debug/warn/error levels.

Applied to files:

  • crates/libtortillas/src/torrent/actor.rs
🧬 Code graph analysis (2)
crates/libtortillas/src/tracker/mod.rs (2)
crates/libtortillas/src/tracker/http.rs (4)
  • interval (175-177)
  • interval (270-272)
  • new (97-123)
  • new (144-169)
crates/libtortillas/src/tracker/udp.rs (4)
  • interval (595-597)
  • interval (929-931)
  • new (359-371)
  • new (544-580)
crates/libtortillas/src/torrent/actor.rs (2)
crates/libtortillas/src/torrent/piece_manager.rs (2)
  • info (17-17)
  • info (147-149)
crates/libtortillas/src/metainfo/file.rs (1)
  • total_length (144-149)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test
🔇 Additional comments (3)
crates/libtortillas/src/torrent/actor.rs (3)

256-290: Tracker event sequencing aligns with BitTorrent protocol.

The event flow correctly implements the BitTorrent tracker protocol:

  • Seeding torrents (already complete) emit Event::Completed
  • Downloading torrents emit Event::Started, force an immediate Announce to fetch peers, then reset to Event::Empty for subsequent interval-based announces

This sequencing ensures trackers receive appropriate lifecycle events while avoiding redundant announces.


543-569: LGTM: Concurrent tracker update broadcast implemented correctly.

The method snapshots tracker refs before spawning tasks, avoiding lock contention. Each task clones the TrackerUpdate message (Line 555) and checks liveness before sending, with automatic cleanup of dead trackers.


571-593: LGTM: Concurrent tracker message broadcast implemented correctly.

Since TrackerMessage implements Copy, it's automatically copied for each spawned task without explicit cloning. The pattern matches update_trackers and correctly handles liveness checks and cleanup.


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.

@artrixdotdev artrixdotdev linked an issue Nov 6, 2025 that may be closed by this pull request
@coderabbitai coderabbitai bot added enhancement New feature or request med prio Medium Priority refactor Neither fixes a bug nor adds a feature labels Nov 6, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c25a00 and 4d86616.

📒 Files selected for processing (4)
  • crates/libtortillas/src/torrent/actor.rs (5 hunks)
  • crates/libtortillas/src/torrent/messages.rs (3 hunks)
  • crates/libtortillas/src/tracker/http.rs (1 hunks)
  • crates/libtortillas/src/tracker/mod.rs (4 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-08-18T18:25:24.831Z
Learnt from: kurealnum
Repo: artrixdotdev/tortillas PR: 99
File: crates/libtortillas/src/engine/mod.rs:153-155
Timestamp: 2025-08-18T18:25:24.831Z
Learning: The peer stream handling logic in the Engine's EngineMessage::IncomingPeer handler in crates/libtortillas/src/engine/mod.rs is temporary and planned to be migrated to separate code in the future, so extensive refactoring may not be worthwhile in this location.

Applied to files:

  • crates/libtortillas/src/torrent/messages.rs
  • crates/libtortillas/src/tracker/mod.rs
📚 Learning: 2025-10-14T04:03:55.852Z
Learnt from: artrixdotdev
Repo: artrixdotdev/tortillas PR: 160
File: crates/libtortillas/src/peer/actor.rs:76-76
Timestamp: 2025-10-14T04:03:55.852Z
Learning: In BitTorrent P2P networking (libtortillas crate), peer network errors (I/O errors, connection resets, unexpected EOF) occur at very high frequency as normal protocol behavior. These should be logged at trace level to avoid excessive log volume, not at debug/warn/error levels.

Applied to files:

  • crates/libtortillas/src/torrent/messages.rs
  • crates/libtortillas/src/torrent/actor.rs
📚 Learning: 2025-08-28T06:33:16.003Z
Learnt from: artrixdotdev
Repo: artrixdotdev/tortillas PR: 125
File: crates/libtortillas/src/torrent/actor.rs:151-171
Timestamp: 2025-08-28T06:33:16.003Z
Learning: In the torrent actor's append_peer function in crates/libtortillas/src/torrent/actor.rs, when a PeerStream is provided, the peer has already been pre-handshaked by the engine. The engine handles the initial handshake validation, info hash verification, and peer ID extraction before passing the stream to the torrent actor. Therefore, the peer.id should already be populated when a stream is provided.

Applied to files:

  • crates/libtortillas/src/torrent/messages.rs
  • crates/libtortillas/src/torrent/actor.rs
📚 Learning: 2025-08-28T06:30:48.942Z
Learnt from: artrixdotdev
Repo: artrixdotdev/tortillas PR: 125
File: crates/libtortillas/src/torrent/actor.rs:136-139
Timestamp: 2025-08-28T06:30:48.942Z
Learning: In the libtortillas crate, BitVec<AtomicU8> is used for thread-safe bitfield operations in TorrentActor, providing atomic access to individual bits without requiring additional synchronization like RwLock.

Applied to files:

  • crates/libtortillas/src/torrent/actor.rs
🧬 Code graph analysis (3)
crates/libtortillas/src/torrent/messages.rs (2)
crates/libtortillas/src/metainfo/file.rs (2)
  • total_length (144-149)
  • piece_count (140-142)
crates/libtortillas/src/torrent/actor.rs (1)
  • info_dict (205-214)
crates/libtortillas/src/torrent/actor.rs (1)
crates/libtortillas/src/torrent/piece_manager.rs (2)
  • info (17-17)
  • info (147-149)
crates/libtortillas/src/tracker/mod.rs (2)
crates/libtortillas/src/tracker/http.rs (4)
  • interval (175-177)
  • interval (270-272)
  • new (97-123)
  • new (144-169)
crates/libtortillas/src/tracker/udp.rs (4)
  • interval (595-597)
  • interval (929-931)
  • new (359-371)
  • new (544-580)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
crates/libtortillas/src/torrent/actor.rs (1)

322-335: Still overcounting partial pieces (risking underflow).
Line 328 multiplies count_ones() by the uniform piece_length and Line 331 assumes every block is a full BLOCK_SIZE. The final piece is usually shorter and the last block of any piece may be partial, so we overcount bytes. When TorrentMessage::IncomingPiece subtracts that figure from info.total_length() the subtraction can panic in debug or wrap in release, which makes us send bogus left values to trackers. Please compute actual per-piece and per-block sizes—derive the last-piece length from info.total_length(), iterate the block bitsets by index, clamp each block with min(BLOCK_SIZE, remaining_bytes), and cap the aggregate at the torrent length—before returning.

-      let completed_pieces = self.bitfield.count_ones();
-      let piece_size = self.info_dict().map(|info| info.piece_length)? as usize;
-
-      let mut total_bytes = completed_pieces * piece_size;
-
-      for block in self.block_map.iter() {
-         total_bytes += BLOCK_SIZE * block.count_ones();
-      }
-
-      Some(total_bytes)
+      let info = self.info_dict()?;
+      let piece_len = usize::try_from(info.piece_length).ok()?;
+      let total_len = info.total_length() as usize;
+      let piece_count = info.piece_count();
+      if piece_len == 0 || piece_count == 0 {
+         return Some(0);
+      }
+      let last_piece_len = {
+         let rem = total_len % piece_len;
+         if rem == 0 { piece_len } else { rem }
+      };
+
+      let mut total_bytes = 0usize;
+      for idx in self.bitfield.as_bitslice().iter_ones() {
+         let piece_bytes = if idx + 1 == piece_count {
+            last_piece_len
+         } else {
+            piece_len
+         };
+         total_bytes = total_bytes.saturating_add(piece_bytes);
+      }
+      for entry in self.block_map.iter() {
+         let piece_idx = *entry.key();
+         let piece_bytes = if piece_idx + 1 == piece_count {
+            last_piece_len
+         } else {
+            piece_len
+         };
+         for block_idx in entry.value().as_bitslice().iter_ones() {
+            let offset = block_idx * BLOCK_SIZE;
+            if offset >= piece_bytes {
+               continue;
+            }
+            let remaining = piece_bytes - offset;
+            total_bytes =
+               total_bytes.saturating_add(remaining.min(BLOCK_SIZE));
+         }
+      }
+      Some(total_bytes.min(total_len))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d86616 and 31037cc.

📒 Files selected for processing (1)
  • crates/libtortillas/src/torrent/actor.rs (4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-28T06:33:16.003Z
Learnt from: artrixdotdev
Repo: artrixdotdev/tortillas PR: 125
File: crates/libtortillas/src/torrent/actor.rs:151-171
Timestamp: 2025-08-28T06:33:16.003Z
Learning: In the torrent actor's append_peer function in crates/libtortillas/src/torrent/actor.rs, when a PeerStream is provided, the peer has already been pre-handshaked by the engine. The engine handles the initial handshake validation, info hash verification, and peer ID extraction before passing the stream to the torrent actor. Therefore, the peer.id should already be populated when a stream is provided.

Applied to files:

  • crates/libtortillas/src/torrent/actor.rs
📚 Learning: 2025-10-14T04:03:55.852Z
Learnt from: artrixdotdev
Repo: artrixdotdev/tortillas PR: 160
File: crates/libtortillas/src/peer/actor.rs:76-76
Timestamp: 2025-10-14T04:03:55.852Z
Learning: In BitTorrent P2P networking (libtortillas crate), peer network errors (I/O errors, connection resets, unexpected EOF) occur at very high frequency as normal protocol behavior. These should be logged at trace level to avoid excessive log volume, not at debug/warn/error levels.

Applied to files:

  • crates/libtortillas/src/torrent/actor.rs
🧬 Code graph analysis (1)
crates/libtortillas/src/torrent/actor.rs (2)
crates/libtortillas/src/torrent/piece_manager.rs (2)
  • info (17-17)
  • info (147-149)
crates/libtortillas/src/tracker/mod.rs (1)
  • uri (121-127)

@artrixdotdev artrixdotdev merged commit 06f5999 into main Nov 8, 2025
3 checks passed
@artrixdotdev artrixdotdev deleted the feat/send-trackers-torrent-state branch November 8, 2025 19:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request 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.

Update trackers with the current state of the torrent Send tracker correct Event instead of defaulting to Started

1 participant