summaryrefslogtreecommitdiff
path: root/src/trackerlogic.c
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-11-07 19:39:01 +0100
committerDirk Engling <erdgeist@erdgeist.org>2026-11-07 19:39:01 +0100
commit589cb500fa5a6df13fa9634d0071922ec11fce17 (patch)
treefa5fc07d6d13d7b241d884441d3d22c1d0189d87 /src/trackerlogic.c
parent1e7248b330bde4c70528ccf91535391e19294dc3 (diff)
Unify the peerlists for v4 and v6
This commit finishes what started with unifying v4 and v6 trackers, from then on, torrents carried a peer list per family, with obvious redundancies. With the race to save memory, these became burdening enough that now these peerlist pointers have been moved into a single struct, holding both v4 and v6 peers in their respective ot_vectors, so the per- torrent cost shrunk by another pointer's size. Each peerlist also lost its peer count cache, as in most cases, it's trivial to load it directly from the ot_vector's entry count. The seed counts that were kept per family only to be aggregated later for every consumer, are also merged now. Total savings depend on the tracker's actual load. In some corner cases, where only one address family dominates the torrents, we actually use a few bytes more in the ot_vector for the absent family, but even that mostly becomes noise: before it actually adds to the memory load, the peer payload will be the deciding factor. Most wins are on what often is experienced on trackers: A long tail of abandoned torrents now needs eight fewer bytes, slipping into a smaller allocation unit on glibc's allocator.
Diffstat (limited to 'src/trackerlogic.c')
-rw-r--r--src/trackerlogic.c118
1 files changed, 55 insertions, 63 deletions
diff --git a/src/trackerlogic.c b/src/trackerlogic.c
index 6b0659c..4ba3a41 100644
--- a/src/trackerlogic.c
+++ b/src/trackerlogic.c
@@ -28,13 +28,18 @@
28/* Forward declaration */ 28/* Forward declaration */
29size_t return_peers_for_torrent(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply, PROTO_FLAG proto); 29size_t return_peers_for_torrent(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply, PROTO_FLAG proto);
30 30
31void free_peer_vector(ot_vector *peer_vector) {
32 if (OT_VECTOR_HASBUCKETS(peer_vector))
33 vector_clean_list((ot_vector *)peer_vector->data, peer_vector->size);
34 else
35 free(peer_vector->data); /* free(NULL) on an absent family is fine */
36}
37
31void free_peerlist(ot_peerlist *peer_list) { 38void free_peerlist(ot_peerlist *peer_list) {
32 if (peer_list && peer_list->peers.data) { 39 if (!peer_list)
33 if (OT_PEERLIST_HASBUCKETS(peer_list)) 40 return;
34 vector_clean_list((ot_vector *)peer_list->peers.data, peer_list->peers.size); 41 free_peer_vector(&peer_list->peers6);
35 else 42 free_peer_vector(&peer_list->peers4);
36 free(peer_list->peers.data);
37 }
38 free(peer_list); 43 free(peer_list);
39} 44}
40 45
@@ -113,20 +118,17 @@ size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstru
113 118
114 torrent->base = time_now_minutes(); 119 torrent->base = time_now_minutes();
115 120
116 /* The announcing peer's family list is created on first use */ 121 /* The peerlist, holding both families, is created on the first peer */
117 { 122 if (!torrent->peer_list && !(torrent->peer_list = calloc(1, sizeof(ot_peerlist)))) {
118 ot_peerlist **slot = peer_size == OT_PEER_SIZE6 ? &torrent->peer_list6 : &torrent->peer_list4; 123 if (delta_torrentcount)
119 if (!*slot && !(*slot = calloc(1, sizeof(ot_peerlist)))) { 124 vector_remove_torrent(torrents_list, torrent);
120 if (delta_torrentcount) 125 mutex_bucket_unlock_by_hash(*ws->hash, 0);
121 vector_remove_torrent(torrents_list, torrent); 126 return 0;
122 mutex_bucket_unlock_by_hash(*ws->hash, 0);
123 return 0;
124 }
125 peer_list = *slot;
126 } 127 }
128 peer_list = torrent->peer_list;
127 129
128 /* Check for peer in torrent */ 130 /* Check for peer in its family's vector */
129 peer_dest = vector_find_or_insert_peer(&(peer_list->peers), peer_src, peer_size, &exactmatch); 131 peer_dest = vector_find_or_insert_peer(peer_size == OT_PEER_SIZE6 ? &peer_list->peers6 : &peer_list->peers4, peer_src, peer_size, &exactmatch);
130 if (!peer_dest) { 132 if (!peer_dest) {
131 mutex_bucket_unlock_by_hash(*ws->hash, delta_torrentcount); 133 mutex_bucket_unlock_by_hash(*ws->hash, delta_torrentcount);
132 return 0; 134 return 0;
@@ -149,7 +151,6 @@ size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstru
149 livesync_tell(ws); 151 livesync_tell(ws);
150#endif 152#endif
151 153
152 peer_list->peer_count++;
153 if (OT_PEERFLAG(ws->peer) & PEER_FLAG_COMPLETED) { 154 if (OT_PEERFLAG(ws->peer) & PEER_FLAG_COMPLETED) {
154 torrent->down_count++; 155 torrent->down_count++;
155 stats_issue_event(ws, EVENT_COMPLETED, 0, 0); 156 stats_issue_event(ws, EVENT_COMPLETED, 0, 0);
@@ -198,14 +199,14 @@ size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstru
198 return ws->reply_size; 199 return ws->reply_size;
199} 200}
200 201
201static size_t return_peers_all(ot_peerlist *peer_list, size_t peer_size, char *reply) { 202static size_t return_peers_all(ot_vector *peer_vector, size_t peer_size, size_t peer_count, char *reply) {
202 size_t bucket, num_buckets = 1; 203 size_t bucket, num_buckets = 1;
203 ot_vector *bucket_list = &peer_list->peers; 204 ot_vector *bucket_list = peer_vector;
204 size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size); 205 size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size);
205 size_t result = compare_size * peer_list->peer_count; 206 size_t result = compare_size * peer_count;
206 char *r_end = reply + result; 207 char *r_end = reply + result;
207 208
208 if (OT_PEERLIST_HASBUCKETS(peer_list)) { 209 if (OT_VECTOR_HASBUCKETS(peer_vector)) {
209 num_buckets = bucket_list->size; 210 num_buckets = bucket_list->size;
210 bucket_list = (ot_vector *)bucket_list->data; 211 bucket_list = (ot_vector *)bucket_list->data;
211 } 212 }
@@ -227,17 +228,17 @@ static size_t return_peers_all(ot_peerlist *peer_list, size_t peer_size, char *r
227 return result; 228 return result;
228} 229}
229 230
230static size_t return_peers_selection(struct ot_workstruct *ws, ot_peerlist *peer_list, size_t peer_size, size_t amount, char *reply) { 231static size_t return_peers_selection(struct ot_workstruct *ws, ot_vector *peer_vector, size_t peer_size, size_t amount, size_t peer_count, char *reply) {
231 size_t bucket_offset, bucket_index = 0, num_buckets = 1; 232 size_t bucket_offset, bucket_index = 0, num_buckets = 1;
232 ot_vector *bucket_list = &peer_list->peers; 233 ot_vector *bucket_list = peer_vector;
233 unsigned int shifted_pc = peer_list->peer_count; 234 unsigned int shifted_pc = peer_count;
234 unsigned int shifted_step = 0; 235 unsigned int shifted_step = 0;
235 unsigned int shift = 0; 236 unsigned int shift = 0;
236 size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size); 237 size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size);
237 size_t result = compare_size * amount; 238 size_t result = compare_size * amount;
238 char *r_end = reply + result; 239 char *r_end = reply + result;
239 240
240 if (OT_PEERLIST_HASBUCKETS(peer_list)) { 241 if (OT_VECTOR_HASBUCKETS(peer_vector)) {
241 num_buckets = bucket_list->size; 242 num_buckets = bucket_list->size;
242 bucket_list = (ot_vector *)bucket_list->data; 243 bucket_list = (ot_vector *)bucket_list->data;
243 } 244 }
@@ -253,7 +254,7 @@ static size_t return_peers_selection(struct ot_workstruct *ws, ot_peerlist *peer
253 254
254 /* Initialize somewhere in the middle of peers so that 255 /* Initialize somewhere in the middle of peers so that
255 fixpoint's aliasing doesn't alway miss the same peers */ 256 fixpoint's aliasing doesn't alway miss the same peers */
256 bucket_offset = nrand48(ws->rand48_state) % peer_list->peer_count; 257 bucket_offset = nrand48(ws->rand48_state) % peer_count;
257 258
258 while (amount--) { 259 while (amount--) {
259 ot_peer *peer; 260 ot_peer *peer;
@@ -281,10 +282,11 @@ static size_t return_peers_selection(struct ot_workstruct *ws, ot_peerlist *peer
281static size_t return_peers_for_torrent_udp(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply) { 282static size_t return_peers_for_torrent_udp(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply) {
282 char *r = reply; 283 char *r = reply;
283 size_t peer_size = peer_size_from_peer6(&ws->peer); 284 size_t peer_size = peer_size_from_peer6(&ws->peer);
284 ot_peerlist *peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4; 285 ot_peerlist *peer_list = torrent->peer_list;
285 size_t list_count = peerlist_peer_count(peer_list); 286 ot_vector *family = peer_size == OT_PEER_SIZE6 ? &peer_list->peers6 : &peer_list->peers4;
286 size_t peer_count = peerlist_peer_count(torrent->peer_list6) + peerlist_peer_count(torrent->peer_list4); 287 size_t list_count = vector_peer_count(family);
287 size_t seed_count = peerlist_seed_count(torrent->peer_list6) + peerlist_seed_count(torrent->peer_list4); 288 size_t peer_count = torrent_peer_count(torrent);
289 size_t seed_count = torrent_seed_count(torrent);
288 290
289 if (amount > list_count) 291 if (amount > list_count)
290 amount = list_count; 292 amount = list_count;
@@ -296,9 +298,9 @@ static size_t return_peers_for_torrent_udp(struct ot_workstruct *ws, ot_torrent
296 298
297 if (amount) { 299 if (amount) {
298 if (amount == list_count) 300 if (amount == list_count)
299 r += return_peers_all(peer_list, peer_size, r); 301 r += return_peers_all(family, peer_size, list_count, r);
300 else 302 else
301 r += return_peers_selection(ws, peer_list, peer_size, amount, r); 303 r += return_peers_selection(ws, family, peer_size, amount, list_count, r);
302 } 304 }
303 return r - reply; 305 return r - reply;
304} 306}
@@ -307,9 +309,10 @@ static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent
307 char *r = reply; 309 char *r = reply;
308 const char *r_end = ws->outbuf + G_OUTBUF_SIZE; 310 const char *r_end = ws->outbuf + G_OUTBUF_SIZE;
309 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; 311 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM;
310 size_t count6 = peerlist_peer_count(torrent->peer_list6); 312 ot_peerlist *peer_list = torrent->peer_list;
311 size_t count4 = peerlist_peer_count(torrent->peer_list4); 313 size_t count6 = vector_peer_count(&peer_list->peers6);
312 size_t seed_count = peerlist_seed_count(torrent->peer_list6) + peerlist_seed_count(torrent->peer_list4); 314 size_t count4 = vector_peer_count(&peer_list->peers4);
315 size_t seed_count = peer_list->seed_count;
313 size_t down_count = torrent->down_count; 316 size_t down_count = torrent->down_count;
314 size_t peer_count = count6 + count4 - seed_count; 317 size_t peer_count = count6 + count4 - seed_count;
315 318
@@ -355,17 +358,17 @@ static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent
355 if (amount_v4) { 358 if (amount_v4) {
356 fmt_append(&r, r_end, PEERS_BENCODED4 "%zd:", OT_PEER_COMPARE_SIZE4 * amount_v4); 359 fmt_append(&r, r_end, PEERS_BENCODED4 "%zd:", OT_PEER_COMPARE_SIZE4 * amount_v4);
357 if (amount_v4 == count4) 360 if (amount_v4 == count4)
358 r += return_peers_all(torrent->peer_list4, OT_PEER_SIZE4, r); 361 r += return_peers_all(&peer_list->peers4, OT_PEER_SIZE4, count4, r);
359 else 362 else
360 r += return_peers_selection(ws, torrent->peer_list4, OT_PEER_SIZE4, amount_v4, r); 363 r += return_peers_selection(ws, &peer_list->peers4, OT_PEER_SIZE4, amount_v4, count4, r);
361 } 364 }
362 365
363 if (amount_v6) { 366 if (amount_v6) {
364 fmt_append(&r, r_end, PEERS_BENCODED6 "%zd:", OT_PEER_COMPARE_SIZE6 * amount_v6); 367 fmt_append(&r, r_end, PEERS_BENCODED6 "%zd:", OT_PEER_COMPARE_SIZE6 * amount_v6);
365 if (amount_v6 == count6) 368 if (amount_v6 == count6)
366 r += return_peers_all(torrent->peer_list6, OT_PEER_SIZE6, r); 369 r += return_peers_all(&peer_list->peers6, OT_PEER_SIZE6, count6, r);
367 else 370 else
368 r += return_peers_selection(ws, torrent->peer_list6, OT_PEER_SIZE6, amount_v6, r); 371 r += return_peers_selection(ws, &peer_list->peers6, OT_PEER_SIZE6, amount_v6, count6, r);
369 } 372 }
370 373
371 fmt_append(&r, r_end, "e"); 374 fmt_append(&r, r_end, "e");
@@ -401,9 +404,9 @@ size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply) {
401 memset(reply, 0, 12); 404 memset(reply, 0, 12);
402 delta_torrentcount = -1; 405 delta_torrentcount = -1;
403 } else { 406 } else {
404 r[0] = htonl(peerlist_seed_count(torrent->peer_list6) + peerlist_seed_count(torrent->peer_list4)); 407 r[0] = htonl(torrent_seed_count(torrent));
405 r[1] = htonl(torrent->down_count); 408 r[1] = htonl(torrent->down_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)); 409 r[2] = htonl(torrent_peer_count(torrent) - torrent_seed_count(torrent));
407 } 410 }
408 } 411 }
409 mutex_bucket_unlock_by_hash(hash, delta_torrentcount); 412 mutex_bucket_unlock_by_hash(hash, delta_torrentcount);
@@ -430,8 +433,8 @@ void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char
430 } else { 433 } else {
431 fmt_append(reply, reply_end, "20:"); 434 fmt_append(reply, reply_end, "20:");
432 fmt_bytes(reply, reply_end, hash, sizeof(ot_hash)); 435 fmt_bytes(reply, reply_end, hash, sizeof(ot_hash));
433 size_t seed_count = peerlist_seed_count(torrent->peer_list6) + peerlist_seed_count(torrent->peer_list4); 436 size_t seed_count = torrent_seed_count(torrent);
434 size_t peer_count = peerlist_peer_count(torrent->peer_list6) + peerlist_peer_count(torrent->peer_list4); 437 size_t peer_count = torrent_peer_count(torrent);
435 fmt_append(reply, reply_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", seed_count, (size_t)torrent->down_count, peer_count - seed_count); 438 fmt_append(reply, reply_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", seed_count, (size_t)torrent->down_count, peer_count - seed_count);
436 } 439 }
437 } 440 }
@@ -441,12 +444,10 @@ void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char
441 fmt_append(reply, reply_end, "ee"); 444 fmt_append(reply, reply_end, "ee");
442} 445}
443 446
444static ot_peerlist dummy_list;
445size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws) { 447size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws) {
446 int exactmatch; 448 int exactmatch;
447 ot_vector *torrents_list = mutex_bucket_lock_by_hash(*ws->hash); 449 ot_vector *torrents_list = mutex_bucket_lock_by_hash(*ws->hash);
448 ot_torrent *torrent = vector_binary_search(ws->hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch); 450 ot_torrent *torrent = vector_binary_search(ws->hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch);
449 ot_peerlist *peer_list = &dummy_list;
450 size_t peer_size; /* initialized in next line */ 451 size_t peer_size; /* initialized in next line */
451 ot_peer const *peer_src = peer_from_peer6(&ws->peer, &peer_size); 452 ot_peer const *peer_src = peer_from_peer6(&ws->peer, &peer_size);
452 size_t peer_count = 0, seed_count = 0; 453 size_t peer_count = 0, seed_count = 0;
@@ -458,21 +459,13 @@ size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws) {
458 } 459 }
459#endif 460#endif
460 461
461 if (exactmatch) { 462 if (exactmatch && torrent->peer_list) {
462 peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4; 463 ot_vector *family = peer_size == OT_PEER_SIZE6 ? &torrent->peer_list->peers6 : &torrent->peer_list->peers4;
463 if (peer_list) { 464 if (vector_remove_peer(family, peer_src, peer_size) == 2)
464 switch (vector_remove_peer(&peer_list->peers, peer_src, peer_size)) { 465 torrent->peer_list->seed_count--;
465 case 2:
466 peer_list->seed_count--; /* Intentional fallthrough */
467 case 1:
468 peer_list->peer_count--; /* Intentional fallthrough */
469 default:
470 break;
471 }
472 }
473 466
474 peer_count = peerlist_peer_count(torrent->peer_list6) + peerlist_peer_count(torrent->peer_list4); 467 peer_count = torrent_peer_count(torrent);
475 seed_count = peerlist_seed_count(torrent->peer_list6) + peerlist_seed_count(torrent->peer_list4); 468 seed_count = torrent_seed_count(torrent);
476 } 469 }
477 470
478 if (proto == FLAG_TCP) { 471 if (proto == FLAG_TCP) {
@@ -621,8 +614,7 @@ void trackerlogic_deinit(void) {
621 if (torrents_list->size) { 614 if (torrents_list->size) {
622 for (j = 0; j < torrents_list->size; ++j) { 615 for (j = 0; j < torrents_list->size; ++j) {
623 ot_torrent *torrent = ((ot_torrent *)(torrents_list->data)) + j; 616 ot_torrent *torrent = ((ot_torrent *)(torrents_list->data)) + j;
624 free_peerlist(torrent->peer_list6); 617 free_peerlist(torrent->peer_list);
625 free_peerlist(torrent->peer_list4);
626 delta_torrentcount -= 1; 618 delta_torrentcount -= 1;
627 } 619 }
628 free(torrents_list->data); 620 free(torrents_list->data);