From 85320d669198dbfb8cc0473ee277d8081d133a45 Mon Sep 17 00:00:00 2001 From: artrixdotdev Date: Sun, 26 Oct 2025 19:32:20 -0700 Subject: [PATCH 1/6] feat: Send trackers `Stopped` message when `TorrentActor` stops --- crates/libtortillas/src/tracker/http.rs | 14 +++++++++++++ crates/libtortillas/src/tracker/mod.rs | 26 +++++++++++++++++++++++-- crates/libtortillas/src/tracker/udp.rs | 20 +++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/crates/libtortillas/src/tracker/http.rs b/crates/libtortillas/src/tracker/http.rs index 657fe5d..5d7fc02 100644 --- a/crates/libtortillas/src/tracker/http.rs +++ b/crates/libtortillas/src/tracker/http.rs @@ -264,6 +264,20 @@ impl TrackerBase for HttpTracker { fn interval(&self) -> usize { self.interval() } + #[instrument(skip(self), fields( + tracker_uri = %self.uri, + peer_id = %self.peer_id, + torrent_id = %self.info_hash, + ))] + async fn stop(&self) -> Result<()> { + { + self.params.write().await.event = Event::Stopped; + } + self.announce().await?; + + debug!("Stopped tracker"); + Ok(()) + } } fn urlencode(t: &[u8; 20]) -> String { diff --git a/crates/libtortillas/src/tracker/mod.rs b/crates/libtortillas/src/tracker/mod.rs index 01dd4b7..4ab8975 100644 --- a/crates/libtortillas/src/tracker/mod.rs +++ b/crates/libtortillas/src/tracker/mod.rs @@ -14,7 +14,8 @@ use atomic_time::{AtomicInstant, AtomicOptionInstant}; use http::HttpTracker; use kameo::{ Actor, - actor::ActorRef, + actor::{ActorRef, WeakActorRef}, + error::ActorStopReason, mailbox::Signal, prelude::{Context, Message}, }; @@ -25,7 +26,7 @@ use serde::{ }; use serde_repr::{Deserialize_repr, Serialize_repr}; use tokio::time::{Instant, Interval, interval}; -use tracing::error; +use tracing::{debug, error, instrument, trace}; use udp::UdpTracker; use crate::{ @@ -143,6 +144,9 @@ pub trait TrackerBase: Send + Sync { /// Gets the announce interval. fn interval(&self) -> usize; + + /// Stops the tracker and removes it from the tracker's list of peers. + async fn stop(&self) -> Result<()>; } /// Enum for the different tracker variants that implement [TrackerBase] rather @@ -176,6 +180,13 @@ impl TrackerBase for TrackerInstance { } } + async fn stop(&self) -> Result<()> { + match self { + TrackerInstance::Udp(tracker) => tracker.stop().await, + TrackerInstance::Http(tracker) => tracker.stop().await, + } + } + fn interval(&self) -> usize { match self { TrackerInstance::Udp(tracker) => tracker.interval() as usize, @@ -268,6 +279,17 @@ impl Actor for TrackerActor { interval: interval(Duration::from_secs(30)), }) } + async fn on_stop( + &mut self, _: WeakActorRef, reason: ActorStopReason, + ) -> Result<(), Self::Error> { + match reason { + ActorStopReason::Killed | ActorStopReason::Normal => { + self.tracker.stop().await?; + Ok(()) + } + _ => Ok(()), + } + } async fn next( &mut self, _: kameo::prelude::WeakActorRef, diff --git a/crates/libtortillas/src/tracker/udp.rs b/crates/libtortillas/src/tracker/udp.rs index 75da637..38bd2fd 100644 --- a/crates/libtortillas/src/tracker/udp.rs +++ b/crates/libtortillas/src/tracker/udp.rs @@ -929,6 +929,26 @@ impl TrackerBase for UdpTracker { fn interval(&self) -> usize { self.interval.load(Ordering::Acquire) as usize } + #[instrument(skip(self), fields( + tracker_uri = %self.uri, + tracker_connection_id = ?self.get_connection_id(), + torrent_id = %self.info_hash, + tracker_ready_state = ?self.get_ready_state(), + ))] + async fn stop(&self) -> anyhow::Result<()> { + ensure!( + self.get_ready_state() == ReadyState::Ready, + "Tracker not ready for a stop request" + ); + + { + self.announce_params.write().await.event = Event::Stopped; + } + let _ = self.announce().await?; // Discard the response since we don't need it + + debug!("Stopped tracker"); + Ok(()) + } } #[cfg(test)] From 781148229f3dcad26db388620b8e488bcb186340 Mon Sep 17 00:00:00 2001 From: artrixdotdev Date: Sun, 26 Oct 2025 19:35:18 -0700 Subject: [PATCH 2/6] style: Fix clippy errors --- crates/libtortillas/src/tracker/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/libtortillas/src/tracker/mod.rs b/crates/libtortillas/src/tracker/mod.rs index 4ab8975..9dbad01 100644 --- a/crates/libtortillas/src/tracker/mod.rs +++ b/crates/libtortillas/src/tracker/mod.rs @@ -26,7 +26,7 @@ use serde::{ }; use serde_repr::{Deserialize_repr, Serialize_repr}; use tokio::time::{Instant, Interval, interval}; -use tracing::{debug, error, instrument, trace}; +use tracing::error; use udp::UdpTracker; use crate::{ From 8f3963160c113b4b01839ee6f1b596725b2ec34b Mon Sep 17 00:00:00 2001 From: Artrix Date: Mon, 27 Oct 2025 11:45:19 -0700 Subject: [PATCH 3/6] fix: Bound and soften stop announce and avoid blocking shutdown. Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- crates/libtortillas/src/tracker/http.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/libtortillas/src/tracker/http.rs b/crates/libtortillas/src/tracker/http.rs index 5d7fc02..0df19ca 100644 --- a/crates/libtortillas/src/tracker/http.rs +++ b/crates/libtortillas/src/tracker/http.rs @@ -273,9 +273,12 @@ impl TrackerBase for HttpTracker { { self.params.write().await.event = Event::Stopped; } - self.announce().await?; - - debug!("Stopped tracker"); + // Best‑effort, bounded final announce + match tokio::time::timeout(std::time::Duration::from_secs(3), self.announce()).await { + Ok(Ok(_)) => debug!("Stopped tracker"), + Ok(Err(e)) => debug!(error = %e, "Stop announce failed; ignoring"), + Err(_) => debug!("Stop announce timed out; ignoring"), + } Ok(()) } } From 3c47c335a5c75f032276bdc1962c0ebfa67d7ac3 Mon Sep 17 00:00:00 2001 From: artrixdotdev Date: Mon, 27 Oct 2025 12:52:22 -0700 Subject: [PATCH 4/6] fix: Timeout stop announce --- crates/libtortillas/src/tracker/mod.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/libtortillas/src/tracker/mod.rs b/crates/libtortillas/src/tracker/mod.rs index 9dbad01..afdbc23 100644 --- a/crates/libtortillas/src/tracker/mod.rs +++ b/crates/libtortillas/src/tracker/mod.rs @@ -25,8 +25,8 @@ use serde::{ de::{self, Visitor}, }; use serde_repr::{Deserialize_repr, Serialize_repr}; -use tokio::time::{Instant, Interval, interval}; -use tracing::error; +use tokio::time::{Instant, Interval, interval, timeout}; +use tracing::{error, warn}; use udp::UdpTracker; use crate::{ @@ -284,11 +284,14 @@ impl Actor for TrackerActor { ) -> Result<(), Self::Error> { match reason { ActorStopReason::Killed | ActorStopReason::Normal => { - self.tracker.stop().await?; - Ok(()) + // We don't care if the tracker stops successfully or not + let _ = timeout(Duration::from_secs(5), self.tracker.stop()) + .await + .inspect_err(|e| warn!(e = %e.to_string(), "Tracker stop timed out")); } - _ => Ok(()), + _ => {} } + Ok(()) } async fn next( From c5e8b108fd0c77d0a7a03ecbd9c021eb0ac980a9 Mon Sep 17 00:00:00 2001 From: artrixdotdev Date: Mon, 27 Oct 2025 12:53:37 -0700 Subject: [PATCH 5/6] refactor: Send `Stop` announce regardless of the reason the actor stopped --- crates/libtortillas/src/tracker/mod.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/crates/libtortillas/src/tracker/mod.rs b/crates/libtortillas/src/tracker/mod.rs index afdbc23..275e345 100644 --- a/crates/libtortillas/src/tracker/mod.rs +++ b/crates/libtortillas/src/tracker/mod.rs @@ -280,17 +280,13 @@ impl Actor for TrackerActor { }) } async fn on_stop( - &mut self, _: WeakActorRef, reason: ActorStopReason, + &mut self, _: WeakActorRef, _: ActorStopReason, ) -> Result<(), Self::Error> { - match reason { - ActorStopReason::Killed | ActorStopReason::Normal => { - // We don't care if the tracker stops successfully or not - let _ = timeout(Duration::from_secs(5), self.tracker.stop()) - .await - .inspect_err(|e| warn!(e = %e.to_string(), "Tracker stop timed out")); - } - _ => {} - } + // We don't care if the tracker stops successfully or not + let _ = timeout(Duration::from_secs(5), self.tracker.stop()) + .await + .inspect_err(|e| warn!(e = %e.to_string(), "Tracker stop timed out")); + Ok(()) } From 86399dbde29fc3d6f1ba04f3000a8407ea74dd5e Mon Sep 17 00:00:00 2001 From: artrixdotdev Date: Mon, 27 Oct 2025 13:34:55 -0700 Subject: [PATCH 6/6] style: Fix clippy errors --- crates/libtortillas/src/tracker/http.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/libtortillas/src/tracker/http.rs b/crates/libtortillas/src/tracker/http.rs index 0df19ca..fe74529 100644 --- a/crates/libtortillas/src/tracker/http.rs +++ b/crates/libtortillas/src/tracker/http.rs @@ -15,7 +15,10 @@ use serde::{ de::{self, Visitor}, }; use serde_with::serde_as; -use tokio::{sync::RwLock, time::Instant}; +use tokio::{ + sync::RwLock, + time::{Instant, timeout}, +}; use tracing::{debug, error, instrument, trace, warn}; /// See https://www.bittorrent.org/beps/bep_0003.html @@ -274,7 +277,7 @@ impl TrackerBase for HttpTracker { self.params.write().await.event = Event::Stopped; } // Best‑effort, bounded final announce - match tokio::time::timeout(std::time::Duration::from_secs(3), self.announce()).await { + match timeout(std::time::Duration::from_secs(3), self.announce()).await { Ok(Ok(_)) => debug!("Stopped tracker"), Ok(Err(e)) => debug!(error = %e, "Stop announce failed; ignoring"), Err(_) => debug!("Stop announce timed out; ignoring"),