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/ot_clean.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/ot_clean.c')
| -rw-r--r-- | src/ot_clean.c | 64 |
1 files changed, 35 insertions, 29 deletions
diff --git a/src/ot_clean.c b/src/ot_clean.c index 9e2186f..130bd2a 100644 --- a/src/ot_clean.c +++ b/src/ot_clean.c | |||
| @@ -51,51 +51,48 @@ static ssize_t clean_single_bucket(ot_peer *peers, size_t peer_count, size_t pee | |||
| 51 | return (peers - insert_point) / peer_size; | 51 | return (peers - insert_point) / peer_size; |
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | /* Age one family's peers by timedout minutes. Frees the list and clears the | 54 | /* Age one family's peers by timedout minutes. Releases and zeroes the vector |
| 55 | slot once its last peer is gone, so an absent family costs nothing. */ | 55 | once its last peer is gone, so an empty family costs nothing. */ |
| 56 | static void clean_peer_list_family(ot_peerlist **slot, size_t peer_size, ot_time timedout) { | 56 | static void clean_family_vector(ot_vector *peer_vector, size_t peer_size, ot_time timedout, size_t *removed_seeders) { |
| 57 | ot_peerlist *peer_list = *slot; | 57 | ot_vector *bucket = peer_vector; |
| 58 | ot_vector *peer_vector; | 58 | size_t num_buckets = 1, peer_count; |
| 59 | size_t num_buckets = 1, removed_seeders = 0; | ||
| 60 | 59 | ||
| 61 | if (!peer_list) | 60 | if (OT_VECTOR_HASBUCKETS(peer_vector)) { |
| 62 | return; | ||
| 63 | |||
| 64 | peer_vector = &peer_list->peers; | ||
| 65 | if (OT_PEERLIST_HASBUCKETS(peer_list)) { | ||
| 66 | num_buckets = peer_vector->size; | 61 | num_buckets = peer_vector->size; |
| 67 | peer_vector = (ot_vector *)peer_vector->data; | 62 | bucket = (ot_vector *)peer_vector->data; |
| 68 | } | 63 | } |
| 69 | 64 | ||
| 70 | while (num_buckets--) { | 65 | while (num_buckets--) { |
| 71 | size_t removed_peers = clean_single_bucket(peer_vector->data, peer_vector->size, peer_size, timedout, &removed_seeders); | 66 | size_t removed_peers = clean_single_bucket(bucket->data, bucket->size, peer_size, timedout, removed_seeders); |
| 72 | peer_list->peer_count -= removed_peers; | 67 | bucket->size -= removed_peers; |
| 73 | peer_vector->size -= removed_peers; | ||
| 74 | if (removed_peers) | 68 | if (removed_peers) |
| 75 | vector_fixup_peers(peer_vector, peer_size); | 69 | vector_fixup_peers(bucket, peer_size); |
| 76 | 70 | ||
| 77 | /* Skip to next bucket, a vector containing peers */ | 71 | /* Skip to next bucket, a vector containing peers */ |
| 78 | ++peer_vector; | 72 | ++bucket; |
| 79 | } | 73 | } |
| 80 | 74 | ||
| 81 | peer_list->seed_count -= removed_seeders; | 75 | peer_count = vector_peer_count(peer_vector); |
| 82 | 76 | ||
| 83 | if (!peer_list->peer_count) { | 77 | /* Run dry: release the family (a collapsed bucket list or a plain vector) */ |
| 84 | free_peerlist(peer_list); | 78 | if (!peer_count) { |
| 85 | *slot = NULL; | 79 | free_peer_vector(peer_vector); |
| 80 | memset(peer_vector, 0, sizeof(*peer_vector)); | ||
| 86 | return; | 81 | return; |
| 87 | } | 82 | } |
| 88 | 83 | ||
| 89 | /* See if we need to convert the list from simple vector to bucket list */ | 84 | /* See if we need to convert the vector from simple to bucket list or back */ |
| 90 | if ((peer_list->peer_count > OT_PEER_BUCKET_MINCOUNT) || OT_PEERLIST_HASBUCKETS(peer_list)) | 85 | if ((peer_count > OT_PEER_BUCKET_MINCOUNT) || OT_VECTOR_HASBUCKETS(peer_vector)) |
| 91 | vector_redistribute_buckets(peer_list, peer_size); | 86 | vector_redistribute_buckets(peer_vector, peer_size, peer_count); |
| 92 | } | 87 | } |
| 93 | 88 | ||
| 94 | /* Clean a single torrent | 89 | /* Clean a single torrent |
| 95 | return 1 if torrent timed out | 90 | return 1 if torrent timed out |
| 96 | */ | 91 | */ |
| 97 | int clean_single_torrent(ot_torrent *torrent) { | 92 | int clean_single_torrent(ot_torrent *torrent) { |
| 98 | ot_time timedout = time_since_minutes(torrent->base); | 93 | ot_time timedout = time_since_minutes(torrent->base); |
| 94 | ot_peerlist *peer_list = torrent->peer_list; | ||
| 95 | size_t removed_seeders = 0; | ||
| 99 | 96 | ||
| 100 | /* Touched since the last sweep: nothing to do */ | 97 | /* Touched since the last sweep: nothing to do */ |
| 101 | if (!timedout) | 98 | if (!timedout) |
| @@ -107,7 +104,7 @@ int clean_single_torrent(ot_torrent *torrent) { | |||
| 107 | 104 | ||
| 108 | /* Already empty and nothing arrived before the peer timeout: keep it only | 105 | /* Already empty and nothing arrived before the peer timeout: keep it only |
| 109 | while a down count is worth remembering */ | 106 | while a down count is worth remembering */ |
| 110 | if (timedout > OT_PEER_TIMEOUT && !torrent->peer_list6 && !torrent->peer_list4) | 107 | if (timedout > OT_PEER_TIMEOUT && !peer_list) |
| 111 | return (torrent->down_count && timedout <= g_empty_torrent_timeout) ? 0 : 1; | 108 | return (torrent->down_count && timedout <= g_empty_torrent_timeout) ? 0 : 1; |
| 112 | 109 | ||
| 113 | /* Peers past the peer timeout are gone regardless, which caps how far the | 110 | /* Peers past the peer timeout are gone regardless, which caps how far the |
| @@ -115,10 +112,19 @@ int clean_single_torrent(ot_torrent *torrent) { | |||
| 115 | if (timedout > OT_PEER_TIMEOUT) | 112 | if (timedout > OT_PEER_TIMEOUT) |
| 116 | timedout = OT_PEER_TIMEOUT; | 113 | timedout = OT_PEER_TIMEOUT; |
| 117 | 114 | ||
| 118 | clean_peer_list_family(&torrent->peer_list6, OT_PEER_SIZE6, timedout); | 115 | if (peer_list) { |
| 119 | clean_peer_list_family(&torrent->peer_list4, OT_PEER_SIZE4, timedout); | 116 | clean_family_vector(&peer_list->peers6, OT_PEER_SIZE6, timedout, &removed_seeders); |
| 117 | clean_family_vector(&peer_list->peers4, OT_PEER_SIZE4, timedout, &removed_seeders); | ||
| 118 | peer_list->seed_count -= removed_seeders; | ||
| 119 | |||
| 120 | /* Both families gone: drop the peerlist, the torrent keeps only its count */ | ||
| 121 | if (!vector_peer_count(&peer_list->peers6) && !vector_peer_count(&peer_list->peers4)) { | ||
| 122 | free_peerlist(peer_list); | ||
| 123 | torrent->peer_list = peer_list = NULL; | ||
| 124 | } | ||
| 125 | } | ||
| 120 | 126 | ||
| 121 | if (torrent->peer_list6 || torrent->peer_list4) | 127 | if (peer_list) |
| 122 | torrent->base = time_now_minutes(); /* still carrying peers */ | 128 | torrent->base = time_now_minutes(); /* still carrying peers */ |
| 123 | else { | 129 | else { |
| 124 | /* Fell empty: hold base a peer timeout back so a later idle sweep makes | 130 | /* Fell empty: hold base a peer timeout back so a later idle sweep makes |
