Skip to content

Commit 7664ec6

Browse files
committed
Merge #797: Refactor: extract peer list in torrent repository entry
40182b4 test: add tests for PeerList type (Jose Celano) 42f1b30 refactor: extract mod peer_list (Jose Celano) 922afda refactor: rename field from peers to swarm (Jose Celano) 4a567cd refactor: extract PeerList (Jose Celano) Pull request description: This PR extracts a new type, "PeerList", used in the torrent repository entry. ### Why - It can be tested independently (unit tests and benchmarking). - Other implementations could be added in the future. This abstraction hides implementation details (the collection used). ### Performance It looks like it does not affect the performance. ```output Requests out: 406025.97/second Responses in: 365423.41/second - Connect responses: 180950.24 - Announce responses: 180818.87 - Scrape responses: 3654.30 - Error responses: 0.00 Peers per announce response: 0.00 Announce responses per info hash: - p10: 1 - p25: 1 - p50: 1 - p75: 1 - p90: 2 - p95: 3 - p99: 104 - p99.9: 295 - p100: 367 ``` ACKs for top commit: josecelano: ACK 40182b4 Tree-SHA512: 7051fadc2762654748f97ffaba0bdd99f3a8309e612c7f3ac53a5335b78be27dc0421ebe7d3425d563fd7c75e84690e08928f02a63dc8af2828a2eb454bce33b
2 parents 52b7e3a + 40182b4 commit 7664ec6

File tree

12 files changed

+362
-56
lines changed

12 files changed

+362
-56
lines changed

packages/primitives/src/peer.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,38 @@ pub mod fixture {
362362
}
363363

