summaryrefslogtreecommitdiff
path: root/tests
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 /tests
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 'tests')
-rw-r--r--tests/t_clean.c48
-rw-r--r--tests/t_vector.c61
2 files changed, 78 insertions, 31 deletions
diff --git a/tests/t_clean.c b/tests/t_clean.c
index 0237cc6..ce17bc3 100644
--- a/tests/t_clean.c
+++ b/tests/t_clean.c
@@ -33,6 +33,20 @@ static void announce(struct ot_workstruct *ws, ot_hash *hash, uint8_t last_octet
33 add_peer_to_torrent_and_return_peers(FLAG_TCP, ws, 0); 33 add_peer_to_torrent_and_return_peers(FLAG_TCP, ws, 0);
34} 34}
35 35
36/* A distinct v4 peer addressed by two octets, to build swarms past 256 */
37static size_t announce_wide(struct ot_workstruct *ws, ot_hash *hash, unsigned int which, size_t amount) {
38 static const uint8_t v4mapped[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
39
40 ws->reply = ws->outbuf;
41 memset(&ws->peer, 0, sizeof(ws->peer));
42 memcpy(&ws->peer, v4mapped, sizeof(v4mapped));
43 ((uint8_t *)&ws->peer)[14] = (uint8_t)(which >> 8);
44 ((uint8_t *)&ws->peer)[15] = (uint8_t)which;
45 OT_SETPORT(&ws->peer, &(uint16_t){6881});
46 ws->hash = hash;
47 return add_peer_to_torrent_and_return_peers(FLAG_TCP, ws, amount);
48}
49
36static size_t peers_in(ot_hash *hash) { 50static size_t peers_in(ot_hash *hash) {
37 char reply[1024]; 51 char reply[1024];
38 size_t len = return_udp_scrape_for_torrent(*hash, reply); 52 size_t len = return_udp_scrape_for_torrent(*hash, reply);
@@ -157,6 +171,39 @@ static void case_empty_torrent_timeout_bounds_a_counter_only_torrent(void) {
157 workstruct_deinit(&ws); 171 workstruct_deinit(&ws);
158} 172}
159 173
174static void case_a_bucketed_swarm_survives_clean_and_serves(void) {
175 struct ot_workstruct ws;
176 ot_hash hash;
177 ot_torrent *torrent;
178 unsigned int i;
179 const unsigned int n = 600; /* past OT_PEER_BUCKET_MINCOUNT (512) */
180
181 test_case("a swarm large enough to be bucketed cleans, splits and serves without loss");
182 memset(hash, 0x34, sizeof(hash));
183 CHECK_INT(workstruct_init(&ws, G_INBUF_SIZE), 0);
184
185 for (i = 0; i < n; ++i)
186 announce_wide(&ws, &hash, i, 0);
187 CHECK_INT(peers_in(&hash), n);
188
189 torrent = torrent_of(&hash);
190 CHECK(torrent != NULL);
191 CHECK(torrent->peer_list != NULL);
192
193 /* A clean with a minute elapsed splits the flat vector into buckets */
194 step_clock_back(torrent, -1);
195 CHECK_INT(clean_single_torrent(torrent), 0);
196 CHECK(torrent->peer_list != NULL);
197 CHECK_INT(OT_VECTOR_HASBUCKETS(&torrent->peer_list->peers4), 1);
198 CHECK_INT(peers_in(&hash), n);
199
200 /* Serve a selection back, which walks the bucket list */
201 CHECK(announce_wide(&ws, &hash, 1, 200) > 0);
202 CHECK_INT(peers_in(&hash), n);
203
204 workstruct_deinit(&ws);
205}
206
160void test_clean(void) { 207void test_clean(void) {
161 sigset_t hup; 208 sigset_t hup;
162 209
@@ -170,5 +217,6 @@ void test_clean(void) {
170 case_a_clock_that_went_back_keeps_the_swarm(); 217 case_a_clock_that_went_back_keeps_the_swarm();
171 case_an_idle_swarm_still_ages_out(); 218 case_an_idle_swarm_still_ages_out();
172 case_empty_torrent_timeout_bounds_a_counter_only_torrent(); 219 case_empty_torrent_timeout_bounds_a_counter_only_torrent();
220 case_a_bucketed_swarm_survives_clean_and_serves();
173 trackerlogic_deinit(); 221 trackerlogic_deinit();
174} 222}
diff --git a/tests/t_vector.c b/tests/t_vector.c
index f0a9bf7..42ce31a 100644
--- a/tests/t_vector.c
+++ b/tests/t_vector.c
@@ -200,17 +200,16 @@ static void case_insert_compare_prefix(void) {
200 into the first address byte, so the peers are distinct but arrive in no 200 into the first address byte, so the peers are distinct but arrive in no
201 particular order: a list generated in ascending order would come back 201 particular order: a list generated in ascending order would come back
202 sorted whether or not vector_redistribute_buckets sorted it. */ 202 sorted whether or not vector_redistribute_buckets sorted it. */
203static void build_flat_peerlist(ot_peerlist *peer_list, size_t count) { 203static void build_flat_vector(ot_vector *peers, size_t count) {
204 size_t i; 204 size_t i;
205 205
206 memset(peer_list, 0, sizeof(*peer_list)); 206 memset(peers, 0, sizeof(*peers));
207 peer_list->peers.data = malloc(count * OT_PEER_SIZE4); 207 peers->data = malloc(count * OT_PEER_SIZE4);
208 peer_list->peers.size = count; 208 peers->size = count;
209 peer_list->peers.space = count; 209 peers->space = count;
210 peer_list->peer_count = count;
211 210
212 for (i = 0; i < count; ++i) { 211 for (i = 0; i < count; ++i) {
213 uint8_t *peer = (uint8_t *)peer_list->peers.data + i * OT_PEER_SIZE4; 212 uint8_t *peer = (uint8_t *)peers->data + i * OT_PEER_SIZE4;
214 213
215 memset(peer, 0, OT_PEER_SIZE4); 214 memset(peer, 0, OT_PEER_SIZE4);
216 peer[0] = (uint8_t)i; 215 peer[0] = (uint8_t)i;
@@ -222,19 +221,19 @@ static void build_flat_peerlist(ot_peerlist *peer_list, size_t count) {
222 221
223static void case_redistribute_holds_every_peer(void) { 222static void case_redistribute_holds_every_peer(void) {
224 const size_t count = 513; 223 const size_t count = 513;
225 ot_peerlist peer_list; 224 ot_vector peers;
226 size_t bucket, total_size = 0, empty = 0, largest = 0; 225 size_t bucket, total_size = 0, empty = 0, largest = 0;
227 ot_vector *buckets; 226 ot_vector *buckets;
228 227
229 test_case("a peer list past the threshold is split into buckets"); 228 test_case("a peer list past the threshold is split into buckets");
230 build_flat_peerlist(&peer_list, count); 229 build_flat_vector(&peers, count);
231 CHECK(peer_list.peers.data != NULL); 230 CHECK(peers.data != NULL);
232 vector_redistribute_buckets(&peer_list, OT_PEER_SIZE4); 231 vector_redistribute_buckets(&peers, OT_PEER_SIZE4, count);
233 CHECK_INT(OT_PEERLIST_HASBUCKETS(&peer_list), 1); 232 CHECK_INT(OT_VECTOR_HASBUCKETS(&peers), 1);
234 CHECK_INT(peer_list.peers.size, 16); 233 CHECK_INT(peers.size, 16);
235 234
236 buckets = peer_list.peers.data; 235 buckets = peers.data;
237 for (bucket = 0; bucket < peer_list.peers.size; ++bucket) { 236 for (bucket = 0; bucket < peers.size; ++bucket) {
238 total_size += buckets[bucket].size; 237 total_size += buckets[bucket].size;
239 if (!buckets[bucket].size) 238 if (!buckets[bucket].size)
240 ++empty; 239 ++empty;
@@ -252,23 +251,23 @@ static void case_redistribute_holds_every_peer(void) {
252 CHECK_INT(empty, 0); 251 CHECK_INT(empty, 0);
253 CHECK(largest < count / 2); 252 CHECK(largest < count / 2);
254 253
255 vector_clean_list(peer_list.peers.data, peer_list.peers.size); 254 vector_clean_list(peers.data, peers.size);
256} 255}
257 256
258static void case_redistribute_sorts_each_bucket(void) { 257static void case_redistribute_sorts_each_bucket(void) {
259 const size_t count = 1000; 258 const size_t count = 1000;
260 ot_peerlist peer_list; 259 ot_vector peers;
261 size_t bucket, i, out_of_order = 0; 260 size_t bucket, i, out_of_order = 0;
262 ot_vector *buckets; 261 ot_vector *buckets;
263 262
264 test_case("every bucket comes back sorted, so bsearch can be used on it"); 263 test_case("every bucket comes back sorted, so bsearch can be used on it");
265 build_flat_peerlist(&peer_list, count); 264 build_flat_vector(&peers, count);
266 CHECK(peer_list.peers.data != NULL); 265 CHECK(peers.data != NULL);
267 vector_redistribute_buckets(&peer_list, OT_PEER_SIZE4); 266 vector_redistribute_buckets(&peers, OT_PEER_SIZE4, count);
268 CHECK_INT(OT_PEERLIST_HASBUCKETS(&peer_list), 1); 267 CHECK_INT(OT_VECTOR_HASBUCKETS(&peers), 1);
269 268
270 buckets = peer_list.peers.data; 269 buckets = peers.data;
271 for (bucket = 0; bucket < peer_list.peers.size; ++bucket) 270 for (bucket = 0; bucket < peers.size; ++bucket)
272 for (i = 1; i < buckets[bucket].size; ++i) { 271 for (i = 1; i < buckets[bucket].size; ++i) {
273 const uint8_t *prev = (uint8_t *)buckets[bucket].data + (i - 1) * OT_PEER_SIZE4; 272 const uint8_t *prev = (uint8_t *)buckets[bucket].data + (i - 1) * OT_PEER_SIZE4;
274 const uint8_t *next = (uint8_t *)buckets[bucket].data + i * OT_PEER_SIZE4; 273 const uint8_t *next = (uint8_t *)buckets[bucket].data + i * OT_PEER_SIZE4;
@@ -278,19 +277,19 @@ static void case_redistribute_sorts_each_bucket(void) {
278 } 277 }
279 CHECK_INT(out_of_order, 0); 278 CHECK_INT(out_of_order, 0);
280 279
281 vector_clean_list(peer_list.peers.data, peer_list.peers.size); 280 vector_clean_list(peers.data, peers.size);
282} 281}
283 282
284static void case_redistribute_below_threshold(void) { 283static void case_redistribute_below_threshold(void) {
285 ot_peerlist peer_list; 284 ot_vector peers;
286 285
287 test_case("a small peer list is left flat"); 286 test_case("a small peer list is left flat");
288 build_flat_peerlist(&peer_list, 100); 287 build_flat_vector(&peers, 100);
289 CHECK(peer_list.peers.data != NULL); 288 CHECK(peers.data != NULL);
290 vector_redistribute_buckets(&peer_list, OT_PEER_SIZE4); 289 vector_redistribute_buckets(&peers, OT_PEER_SIZE4, 100);
291 CHECK_INT(OT_PEERLIST_HASBUCKETS(&peer_list), 0); 290 CHECK_INT(OT_VECTOR_HASBUCKETS(&peers), 0);
292 CHECK_INT(peer_list.peers.size, 100); 291 CHECK_INT(peers.size, 100);
293 free(peer_list.peers.data); 292 free(peers.data);
294} 293}
295 294
296void test_vector(void) { 295void test_vector(void) {