Skip to content

Commit 627d535

Browse files
committed
Merge #1508: Add logs for torrent cleaning process
f11dfcc feat: [#1502] adding logs for debugging (Jose Celano) 57b4822 refactor: remove debug print (Jose Celano) Pull request description: This adds more logs to the torrent's cleanup process. It would be helpful to find the bug described in the issue #1502. However, it will be useful afterwards. Sample output: ```output 2025-05-08T10:01:18.417631Z INFO torrust_tracker_lib::bootstrap::jobs::torrent_cleanup: Cleaning up torrents (executed every 60 secs) ... 2025-05-08T10:01:18.417661Z INFO bittorrent_tracker_core::torrent::manager: torrents=1 downloads=2 seeders=2 leechers=0 2025-05-08T10:01:18.417666Z INFO bittorrent_tracker_core::torrent::manager: peerless_torrents=0 peers=2 2025-05-08T10:01:18.417670Z INFO torrust_tracker_torrent_repository::swarms: Removing inactive peers since: 2025-05-08T10:00:48.417669546Z ... 2025-05-08T10:01:18.417676Z INFO torrust_tracker_torrent_repository::swarms: Inactive peers removed: 2 2025-05-08T10:01:18.417679Z INFO bittorrent_tracker_core::torrent::manager: torrents=1 downloads=2 seeders=0 leechers=0 2025-05-08T10:01:18.417682Z INFO bittorrent_tracker_core::torrent::manager: peerless_torrents=1 peers=0 2025-05-08T10:01:18.417685Z INFO torrust_tracker_torrent_repository::swarms: Removing peerless torrents ... 2025-05-08T10:01:18.417688Z INFO torrust_tracker_torrent_repository::swarms: Peerless torrents removed: 0 2025-05-08T10:01:18.417690Z INFO bittorrent_tracker_core::torrent::manager: torrents=1 downloads=2 seeders=0 leechers=0 2025-05-08T10:01:18.417693Z INFO bittorrent_tracker_core::torrent::manager: peerless_torrents=1 peers=0 2025-05-08T10:01:18.417697Z INFO torrust_tracker_lib::bootstrap::jobs::torrent_cleanup: Cleaned up torrents in: 0 ms ``` ACKs for top commit: josecelano: ACK f11dfcc Tree-SHA512: 3afdc519b17b3f9fc9792e0d7842d06aba3db32e1fa9a13885f3ef58f0f0f2ff8354aeccf790fbc98142f3d6359dac30983d885bd53341737f472d5989a66902
2 parents 5ee9efb + f11dfcc commit 627d535

File tree

9 files changed

+181
-28
lines changed

9 files changed

+181
-28
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/torrent-repository/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ tokio = { version = "1", features = ["macros", "net", "rt-multi-thread", "signal
2424
torrust-tracker-clock = { version = "3.0.0-develop", path = "../clock" }
2525
torrust-tracker-configuration = { version = "3.0.0-develop", path = "../configuration" }
2626
torrust-tracker-primitives = { version = "3.0.0-develop", path = "../primitives" }
27+
tracing = "0"
2728

2829
[dev-dependencies]
2930
async-std = { version = "1", features = ["attributes", "tokio1"] }

packages/torrent-repository/src/swarm.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,9 @@ impl Swarm {
101101
}
102102
}
103103

104-
pub fn remove_inactive(&mut self, current_cutoff: DurationSinceUnixEpoch) {
104+
pub fn remove_inactive(&mut self, current_cutoff: DurationSinceUnixEpoch) -> u64 {
105+
let mut inactive_peers_removed = 0;
106+
105107
self.peers.retain(|_, peer| {
106108
let is_active = peer::ReadInfo::get_updated(peer) > current_cutoff;
107109

@@ -112,10 +114,14 @@ impl Swarm {
112114
} else {
113115
self.metadata.incomplete -= 1;
114116
}
117+
118+
inactive_peers_removed += 1;
115119
}
116120

117121
is_active
118122
});
123+
124+
inactive_peers_removed
119125
}
120126

121127
#[must_use]
@@ -190,6 +196,11 @@ impl Swarm {
190196
self.peers.is_empty()
191197
}
192198

199+
#[must_use]
200+
pub fn is_peerless(&self) -> bool {
201+
self.is_empty()
202+
}
203+
193204
/// Returns true if the swarm meets the retention policy, meaning that
194205
/// it should be kept in the list of swarms.
195206
#[must_use]

packages/torrent-repository/src/swarms.rs

Lines changed: 101 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::sync::{Arc, Mutex};
22

33
use bittorrent_primitives::info_hash::InfoHash;
44
use crossbeam_skiplist::SkipMap;
5+
use torrust_tracker_clock::conv::convert_from_timestamp_to_datetime_utc;
56
use torrust_tracker_configuration::TrackerPolicy;
67
use torrust_tracker_primitives::pagination::Pagination;
78
use torrust_tracker_primitives::swarm_metadata::{AggregateSwarmMetadata, SwarmMetadata};
@@ -76,24 +77,6 @@ impl Swarms {
7677
self.swarms.remove(key).map(|entry| entry.value().clone())
7778
}
7879

79-
/// Removes inactive peers from all torrent entries.
80-
///
81-
/// A peer is considered inactive if its last update timestamp is older than
82-
/// the provided cutoff time.
83-
///
84-
/// # Errors
85-
///
86-
/// This function returns an error if it fails to acquire the lock for any
87-
/// swarm handle.
88-
pub fn remove_inactive_peers(&self, current_cutoff: DurationSinceUnixEpoch) -> Result<(), Error> {
89-
for swarm_handle in &self.swarms {
90-
let mut swarm = swarm_handle.value().lock()?;
91-
swarm.remove_inactive(current_cutoff);
92-
}
93-
94-
Ok(())
95-
}
96-
9780
/// Retrieves a tracked torrent handle by its infohash.
9881
///
9982
/// # Returns
@@ -225,6 +208,34 @@ impl Swarms {
225208
}
226209
}
227210

211+
/// Removes inactive peers from all torrent entries.
212+
///
213+
/// A peer is considered inactive if its last update timestamp is older than
214+
/// the provided cutoff time.
215+
///
216+
/// # Errors
217+
///
218+
/// This function returns an error if it fails to acquire the lock for any
219+
/// swarm handle.
220+
pub fn remove_inactive_peers(&self, current_cutoff: DurationSinceUnixEpoch) -> Result<u64, Error> {
221+
tracing::info!(
222+
"Removing inactive peers since: {:?} ...",
223+
convert_from_timestamp_to_datetime_utc(current_cutoff)
224+
);
225+
226+
let mut inactive_peers_removed = 0;
227+
228+
for swarm_handle in &self.swarms {
229+
let mut swarm = swarm_handle.value().lock()?;
230+
let removed = swarm.remove_inactive(current_cutoff);
231+
inactive_peers_removed += removed;
232+
}
233+
234+
tracing::info!("Inactive peers removed: {inactive_peers_removed}");
235+
236+
Ok(inactive_peers_removed)
237+
}
238+
228239
/// Removes torrent entries that have no active peers.
229240
///
230241
/// Depending on the tracker policy, torrents without any peers may be
@@ -234,7 +245,11 @@ impl Swarms {
234245
///
235246
/// This function returns an error if it fails to acquire the lock for any
236247
/// swarm handle.
237-
pub fn remove_peerless_torrents(&self, policy: &TrackerPolicy) -> Result<(), Error> {
248+
pub fn remove_peerless_torrents(&self, policy: &TrackerPolicy) -> Result<u64, Error> {
249+
tracing::info!("Removing peerless torrents ...");
250+
251+
let mut peerless_torrents_removed = 0;
252+
238253
for swarm_handle in &self.swarms {
239254
let swarm = swarm_handle.value().lock()?;
240255

@@ -243,17 +258,25 @@ impl Swarms {
243258
}
244259

245260
swarm_handle.remove();
261+
262+
peerless_torrents_removed += 1;
246263
}
247264

248-
Ok(())
265+
tracing::info!("Peerless torrents removed: {peerless_torrents_removed}");
266+
267+
Ok(peerless_torrents_removed)
249268
}
250269

251270
/// Imports persistent torrent data into the in-memory repository.
252271
///
253272
/// This method takes a set of persisted torrent entries (e.g., from a
254273
/// database) and imports them into the in-memory repository for immediate
255274
/// access.
256-
pub fn import_persistent(&self, persistent_torrents: &PersistentTorrents) {
275+
pub fn import_persistent(&self, persistent_torrents: &PersistentTorrents) -> u64 {
276+
tracing::info!("Importing persisted info about torrents ...");
277+
278+
let mut torrents_imported = 0;
279+
257280
for (info_hash, completed) in persistent_torrents {
258281
if self.swarms.contains_key(info_hash) {
259282
continue;
@@ -264,7 +287,13 @@ impl Swarms {
264287
// Since SkipMap is lock-free the torrent could have been inserted
265288
// after checking if it exists.
266289
self.swarms.get_or_insert(*info_hash, entry);
290+
291+
torrents_imported += 1;
267292
}
293+
294+
tracing::info!("Imported torrents: {torrents_imported}");
295+
296+
torrents_imported
268297
}
269298

270299
/// Calculates and returns overall torrent metrics.
@@ -284,9 +313,11 @@ impl Swarms {
284313
pub fn get_aggregate_swarm_metadata(&self) -> Result<AggregateSwarmMetadata, Error> {
285314
let mut metrics = AggregateSwarmMetadata::default();
286315

287-
for entry in &self.swarms {
288-
let swarm = entry.value().lock()?;
316+
for swarm_handle in &self.swarms {
317+
let swarm = swarm_handle.value().lock()?;
318+
289319
let stats = swarm.metadata();
320+
290321
metrics.total_complete += u64::from(stats.complete);
291322
metrics.total_downloaded += u64::from(stats.downloaded);
292323
metrics.total_incomplete += u64::from(stats.incomplete);
@@ -296,6 +327,53 @@ impl Swarms {
296327
Ok(metrics)
297328
}
298329

330+
/// Counts the number of torrents that are peerless (i.e., have no active
331+
/// peers).
332+
///
333+
/// # Returns
334+
///
335+
/// A `usize` representing the number of peerless torrents.
336+
///
337+
/// # Errors
338+
///
339+
/// This function returns an error if it fails to acquire the lock for any
340+
/// swarm handle.
341+
pub fn count_peerless_torrents(&self) -> Result<usize, Error> {
342+
let mut peerless_torrents = 0;
343+
344+
for swarm_handle in &self.swarms {
345+
let swarm = swarm_handle.value().lock()?;
346+
347+
if swarm.is_peerless() {
348+
peerless_torrents += 1;
349+
}
350+
}
351+
352+
Ok(peerless_torrents)
353+
}
354+
355+
/// Counts the total number of peers across all torrents.
356+
///
357+
/// # Returns
358+
///
359+
/// A `usize` representing the total number of peers.
360+
///
361+
/// # Errors
362+
///
363+
/// This function returns an error if it fails to acquire the lock for any
364+
/// swarm handle.
365+
pub fn count_peers(&self) -> Result<usize, Error> {
366+
let mut peers = 0;
367+
368+
for swarm_handle in &self.swarms {
369+
let swarm = swarm_handle.value().lock()?;
370+
371+
peers += swarm.len();
372+
}
373+
374+
Ok(peers)
375+
}
376+
299377
#[must_use]
300378
pub fn len(&self) -> usize {
301379
self.swarms.len()

packages/tracker-core/src/torrent/manager.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,51 @@ impl TorrentsManager {
9292
/// (`remove_peerless_torrents` is set), it removes entire torrent
9393
/// entries that have no active peers.
9494
pub fn cleanup_torrents(&self) {
95+
self.log_aggregate_swarm_metadata();
96+
97+
self.remove_inactive_peers();
98+
99+
self.log_aggregate_swarm_metadata();
100+
101+
self.remove_peerless_torrents();
102+
103+
self.log_aggregate_swarm_metadata();
104+
}
105+
106+
fn remove_inactive_peers(&self) {
95107
let current_cutoff = CurrentClock::now_sub(&Duration::from_secs(u64::from(self.config.tracker_policy.max_peer_timeout)))
96108
.unwrap_or_default();
97109

98110
self.in_memory_torrent_repository.remove_inactive_peers(current_cutoff);
111+
}
99112

113+
fn remove_peerless_torrents(&self) {
100114
if self.config.tracker_policy.remove_peerless_torrents {
101115
self.in_memory_torrent_repository
102116
.remove_peerless_torrents(&self.config.tracker_policy);
103117
}
104118
}
119+
120+
fn log_aggregate_swarm_metadata(&self) {
121+
// Pre-calculated data
122+
let aggregate_swarm_metadata = self.in_memory_torrent_repository.get_aggregate_swarm_metadata();
123+
124+
tracing::info!(name: "pre_calculated_aggregate_swarm_metadata",
125+
torrents = aggregate_swarm_metadata.total_torrents,
126+
downloads = aggregate_swarm_metadata.total_downloaded,
127+
seeders = aggregate_swarm_metadata.total_complete,
128+
leechers = aggregate_swarm_metadata.total_incomplete,
129+
);
130+
131+
// Hot data (iterating over data structures)
132+
let peerless_torrents = self.in_memory_torrent_repository.count_peerless_torrents();
133+
let peers = self.in_memory_torrent_repository.count_peers();
134+
135+
tracing::info!(name: "hot_aggregate_swarm_metadata",
136+
peerless_torrents = peerless_torrents,
137+
peers = peers,
138+
);
139+
}
105140
}
106141

107142
#[cfg(test)]

packages/tracker-core/src/torrent/repository/in_memory.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,28 @@ impl InMemoryTorrentRepository {
241241
.expect("Failed to get aggregate swarm metadata")
242242
}
243243

244+
/// Counts the number of peerless torrents in the repository.
245+
///
246+
/// # Panics
247+
///
248+
/// This function panics if the underling swarms return an error.
249+
#[must_use]
250+
pub fn count_peerless_torrents(&self) -> usize {
251+
self.swarms
252+
.count_peerless_torrents()
253+
.expect("Failed to count peerless torrents")
254+
}
255+
256+
/// Counts the number of peers in the repository.
257+
///
258+
/// # Panics
259+
///
260+
/// This function panics if the underling swarms return an error.
261+
#[must_use]
262+
pub fn count_peers(&self) -> usize {
263+
self.swarms.count_peers().expect("Failed to count peers")
264+
}
265+
244266
/// Imports persistent torrent data into the in-memory repository.
245267
///
246268
/// This method takes a set of persisted torrent entries (e.g., from a database)

packages/udp-tracker-core/src/services/announce.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,6 @@ impl AnnounceService {
119119

120120
tracing::debug!(target = crate::UDP_TRACKER_LOG_TARGET, "Sending UdpAnnounce event: {event:?}");
121121

122-
println!("Sending UdpAnnounce event: {event:?}");
123-
124122
udp_stats_event_sender.send(event).await;
125123
}
126124
}

share/default/config/tracker.development.sqlite3.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@ schema_version = "2.0.0"
77
threshold = "info"
88

99
[core]
10+
#inactive_peer_cleanup_interval = 60
1011
listed = false
1112
private = false
1213

14+
#[core.tracker_policy]
15+
#max_peer_timeout = 30
16+
#persistent_torrent_completed_stat = true
17+
#remove_peerless_torrents = true
18+
1319
[[udp_trackers]]
1420
bind_address = "0.0.0.0:6868"
1521
tracker_usage_statistics = true

src/bootstrap/jobs/torrent_cleanup.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use tracing::instrument;
2828
pub fn start_job(config: &Core, torrents_manager: &Arc<TorrentsManager>) -> JoinHandle<()> {
2929
let weak_torrents_manager = std::sync::Arc::downgrade(torrents_manager);
3030
let interval = config.inactive_peer_cleanup_interval;
31+
let interval_in_secs = interval;
3132

3233
tokio::spawn(async move {
3334
let interval = std::time::Duration::from_secs(interval);
@@ -43,9 +44,9 @@ pub fn start_job(config: &Core, torrents_manager: &Arc<TorrentsManager>) -> Join
4344
_ = interval.tick() => {
4445
if let Some(torrents_manager) = weak_torrents_manager.upgrade() {
4546
let start_time = Utc::now().time();
46-
tracing::info!("Cleaning up torrents..");
47+
tracing::info!("Cleaning up torrents (executed every {} secs) ...", interval_in_secs);
4748
torrents_manager.cleanup_torrents();
48-
tracing::info!("Cleaned up torrents in: {}ms", (Utc::now().time() - start_time).num_milliseconds());
49+
tracing::info!("Cleaned up torrents in: {} ms", (Utc::now().time() - start_time).num_milliseconds());
4950
} else {
5051
break;
5152
}

0 commit comments

Comments
 (0)