Skip to content

Commit 9c25a00

Browse files
authored
feat: Send trackers Stopped message when TorrentActor stops (#172)
2 parents 185d669 + 31fe0a2 commit 9c25a00

File tree

3 files changed

+65
-4
lines changed

3 files changed

+65
-4
lines changed

crates/libtortillas/src/tracker/http.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ use serde::{
1515
de::{self, Visitor},
1616
};
1717
use serde_with::serde_as;
18-
use tokio::{sync::RwLock, time::Instant};
18+
use tokio::{
19+
sync::RwLock,
20+
time::{Instant, timeout},
21+
};
1922
use tracing::{debug, error, instrument, trace, warn};
2023

2124
/// See https://www.bittorrent.org/beps/bep_0003.html
@@ -264,6 +267,23 @@ impl TrackerBase for HttpTracker {
264267
fn interval(&self) -> usize {
265268
self.interval.load(Ordering::Acquire)
266269
}
270+
#[instrument(skip(self), fields(
271+
tracker_uri = %self.uri,
272+
peer_id = %self.peer_id,
273+
torrent_id = %self.info_hash,
274+
))]
275+
async fn stop(&self) -> Result<()> {
276+
{
277+
self.params.write().await.event = Event::Stopped;
278+
}
279+
// Best‑effort, bounded final announce
280+
match timeout(std::time::Duration::from_secs(3), self.announce()).await {
281+
Ok(Ok(_)) => debug!("Stopped tracker"),
282+
Ok(Err(e)) => debug!(error = %e, "Stop announce failed; ignoring"),
283+
Err(_) => debug!("Stop announce timed out; ignoring"),
284+
}
285+
Ok(())
286+
}
267287
}
268288

269289
fn urlencode(t: &[u8; 20]) -> String {

crates/libtortillas/src/tracker/mod.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ use atomic_time::{AtomicInstant, AtomicOptionInstant};
1414
use http::HttpTracker;
1515
use kameo::{
1616
Actor,
17-
actor::ActorRef,
17+
actor::{ActorRef, WeakActorRef},
18+
error::ActorStopReason,
1819
mailbox::Signal,
1920
prelude::{Context, Message},
2021
};
@@ -24,8 +25,8 @@ use serde::{
2425
de::{self, Visitor},
2526
};
2627
use serde_repr::{Deserialize_repr, Serialize_repr};
27-
use tokio::time::{Instant, Interval, interval};
28-
use tracing::error;
28+
use tokio::time::{Instant, Interval, interval, timeout};
29+
use tracing::{error, warn};
2930
use udp::UdpTracker;
3031

3132
use crate::{
@@ -143,6 +144,9 @@ pub trait TrackerBase: Send + Sync {
143144

144145
/// Gets the announce interval.
145146
fn interval(&self) -> usize;
147+
148+
/// Stops the tracker and removes it from the tracker's list of peers.
149+
async fn stop(&self) -> Result<()>;
146150
}
147151

148152
/// Enum for the different tracker variants that implement [TrackerBase] rather
@@ -176,6 +180,13 @@ impl TrackerBase for TrackerInstance {
176180
}
177181
}
178182

183+
async fn stop(&self) -> Result<()> {
184+
match self {
185+
TrackerInstance::Udp(tracker) => tracker.stop().await,
186+
TrackerInstance::Http(tracker) => tracker.stop().await,
187+
}
188+
}
189+
179190
fn interval(&self) -> usize {
180191
match self {
181192
TrackerInstance::Udp(tracker) => tracker.interval() as usize,
@@ -268,6 +279,16 @@ impl Actor for TrackerActor {
268279
interval: interval(Duration::from_secs(30)),
269280
})
270281
}
282+
async fn on_stop(
283+
&mut self, _: WeakActorRef<Self>, _: ActorStopReason,
284+
) -> Result<(), Self::Error> {
285+
// We don't care if the tracker stops successfully or not
286+
let _ = timeout(Duration::from_secs(5), self.tracker.stop())
287+
.await
288+
.inspect_err(|e| warn!(e = %e.to_string(), "Tracker stop timed out"));
289+
290+
Ok(())
291+
}
271292

272293
async fn next(
273294
&mut self, _: kameo::prelude::WeakActorRef<Self>,

crates/libtortillas/src/tracker/udp.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,26 @@ impl TrackerBase for UdpTracker {
929929
fn interval(&self) -> usize {
930930
self.interval.load(Ordering::Acquire) as usize
931931
}
932+
#[instrument(skip(self), fields(
933+
tracker_uri = %self.uri,
934+
tracker_connection_id = ?self.get_connection_id(),
935+
torrent_id = %self.info_hash,
936+
tracker_ready_state = ?self.get_ready_state(),
937+
))]
938+
async fn stop(&self) -> anyhow::Result<()> {
939+
ensure!(
940+
self.get_ready_state() == ReadyState::Ready,
941+
"Tracker not ready for a stop request"
942+
);
943+
944+
{
945+
self.announce_params.write().await.event = Event::Stopped;
946+
}
947+
let _ = self.announce().await?; // Discard the response since we don't need it
948+
949+
debug!("Stopped tracker");
950+
Ok(())
951+
}
932952
}
933953

934954
#[cfg(test)]

0 commit comments

Comments
 (0)