diff --git a/crates/libtortillas/src/tracker/http.rs b/crates/libtortillas/src/tracker/http.rs index 1a69a08..84b285f 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 @@ -264,6 +267,23 @@ impl TrackerBase for HttpTracker { fn interval(&self) -> usize { self.interval.load(Ordering::Acquire) } + #[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; + } + // Best‑effort, bounded final announce + 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"), + } + 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..275e345 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}, }; @@ -24,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::{ @@ -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,16 @@ impl Actor for TrackerActor { interval: interval(Duration::from_secs(30)), }) } + async fn on_stop( + &mut self, _: WeakActorRef, _: ActorStopReason, + ) -> Result<(), Self::Error> { + // 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(()) + } 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)]