364364
impl PeerBuilder {
365+
#[allow(dead_code)]
366+
#[must_use]
367+
pub fn seeder() -> Self {
368+
let peer = Peer {
369+
peer_id: Id(*b"-qB00000000000000001"),
370+
peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080),
371+
updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
372+
uploaded: NumberOfBytes(0),
373+
downloaded: NumberOfBytes(0),
374+
left: NumberOfBytes(0),
375+
event: AnnounceEvent::Completed,
376+
};
377+
378+
Self { peer }
379+
}
380+
381+
#[allow(dead_code)]
382+
#[must_use]
383+
pub fn leecher() -> Self {
384+
let peer = Peer {
385+
peer_id: Id(*b"-qB00000000000000002"),
386+
peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 8080),
387+
updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
388+
uploaded: NumberOfBytes(0),
389+
downloaded: NumberOfBytes(0),
390+
left: NumberOfBytes(10),
391+
event: AnnounceEvent::Started,
392+
};
393+
394+
Self { peer }
395+
}
396+
365397
#[allow(dead_code)]
366398
#[must_use]
367399
pub fn with_peer_id(mut self, peer_id: &Id) -> Self {
@@ -390,6 +422,13 @@ pub mod fixture {
390422
self
391423
}
392424

425+
#[allow(dead_code)]
426+
#[must_use]
427+
pub fn last_updated_on(mut self, updated: DurationSinceUnixEpoch) -> Self {
428+
self.peer.updated = updated;
429+
self
430+
}
431+
393432
#[allow(dead_code)]
394433
#[must_use]
395434
pub fn build(self) -> Peer {

packages/torrent-repository/src/entry/mod.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ use std::fmt::Debug;
22
use std::net::SocketAddr;
33
use std::sync::Arc;
44

5-
//use serde::{Deserialize, Serialize};
65
use torrust_tracker_configuration::TrackerPolicy;
76
use torrust_tracker_primitives::swarm_metadata::SwarmMetadata;
87
use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch};
98

9+
use self::peer_list::PeerList;
10+
1011
pub mod mutex_std;
1112
pub mod mutex_tokio;
13+
pub mod peer_list;
1214
pub mod single;
1315

1416
pub trait Entry {
@@ -81,9 +83,8 @@ pub trait EntryAsync {
8183
/// The tracker keeps one entry like this for every torrent.
8284
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
8385
pub struct Torrent {
84-
/// The swarm: a network of peers that are all trying to download the torrent associated to this entry
85-
// #[serde(skip)]
86-
pub(crate) peers: std::collections::BTreeMap<peer::Id, Arc<peer::Peer>>,
86+
/// A network of peers that are all trying to download the torrent associated to this entry
87+
pub(crate) swarm: PeerList,
8788
/// The number of peers that have ever completed downloading the torrent associated to this entry
8889
pub(crate) downloaded: u32,
8990
}
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
//! A peer list.
2+
use std::net::SocketAddr;
3+
use std::sync::Arc;
4+
5+
use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch};
6+
7+
// code-review: the current implementation uses the peer Id as the ``BTreeMap``
8+
// key. That would allow adding two identical peers except for the Id.
9+
// For example, two peers with the same socket address but a different peer Id
10+
// would be allowed. That would lead to duplicated peers in the tracker responses.
11+
12+
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
13+
pub struct PeerList {
14+
peers: std::collections::BTreeMap<peer::Id, Arc<peer::Peer>>,
15+
}
16+
17+
impl PeerList {
18+
#[must_use]
19+
pub fn len(&self) -> usize {
20+
self.peers.len()
21+
}
22+
23+
#[must_use]
24+
pub fn is_empty(&self) -> bool {
25+
self.peers.is_empty()
26+
}
27+
28+
pub fn upsert(&mut self, value: Arc<peer::Peer>) -> Option<Arc<peer::Peer>> {
29+
self.peers.insert(value.peer_id, value)
30+
}
31+
32+
pub fn remove(&mut self, key: &peer::Id) -> Option<Arc<peer::Peer>> {
33+
self.peers.remove(key)
34+
}
35+
36+
pub fn remove_inactive_peers(&mut self, current_cutoff: DurationSinceUnixEpoch) {
37+
self.peers
38+
.retain(|_, peer| peer::ReadInfo::get_updated(peer) > current_cutoff);
39+
}
40+
41+
#[must_use]
42+
pub fn get(&self, peer_id: &peer::Id) -> Option<&Arc<peer::Peer>> {
43+
self.peers.get(peer_id)
44+
}
45+
46+
#[must_use]
47+
pub fn get_all(&self, limit: Option<usize>) -> Vec<Arc<peer::Peer>> {
48+
match limit {
49+
Some(limit) => self.peers.values().take(limit).cloned().collect(),
50+
None => self.peers.values().cloned().collect(),
51+
}
52+
}
53+
54+
#[must_use]
55+
pub fn seeders_and_leechers(&self) -> (usize, usize) {
56+
let seeders = self.peers.values().filter(|peer| peer.is_seeder()).count();
57+
let leechers = self.len() - seeders;
58+
59+
(seeders, leechers)
60+
}
61+
62+
#[must_use]
63+
pub fn get_peers_excluding_addr(&self, peer_addr: &SocketAddr, limit: Option<usize>) -> Vec<Arc<peer::Peer>> {
64+
match limit {
65+
Some(limit) => self
66+
.peers
67+
.values()
68+
// Take peers which are not the client peer
69+
.filter(|peer| peer::ReadInfo::get_address(peer.as_ref()) != *peer_addr)
70+
// Limit the number of peers on the result
71+
.take(limit)
72+
.cloned()
73+
.collect(),
74+
None => self
75+
.peers
76+
.values()
77+
// Take peers which are not the client peer
78+
.filter(|peer| peer::ReadInfo::get_address(peer.as_ref()) != *peer_addr)
79+
.cloned()
80+
.collect(),
81+
}
82+
}
83+
}
84+
85+
#[cfg(test)]
86+
mod tests {
87+
88+
mod it_should {
89+
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
90+
use std::sync::Arc;
91+
92+
use torrust_tracker_primitives::peer::fixture::PeerBuilder;
93+
use torrust_tracker_primitives::peer::{self};
94+
use torrust_tracker_primitives::DurationSinceUnixEpoch;
95+
96+
use crate::entry::peer_list::PeerList;
97+
98+
#[test]
99+
fn be_empty_when_no_peers_have_been_inserted() {
100+
let peer_list = PeerList::default();
101+
102+
assert!(peer_list.is_empty());
103+
}
104+
105+
#[test]
106+
fn have_zero_length_when_no_peers_have_been_inserted() {
107+
let peer_list = PeerList::default();
108+
109+
assert_eq!(peer_list.len(), 0);
110+
}
111+
112+
#[test]
113+
fn allow_inserting_a_new_peer() {
114+
let mut peer_list = PeerList::default();
115+
116+
let peer = PeerBuilder::default().build();
117+
118+
assert_eq!(peer_list.upsert(peer.into()), None);
119+
}
120+
121+
#[test]
122+
fn allow_updating_a_preexisting_peer() {
123+
let mut peer_list = PeerList::default();
124+
125+
let peer = PeerBuilder::default().build();
126+
127+
peer_list.upsert(peer.into());
128+
129+
assert_eq!(peer_list.upsert(peer.into()), Some(Arc::new(peer)));
130+
}
131+
132+
#[test]
133+
fn allow_getting_all_peers() {
134+
let mut peer_list = PeerList::default();
135+
136+
let peer = PeerBuilder::default().build();
137+
138+
peer_list.upsert(peer.into());
139+
140+
assert_eq!(peer_list.get_all(None), [Arc::new(peer)]);
141+
}
142+
143+
#[test]
144+
fn allow_getting_one_peer_by_id() {
145+
let mut peer_list = PeerList::default();
146+
147+
let peer = PeerBuilder::default().build();
148+
149+
peer_list.upsert(peer.into());
150+
151+
assert_eq!(peer_list.get(&peer.peer_id), Some(Arc::new(peer)).as_ref());
152+
}
153+
154+
#[test]
155+
fn increase_the_number_of_peers_after_inserting_a_new_one() {
156+
let mut peer_list = PeerList::default();
157+
158+
let peer = PeerBuilder::default().build();
159+
160+
peer_list.upsert(peer.into());
161+
162+
assert_eq!(peer_list.len(), 1);
163+
}
164+
165+
#[test]
166+
fn decrease_the_number_of_peers_after_removing_one() {
167+
let mut peer_list = PeerList::default();
168+
169+
let peer = PeerBuilder::default().build();
170+
171+
peer_list.upsert(peer.into());
172+
173+
peer_list.remove(&peer.peer_id);
174+
175+
assert!(peer_list.is_empty());
176+
}
177+
178+
#[test]
179+
fn allow_removing_an_existing_peer() {
180+
let mut peer_list = PeerList::default();
181+
182+
let peer = PeerBuilder::default().build();
183+
184+
peer_list.upsert(peer.into());
185+
186+
peer_list.remove(&peer.peer_id);
187+
188+
assert_eq!(peer_list.get(&peer.peer_id), None);
189+
}
190+
191+
#[test]
192+
fn allow_getting_all_peers_excluding_peers_with_a_given_address() {
193+
let mut peer_list = PeerList::default();
194+
195+
let peer1 = PeerBuilder::default()
196+
.with_peer_id(&peer::Id(*b"-qB00000000000000001"))
197+
.with_peer_addr(&SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 6969))
198+
.build();
199+
peer_list.upsert(peer1.into());
200+
201+
let peer2 = PeerBuilder::default()
202+
.with_peer_id(&peer::Id(*b"-qB00000000000000002"))
203+
.with_peer_addr(&SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 6969))
204+
.build();
205+
peer_list.upsert(peer2.into());
206+
207+
assert_eq!(peer_list.get_peers_excluding_addr(&peer2.peer_addr, None), [Arc::new(peer1)]);
208+
}
209+
210+
#[test]
211+
fn return_the_number_of_seeders_in_the_list() {
212+
let mut peer_list = PeerList::default();
213+
214+
let seeder = PeerBuilder::seeder().build();
215+
let leecher = PeerBuilder::leecher().build();
216+
217+
peer_list.upsert(seeder.into());
218+
peer_list.upsert(leecher.into());
219+
220+
let (seeders, _leechers) = peer_list.seeders_and_leechers();
221+
222+
assert_eq!(seeders, 1);
223+
}
224+
225+
#[test]
226+
fn return_the_number_of_leechers_in_the_list() {
227+
let mut peer_list = PeerList::default();
228+
229+
let seeder = PeerBuilder::seeder().build();
230+
let leecher = PeerBuilder::leecher().build();
231+
232+
peer_list.upsert(seeder.into());
233+
peer_list.upsert(leecher.into());
234+
235+
let (_seeders, leechers) = peer_list.seeders_and_leechers();
236+
237+
assert_eq!(leechers, 1);
238+
}
239+
240+
#[test]
241+
fn remove_inactive_peers() {
242+
let mut peer_list = PeerList::default();
243+
let one_second = DurationSinceUnixEpoch::new(1, 0);
244+
245+
// Insert the peer
246+
let last_update_time = DurationSinceUnixEpoch::new(1_669_397_478_934, 0);
247+
let peer = PeerBuilder::default().last_updated_on(last_update_time).build();
248+
peer_list.upsert(peer.into());
249+
250+
// Remove peers not updated since one second after inserting the peer
251+
peer_list.remove_inactive_peers(last_update_time + one_second);
252+
253+
assert_eq!(peer_list.len(), 0);
254+
}
255+
256+
#[test]
257+
fn not_remove_active_peers() {
258+
let mut peer_list = PeerList::default();
259+
let one_second = DurationSinceUnixEpoch::new(1, 0);
260+
261+
// Insert the peer
262+
let last_update_time = DurationSinceUnixEpoch::new(1_669_397_478_934, 0);
263+
let peer = PeerBuilder::default().last_updated_on(last_update_time).build();
264+
peer_list.upsert(peer.into());
265+
266+
// Remove peers not updated since one second before inserting the peer.
267+
peer_list.remove_inactive_peers(last_update_time - one_second);
268+
269+
assert_eq!(peer_list.len(), 1);
270+
}
271+
272+
#[test]
273+
fn allow_inserting_two_identical_peers_except_for_the_id() {
274+
let mut peer_list = PeerList::default();
275+
276+
let peer1 = PeerBuilder::default()
277+
.with_peer_id(&peer::Id(*b"-qB00000000000000001"))
278+
.build();
279+
peer_list.upsert(peer1.into());
280+
281+
let peer2 = PeerBuilder::default()
282+
.with_peer_id(&peer::Id(*b"-qB00000000000000002"))
283+
.build();
284+
peer_list.upsert(peer2.into());
285+
286+
assert_eq!(peer_list.len(), 2);
287+
}
288+
}
289+
}

0 commit comments

Comments
 (0)