summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--DESIGN19
-rw-r--r--src/ot_clean.c77
-rw-r--r--src/ot_fullscrape.c8
-rw-r--r--src/ot_stats.c8
-rw-r--r--src/trackerlogic.c109
-rw-r--r--src/trackerlogic.h15
-rw-r--r--tests/t_clean.c8
7 files changed, 142 insertions, 102 deletions
diff --git a/DESIGN b/DESIGN
index 39d0107..a32eeb8 100644
--- a/DESIGN
+++ b/DESIGN
@@ -84,6 +84,25 @@ finish a sweep in OT_CLEAN_INTERVAL_MINUTES. At the default bucket count that
84is one bucket every 117 ms. 84is one bucket every 117 ms.
85 85
86 86
87A torrent carries its hash, two peer-list pointers, a base and a down count,
8848 bytes on a 64-bit host, and the down count sits in what would otherwise be
89alignment padding. Each family's peer list, 32 bytes plus its peer vector, is
90allocated on that family's first peer and freed when its last peer goes. The
91base and the down count live on the torrent rather than on a list, which is
92what lets a list come and go: a torrent kept only for its download count holds
93no list at all, and a single-family torrent holds one rather than two.
94
95This is paid for in the common shape of a large table. A tracker carrying
96millions of torrents averages close to two peers each, a few crowded swarms
97over a long tail of near-empty ones, so fixed per-torrent cost, not peer
98payload, is most of resident memory. Allocating both lists for every torrent
99and keeping an idle torrent alive for a day to preserve one counter cost, on
100glibc, 168 bytes per torrent before a peer; the layout above costs 48 for an
101empty torrent and 96 for a single-family one. The per-torrent counts are
102uint32: no single torrent holds 2^32 peers, and the four bytes saved against a
103size_t are multiplied by the torrent count. Global totals stay 64-bit.
104
105
87A refused announce is answered over UDP with a well formed announce for an 106A refused announce is answered over UDP with a well formed announce for an
88empty swarm rather than with the error action. BEP 15 binds a client to the 107empty swarm rather than with the error action. BEP 15 binds a client to the
89interval it is handed and says nothing about what a client should do with an 108interval it is handed and says nothing about what a client should do with an
diff --git a/src/ot_clean.c b/src/ot_clean.c
index eaf069a..77cb4ba 100644
--- a/src/ot_clean.c
+++ b/src/ot_clean.c
@@ -49,26 +49,17 @@ static ssize_t clean_single_bucket(ot_peer *peers, size_t peer_count, size_t pee
49 return (peers - insert_point) / peer_size; 49 return (peers - insert_point) / peer_size;
50} 50}
51 51
52int clean_single_peer_list(ot_peerlist *peer_list, size_t peer_size) { 52/* Age one family's peers by timedout minutes. Frees the list and clears the
53 ot_vector *peer_vector = &peer_list->peers; 53 slot once its last peer is gone, so an absent family costs nothing. */
54 ot_time timedout = time_since_minutes(peer_list->base); 54static void clean_peer_list_family(ot_peerlist **slot, size_t peer_size, ot_time timedout) {
55 size_t num_buckets = 1, removed_seeders = 0; 55 ot_peerlist *peer_list = *slot;
56 ot_vector *peer_vector;
57 size_t num_buckets = 1, removed_seeders = 0;
56 58
57 /* No need to clean empty torrent */ 59 if (!peer_list)
58 if (!timedout) 60 return;
59 return 0;
60
61 /* Torrent has idled out */
62 if (timedout > OT_TORRENT_TIMEOUT)
63 return 1;
64
65 /* Nothing to be cleaned here? Test if torrent is worth keeping */
66 if (timedout > OT_PEER_TIMEOUT) {
67 if (!peer_list->peer_count)
68 return peer_list->down_count ? 0 : 1;
69 timedout = OT_PEER_TIMEOUT;
70 }
71 61
62 peer_vector = &peer_list->peers;
72 if (OT_PEERLIST_HASBUCKETS(peer_list)) { 63 if (OT_PEERLIST_HASBUCKETS(peer_list)) {
73 num_buckets = peer_vector->size; 64 num_buckets = peer_vector->size;
74 peer_vector = (ot_vector *)peer_vector->data; 65 peer_vector = (ot_vector *)peer_vector->data;
@@ -87,25 +78,53 @@ int clean_single_peer_list(ot_peerlist *peer_list, size_t peer_size) {
87 78
88 peer_list->seed_count -= removed_seeders; 79 peer_list->seed_count -= removed_seeders;
89 80
90 /* See if we need to convert a torrent from simple vector to bucket list */ 81 if (!peer_list->peer_count) {
82 free_peerlist(peer_list);
83 *slot = NULL;
84 return;
85 }
86
87 /* See if we need to convert the list from simple vector to bucket list */
91 if ((peer_list->peer_count > OT_PEER_BUCKET_MINCOUNT) || OT_PEERLIST_HASBUCKETS(peer_list)) 88 if ((peer_list->peer_count > OT_PEER_BUCKET_MINCOUNT) || OT_PEERLIST_HASBUCKETS(peer_list))
92 vector_redistribute_buckets(peer_list, peer_size); 89 vector_redistribute_buckets(peer_list, peer_size);
93
94 if (peer_list->peer_count)
95 peer_list->base = time_now_minutes();
96 else {
97 /* When we got here, the last time that torrent
98 has been touched is OT_PEER_TIMEOUT Minutes before */
99 peer_list->base = time_now_minutes() - OT_PEER_TIMEOUT;
100 }
101 return 0;
102} 90}
103 91
104/* Clean a single torrent 92/* Clean a single torrent
105 return 1 if torrent timed out 93 return 1 if torrent timed out
106*/ 94*/
107int clean_single_torrent(ot_torrent *torrent) { 95int clean_single_torrent(ot_torrent *torrent) {
108 return clean_single_peer_list(torrent->peer_list6, OT_PEER_SIZE6) * clean_single_peer_list(torrent->peer_list4, OT_PEER_SIZE4); 96 ot_time timedout = time_since_minutes(torrent->base);
97
98 /* Touched since the last sweep: nothing to do */
99 if (!timedout)
100 return 0;
101
102 /* Torrent has idled out */
103 if (timedout > OT_TORRENT_TIMEOUT)
104 return 1;
105
106 /* Already empty and nothing arrived before the peer timeout: keep it only
107 while a down count is worth remembering */
108 if (timedout > OT_PEER_TIMEOUT && !torrent->peer_list6 && !torrent->peer_list4)
109 return torrent->down_count ? 0 : 1;
110
111 /* Peers past the peer timeout are gone regardless, which caps how far the
112 lists are aged in this pass */
113 if (timedout > OT_PEER_TIMEOUT)
114 timedout = OT_PEER_TIMEOUT;
115
116 clean_peer_list_family(&torrent->peer_list6, OT_PEER_SIZE6, timedout);
117 clean_peer_list_family(&torrent->peer_list4, OT_PEER_SIZE4, timedout);
118
119 if (torrent->peer_list6 || torrent->peer_list4)
120 torrent->base = time_now_minutes(); /* still carrying peers */
121 else {
122 /* Fell empty: hold base a peer timeout back so a later idle sweep makes
123 the keep-or-drop decision above */
124 ot_time now = time_now_minutes();
125 torrent->base = now > OT_PEER_TIMEOUT ? (uint32_t)(now - OT_PEER_TIMEOUT) : 0;
126 }
127 return 0;
109} 128}
110 129
111/* Written to once, by clean_deinit, and never read: the byte stays in and 130/* Written to once, by clean_deinit, and never read: the byte stays in and
diff --git a/src/ot_fullscrape.c b/src/ot_fullscrape.c
index 52ec480..9fae444 100644
--- a/src/ot_fullscrape.c
+++ b/src/ot_fullscrape.c
@@ -90,9 +90,9 @@ static void fullscrape_write_one( ot_tasktype mode, char **cursor, ot_torrent *t
90 exactly that size */ 90 exactly that size */
91 char *r = *cursor; 91 char *r = *cursor;
92 const char *r_end = r + OT_SCRAPE_MAXENTRYLEN; 92 const char *r_end = r + OT_SCRAPE_MAXENTRYLEN;
93 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; 93 size_t seed_count = peerlist_seed_count(torrent->peer_list6) + peerlist_seed_count(torrent->peer_list4);
94 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; 94 size_t peer_count = peerlist_peer_count(torrent->peer_list6) + peerlist_peer_count(torrent->peer_list4);
95 size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; 95 size_t down_count = torrent->down_count;
96 96
97 switch (mode & TASK_TASK_MASK) { 97 switch (mode & TASK_TASK_MASK) {
98 case TASK_FULLSCRAPE: 98 case TASK_FULLSCRAPE:
@@ -131,7 +131,7 @@ static void fullscrape_write_one( ot_tasktype mode, char **cursor, ot_torrent *t
131 case TASK_FULLSCRAPE_TRACKERSTATE: 131 case TASK_FULLSCRAPE_TRACKERSTATE:
132 hex_from_hash(r, *hash); 132 hex_from_hash(r, *hash);
133 r += 2 * sizeof(ot_hash); 133 r += 2 * sizeof(ot_hash);
134 fmt_append(&r, r_end, ":%zd:%zd\n", (ssize_t)(time_wall_minutes() - time_since_minutes(torrent->peer_list6->base)), down_count); 134 fmt_append(&r, r_end, ":%zd:%zd\n", (ssize_t)(time_wall_minutes() - time_since_minutes(torrent->base)), down_count);
135 break; 135 break;
136 } 136 }
137 *cursor = r; 137 *cursor = r;
diff --git a/src/ot_stats.c b/src/ot_stats.c
index 9901081..c50d4e3 100644
--- a/src/ot_stats.c
+++ b/src/ot_stats.c
@@ -299,8 +299,8 @@ typedef struct {
299static int torrent_statter(ot_torrent *torrent, uintptr_t data) { 299static int torrent_statter(ot_torrent *torrent, uintptr_t data) {
300 torrent_stats *stats = (torrent_stats *)data; 300 torrent_stats *stats = (torrent_stats *)data;
301 stats->torrent_count++; 301 stats->torrent_count++;
302 stats->peer_count += torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; 302 stats->peer_count += peerlist_peer_count(torrent->peer_list6) + peerlist_peer_count(torrent->peer_list4);
303 stats->seed_count += torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; 303 stats->seed_count += peerlist_seed_count(torrent->peer_list6) + peerlist_seed_count(torrent->peer_list4);
304 return 0; 304 return 0;
305} 305}
306 306
@@ -343,8 +343,8 @@ void stats_top_txt(char **reply, const char *reply_end, size_t amount) {
343 ot_vector *torrents_list = mutex_bucket_lock(bucket); 343 ot_vector *torrents_list = mutex_bucket_lock(bucket);
344 for (j = 0; j < torrents_list->size; ++j) { 344 for (j = 0; j < torrents_list->size; ++j) {
345 ot_torrent *torrent = (ot_torrent *)(torrents_list->data) + j; 345 ot_torrent *torrent = (ot_torrent *)(torrents_list->data) + j;
346 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; 346 size_t peer_count = peerlist_peer_count(torrent->peer_list6) + peerlist_peer_count(torrent->peer_list4);
347 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; 347 size_t seed_count = peerlist_seed_count(torrent->peer_list6) + peerlist_seed_count(torrent->peer_list4);
348 size_t leech_count = peer_count - seed_count; 348 size_t leech_count = peer_count - seed_count;
349 349
350 stats_insert_into_highscore(peer_count, top100c, amount, &torrent->hash); 350 stats_insert_into_highscore(peer_count, top100c, amount, &torrent->hash);
diff --git a/src/trackerlogic.c b/src/trackerlogic.c
index 2313379..6b0659c 100644
--- a/src/trackerlogic.c
+++ b/src/trackerlogic.c
@@ -42,6 +42,7 @@ void free_peerlist(ot_peerlist *peer_list) {
42 what a state dump records. */ 42 what a state dump records. */
43void add_torrent_from_saved_state(ot_hash const hash, ot_time age, size_t down_count) { 43void add_torrent_from_saved_state(ot_hash const hash, ot_time age, size_t down_count) {
44 int exactmatch; 44 int exactmatch;
45 ot_time now = time_now_minutes();
45 ot_torrent *torrent; 46 ot_torrent *torrent;
46 ot_vector *torrents_list; 47 ot_vector *torrents_list;
47 48
@@ -61,19 +62,12 @@ void add_torrent_from_saved_state(ot_hash const hash, ot_time age, size_t down_c
61 return; 62 return;
62 } 63 }
63 64
64 /* Create a new torrent entry, then */ 65 /* A restored torrent starts with no peers, carrying only its base and its
66 download count until the first announce brings a family list to life */
65 memset(torrent, 0, sizeof(ot_torrent)); 67 memset(torrent, 0, sizeof(ot_torrent));
66 memcpy(torrent->hash, hash, sizeof(ot_hash)); 68 memcpy(torrent->hash, hash, sizeof(ot_hash));
67 69 torrent->base = now > age ? (uint32_t)(now - age) : 0;
68 if (!(torrent->peer_list6 = calloc(1, sizeof(ot_peerlist))) || !(torrent->peer_list4 = calloc(1, sizeof(ot_peerlist)))) { 70 torrent->down_count = (uint32_t)down_count;
69 vector_remove_torrent(torrents_list, torrent);
70 mutex_bucket_unlock_by_hash(hash, 0);
71 return;
72 }
73
74 torrent->peer_list6->base = time_now_minutes() - age;
75 torrent->peer_list4->base = time_now_minutes() - age;
76 torrent->peer_list6->down_count = down_count;
77 71
78 mutex_bucket_unlock_by_hash(hash, 1); 72 mutex_bucket_unlock_by_hash(hash, 1);
79} 73}
@@ -113,21 +107,23 @@ size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstru
113 /* Create a new torrent entry, then */ 107 /* Create a new torrent entry, then */
114 memset(torrent, 0, sizeof(ot_torrent)); 108 memset(torrent, 0, sizeof(ot_torrent));
115 memcpy(torrent->hash, *ws->hash, sizeof(ot_hash)); 109 memcpy(torrent->hash, *ws->hash, sizeof(ot_hash));
116
117 if (!(torrent->peer_list6 = calloc(1, sizeof(ot_peerlist))) || !(torrent->peer_list4 = calloc(1, sizeof(ot_peerlist)))) {
118 vector_remove_torrent(torrents_list, torrent);
119 mutex_bucket_unlock_by_hash(*ws->hash, 0);
120 return 0;
121 }
122
123 delta_torrentcount = 1; 110 delta_torrentcount = 1;
124 } else 111 } else
125 clean_single_torrent(torrent); 112 clean_single_torrent(torrent);
126 113
127 torrent->peer_list6->base = time_now_minutes(); 114 torrent->base = time_now_minutes();
128 torrent->peer_list4->base = time_now_minutes();
129 115
130 peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4; 116 /* The announcing peer's family list is created on first use */
117 {
118 ot_peerlist **slot = peer_size == OT_PEER_SIZE6 ? &torrent->peer_list6 : &torrent->peer_list4;
119 if (!*slot && !(*slot = calloc(1, sizeof(ot_peerlist)))) {
120 if (delta_torrentcount)
121 vector_remove_torrent(torrents_list, torrent);
122 mutex_bucket_unlock_by_hash(*ws->hash, 0);
123 return 0;
124 }
125 peer_list = *slot;
126 }
131 127
132 /* Check for peer in torrent */ 128 /* Check for peer in torrent */
133 peer_dest = vector_find_or_insert_peer(&(peer_list->peers), peer_src, peer_size, &exactmatch); 129 peer_dest = vector_find_or_insert_peer(&(peer_list->peers), peer_src, peer_size, &exactmatch);
@@ -155,7 +151,7 @@ size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstru
155 151
156 peer_list->peer_count++; 152 peer_list->peer_count++;
157 if (OT_PEERFLAG(ws->peer) & PEER_FLAG_COMPLETED) { 153 if (OT_PEERFLAG(ws->peer) & PEER_FLAG_COMPLETED) {
158 peer_list->down_count++; 154 torrent->down_count++;
159 stats_issue_event(ws, EVENT_COMPLETED, 0, 0); 155 stats_issue_event(ws, EVENT_COMPLETED, 0, 0);
160 } 156 }
161 if (OT_PEERFLAG(ws->peer) & PEER_FLAG_SEEDING) 157 if (OT_PEERFLAG(ws->peer) & PEER_FLAG_SEEDING)
@@ -182,7 +178,7 @@ size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstru
182 if (!(OT_PEERFLAG_D(peer_dest, peer_size) & PEER_FLAG_SEEDING) && (OT_PEERFLAG(ws->peer) & PEER_FLAG_SEEDING)) 178 if (!(OT_PEERFLAG_D(peer_dest, peer_size) & PEER_FLAG_SEEDING) && (OT_PEERFLAG(ws->peer) & PEER_FLAG_SEEDING))
183 peer_list->seed_count++; 179 peer_list->seed_count++;
184 if (!(OT_PEERFLAG_D(peer_dest, peer_size) & PEER_FLAG_COMPLETED) && (OT_PEERFLAG(ws->peer) & PEER_FLAG_COMPLETED)) { 180 if (!(OT_PEERFLAG_D(peer_dest, peer_size) & PEER_FLAG_COMPLETED) && (OT_PEERFLAG(ws->peer) & PEER_FLAG_COMPLETED)) {
185 peer_list->down_count++; 181 torrent->down_count++;
186 stats_issue_event(ws, EVENT_COMPLETED, 0, 0); 182 stats_issue_event(ws, EVENT_COMPLETED, 0, 0);
187 } 183 }
188 if (OT_PEERFLAG_D(peer_dest, peer_size) & PEER_FLAG_COMPLETED) 184 if (OT_PEERFLAG_D(peer_dest, peer_size) & PEER_FLAG_COMPLETED)
@@ -286,11 +282,12 @@ static size_t return_peers_for_torrent_udp(struct ot_workstruct *ws, ot_torrent
286 char *r = reply; 282 char *r = reply;
287 size_t peer_size = peer_size_from_peer6(&ws->peer); 283 size_t peer_size = peer_size_from_peer6(&ws->peer);
288 ot_peerlist *peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4; 284 ot_peerlist *peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4;
289 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; 285 size_t list_count = peerlist_peer_count(peer_list);
290 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; 286 size_t peer_count = peerlist_peer_count(torrent->peer_list6) + peerlist_peer_count(torrent->peer_list4);
287 size_t seed_count = peerlist_seed_count(torrent->peer_list6) + peerlist_seed_count(torrent->peer_list4);
291 288
292 if (amount > peer_list->peer_count) 289 if (amount > list_count)
293 amount = peer_list->peer_count; 290 amount = list_count;
294 291
295 *(uint32_t *)(r + 0) = htonl(OT_CLIENT_REQUEST_INTERVAL_RANDOM); 292 *(uint32_t *)(r + 0) = htonl(OT_CLIENT_REQUEST_INTERVAL_RANDOM);
296 *(uint32_t *)(r + 4) = htonl(peer_count - seed_count); 293 *(uint32_t *)(r + 4) = htonl(peer_count - seed_count);
@@ -298,7 +295,7 @@ static size_t return_peers_for_torrent_udp(struct ot_workstruct *ws, ot_torrent
298 r += 12; 295 r += 12;
299 296
300 if (amount) { 297 if (amount) {
301 if (amount == peer_list->peer_count) 298 if (amount == list_count)
302 r += return_peers_all(peer_list, peer_size, r); 299 r += return_peers_all(peer_list, peer_size, r);
303 else 300 else
304 r += return_peers_selection(ws, peer_list, peer_size, amount, r); 301 r += return_peers_selection(ws, peer_list, peer_size, amount, r);
@@ -310,13 +307,15 @@ static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent
310 char *r = reply; 307 char *r = reply;
311 const char *r_end = ws->outbuf + G_OUTBUF_SIZE; 308 const char *r_end = ws->outbuf + G_OUTBUF_SIZE;
312 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; 309 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM;
313 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; 310 size_t count6 = peerlist_peer_count(torrent->peer_list6);
314 size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; 311 size_t count4 = peerlist_peer_count(torrent->peer_list4);
315 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - seed_count; 312 size_t seed_count = peerlist_seed_count(torrent->peer_list6) + peerlist_seed_count(torrent->peer_list4);
313 size_t down_count = torrent->down_count;
314 size_t peer_count = count6 + count4 - seed_count;
316 315
317 /* Simple case: amount of peers in both lists is less than requested, here we return all results */ 316 /* Simple case: amount of peers in both lists is less than requested, here we return all results */
318 size_t amount_v4 = torrent->peer_list4->peer_count; 317 size_t amount_v4 = count4;
319 size_t amount_v6 = torrent->peer_list6->peer_count; 318 size_t amount_v6 = count6;
320 319
321 /* Complex case: both lists have more than enough entries and we need to split between v4 and v6 clients */ 320 /* Complex case: both lists have more than enough entries and we need to split between v4 and v6 clients */
322 if (amount_v4 + amount_v6 > amount) { 321 if (amount_v4 + amount_v6 > amount) {
@@ -332,8 +331,8 @@ static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent
332 /* Fill the rest according to which family's pool provides more peers */ 331 /* Fill the rest according to which family's pool provides more peers */
333 amount_left = amount - (amount_v4 + amount_v6); 332 amount_left = amount - (amount_v4 + amount_v6);
334 333
335 left_v4 = torrent->peer_list4->peer_count - amount_v4; 334 left_v4 = count4 - amount_v4;
336 left_v6 = torrent->peer_list6->peer_count - amount_v6; 335 left_v6 = count6 - amount_v6;
337 336
338 if (left_v4 + left_v6) { 337 if (left_v4 + left_v6) {
339 percent_v4 = (SCALE * left_v4) / (left_v4 + left_v6); 338 percent_v4 = (SCALE * left_v4) / (left_v4 + left_v6);
@@ -344,9 +343,9 @@ static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent
344 amount_v6 += (amount_left * percent_v6) / SCALE; 343 amount_v6 += (amount_left * percent_v6) / SCALE;
345 344
346 /* Integer division rounding can leave out a peer */ 345 /* Integer division rounding can leave out a peer */
347 if (amount_v4 + amount_v6 < amount && amount_v6 < torrent->peer_list6->peer_count) 346 if (amount_v4 + amount_v6 < amount && amount_v6 < count6)
348 ++amount_v6; 347 ++amount_v6;
349 if (amount_v4 + amount_v6 < amount && amount_v4 < torrent->peer_list4->peer_count) 348 if (amount_v4 + amount_v6 < amount && amount_v4 < count4)
350 ++amount_v4; 349 ++amount_v4;
351 } 350 }
352 351
@@ -355,7 +354,7 @@ static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent
355 354
356 if (amount_v4) { 355 if (amount_v4) {
357 fmt_append(&r, r_end, PEERS_BENCODED4 "%zd:", OT_PEER_COMPARE_SIZE4 * amount_v4); 356 fmt_append(&r, r_end, PEERS_BENCODED4 "%zd:", OT_PEER_COMPARE_SIZE4 * amount_v4);
358 if (amount_v4 == torrent->peer_list4->peer_count) 357 if (amount_v4 == count4)
359 r += return_peers_all(torrent->peer_list4, OT_PEER_SIZE4, r); 358 r += return_peers_all(torrent->peer_list4, OT_PEER_SIZE4, r);
360 else 359 else
361 r += return_peers_selection(ws, torrent->peer_list4, OT_PEER_SIZE4, amount_v4, r); 360 r += return_peers_selection(ws, torrent->peer_list4, OT_PEER_SIZE4, amount_v4, r);
@@ -363,7 +362,7 @@ static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent
363 362
364 if (amount_v6) { 363 if (amount_v6) {
365 fmt_append(&r, r_end, PEERS_BENCODED6 "%zd:", OT_PEER_COMPARE_SIZE6 * amount_v6); 364 fmt_append(&r, r_end, PEERS_BENCODED6 "%zd:", OT_PEER_COMPARE_SIZE6 * amount_v6);
366 if (amount_v6 == torrent->peer_list6->peer_count) 365 if (amount_v6 == count6)
367 r += return_peers_all(torrent->peer_list6, OT_PEER_SIZE6, r); 366 r += return_peers_all(torrent->peer_list6, OT_PEER_SIZE6, r);
368 else 367 else
369 r += return_peers_selection(ws, torrent->peer_list6, OT_PEER_SIZE6, amount_v6, r); 368 r += return_peers_selection(ws, torrent->peer_list6, OT_PEER_SIZE6, amount_v6, r);
@@ -402,9 +401,9 @@ size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply) {
402 memset(reply, 0, 12); 401 memset(reply, 0, 12);
403 delta_torrentcount = -1; 402 delta_torrentcount = -1;
404 } else { 403 } else {
405 r[0] = htonl(torrent->peer_list6->seed_count + torrent->peer_list4->seed_count); 404 r[0] = htonl(peerlist_seed_count(torrent->peer_list6) + peerlist_seed_count(torrent->peer_list4));
406 r[1] = htonl(torrent->peer_list6->down_count + torrent->peer_list4->down_count); 405 r[1] = htonl(torrent->down_count);
407 r[2] = htonl(torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - torrent->peer_list6->seed_count - torrent->peer_list4->seed_count); 406 r[2] = htonl(peerlist_peer_count(torrent->peer_list6) + peerlist_peer_count(torrent->peer_list4) - peerlist_seed_count(torrent->peer_list6) - peerlist_seed_count(torrent->peer_list4));
408 } 407 }
409 } 408 }
410 mutex_bucket_unlock_by_hash(hash, delta_torrentcount); 409 mutex_bucket_unlock_by_hash(hash, delta_torrentcount);
@@ -431,9 +430,9 @@ void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char
431 } else { 430 } else {
432 fmt_append(reply, reply_end, "20:"); 431 fmt_append(reply, reply_end, "20:");
433 fmt_bytes(reply, reply_end, hash, sizeof(ot_hash)); 432 fmt_bytes(reply, reply_end, hash, sizeof(ot_hash));
434 fmt_append(reply, reply_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", 433 size_t seed_count = peerlist_seed_count(torrent->peer_list6) + peerlist_seed_count(torrent->peer_list4);
435 torrent->peer_list6->seed_count + torrent->peer_list4->seed_count, torrent->peer_list6->down_count + torrent->peer_list4->down_count, 434 size_t peer_count = peerlist_peer_count(torrent->peer_list6) + peerlist_peer_count(torrent->peer_list4);
436 torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - torrent->peer_list6->seed_count - torrent->peer_list4->seed_count); 435 fmt_append(reply, reply_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", seed_count, (size_t)torrent->down_count, peer_count - seed_count);
437 } 436 }
438 } 437 }
439 mutex_bucket_unlock_by_hash(*hash, delta_torrentcount); 438 mutex_bucket_unlock_by_hash(*hash, delta_torrentcount);
@@ -461,17 +460,19 @@ size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws) {
461 460
462 if (exactmatch) { 461 if (exactmatch) {
463 peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4; 462 peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4;
464 switch (vector_remove_peer(&peer_list->peers, peer_src, peer_size)) { 463 if (peer_list) {
465 case 2: 464 switch (vector_remove_peer(&peer_list->peers, peer_src, peer_size)) {
466 peer_list->seed_count--; /* Intentional fallthrough */ 465 case 2:
467 case 1: 466 peer_list->seed_count--; /* Intentional fallthrough */
468 peer_list->peer_count--; /* Intentional fallthrough */ 467 case 1:
469 default: 468 peer_list->peer_count--; /* Intentional fallthrough */
470 break; 469 default:
470 break;
471 }
471 } 472 }
472 473
473 peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; 474 peer_count = peerlist_peer_count(torrent->peer_list6) + peerlist_peer_count(torrent->peer_list4);
474 seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; 475 seed_count = peerlist_seed_count(torrent->peer_list6) + peerlist_seed_count(torrent->peer_list4);
475 } 476 }
476 477
477 if (proto == FLAG_TCP) { 478 if (proto == FLAG_TCP) {
diff --git a/src/trackerlogic.h b/src/trackerlogic.h
index 241b6a7..6b53406 100644
--- a/src/trackerlogic.h
+++ b/src/trackerlogic.h
@@ -165,17 +165,17 @@ struct ot_peerlist;
165typedef struct ot_peerlist ot_peerlist; 165typedef struct ot_peerlist ot_peerlist;
166typedef struct { 166typedef struct {
167 ot_hash hash; 167 ot_hash hash;
168 ot_peerlist *peer_list6; 168 ot_peerlist *peer_list6; /* NULL until the torrent has a v6 peer */
169 ot_peerlist *peer_list4; 169 ot_peerlist *peer_list4; /* NULL until the torrent has a v4 peer */
170 uint32_t base; /* minutes, when the torrent was last cleaned */
171 uint32_t down_count; /* completed events, summed over both families */
170} ot_torrent; 172} ot_torrent;
171 173
172#include "ot_vector.h" 174#include "ot_vector.h"
173 175
174struct ot_peerlist { 176struct ot_peerlist {
175 ot_time base; 177 uint32_t seed_count;
176 size_t seed_count; 178 uint32_t peer_count;
177 size_t peer_count;
178 size_t down_count;
179 /* normal peers vector or 179 /* normal peers vector or
180 pointer to ot_vector[32] buckets if data != NULL and space == 0 180 pointer to ot_vector[32] buckets if data != NULL and space == 0
181 */ 181 */
@@ -183,6 +183,9 @@ struct ot_peerlist {
183}; 183};
184#define OT_PEERLIST_HASBUCKETS(peer_list) ((peer_list)->peers.size > (peer_list)->peers.space) 184#define OT_PEERLIST_HASBUCKETS(peer_list) ((peer_list)->peers.size > (peer_list)->peers.space)
185 185
186static inline uint32_t peerlist_peer_count(const ot_peerlist *peer_list) { return peer_list ? peer_list->peer_count : 0; }
187static inline uint32_t peerlist_seed_count(const ot_peerlist *peer_list) { return peer_list ? peer_list->seed_count : 0; }
188
186/* The http replies the tracker counts when it has to refuse a request. */ 189/* The http replies the tracker counts when it has to refuse a request. */
187enum { 190enum {
188 CODE_HTTPERROR_302, 191 CODE_HTTPERROR_302,
diff --git a/tests/t_clean.c b/tests/t_clean.c
index 7eca9aa..2fadd03 100644
--- a/tests/t_clean.c
+++ b/tests/t_clean.c
@@ -11,7 +11,7 @@
11#include "trackerlogic.h" 11#include "trackerlogic.h"
12#include "test.h" 12#include "test.h"
13 13
14/* A clock stepped backwards used to leave peer_list->base in the future, so 14/* A clock stepped backwards used to leave the torrent's base in the future, so
15 the span the cleaner works from came out negative. It was then added to a 15 the span the cleaner works from came out negative. It was then added to a
16 peer's age and stored in the single byte that age lives in, where -1 reads 16 peer's age and stored in the single byte that age lives in, where -1 reads
17 as 255 and the next pass threw the whole swarm away. */ 17 as 255 and the next pass threw the whole swarm away. */
@@ -60,14 +60,12 @@ static ot_torrent *torrent_of(ot_hash *hash) {
60 60
61/* What a clock stepped back by `minutes` leaves behind */ 61/* What a clock stepped back by `minutes` leaves behind */
62static void step_clock_back(ot_torrent *torrent, int minutes) { 62static void step_clock_back(ot_torrent *torrent, int minutes) {
63 torrent->peer_list6->base += minutes; 63 torrent->base += minutes;
64 torrent->peer_list4->base += minutes;
65} 64}
66 65
67/* What the next minute of a well behaved clock looks like */ 66/* What the next minute of a well behaved clock looks like */
68static void age_by_a_minute(ot_torrent *torrent) { 67static void age_by_a_minute(ot_torrent *torrent) {
69 torrent->peer_list6->base -= 1; 68 torrent->base -= 1;
70 torrent->peer_list4->base -= 1;
71} 69}
72 70
73static void case_a_clock_that_went_back_keeps_the_swarm(void) { 71static void case_a_clock_that_went_back_keeps_the_swarm(void) {