diff options
| author | Dirk Engling <erdgeist@erdgeist.org> | 2026-11-07 19:39:01 +0100 |
|---|---|---|
| committer | Dirk Engling <erdgeist@erdgeist.org> | 2026-11-07 19:39:01 +0100 |
| commit | 589cb500fa5a6df13fa9634d0071922ec11fce17 (patch) | |
| tree | fa5fc07d6d13d7b241d884441d3d22c1d0189d87 /src/trackerlogic.c | |
| parent | 1e7248b330bde4c70528ccf91535391e19294dc3 (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.c | 118 |
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 */ |
| 29 | size_t return_peers_for_torrent(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply, PROTO_FLAG proto); | 29 | size_t return_peers_for_torrent(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply, PROTO_FLAG proto); |
| 30 | 30 | ||
| 31 | void 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 | |||
| 31 | void free_peerlist(ot_peerlist *peer_list) { | 38 | void 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 | ||
| 201 | static size_t return_peers_all(ot_peerlist *peer_list, size_t peer_size, char *reply) { | 202 | static 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 | ||
| 230 | static size_t return_peers_selection(struct ot_workstruct *ws, ot_peerlist *peer_list, size_t peer_size, size_t amount, char *reply) { | 231 | static 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 | |||
| 281 | static size_t return_peers_for_torrent_udp(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply) { | 282 | static 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 | ||
| 444 | static ot_peerlist dummy_list; | ||
| 445 | size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws) { | 447 | size_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); |
