Files
soulseek-rs-modded/benchmarks
Michel de Graaf 7435d48b32 perf: raise transfer defaults and stop uploads sleeping on their slot
The slot cap merged from master defaults to 2 uploads, and the CLI capped
downloads at 5. Those are the numbers you pick when your own bandwidth is the
scarce resource, but a Soulseek transfer is paced by the peer on the other end
— usually a few hundred KiB/s — so what fills a modern link is many transfers
at once, not faster ones. Upload slots go to 10, downloads to 20. The queue
itself stays: privileged users being able to jump it is a condition of being a
tolerated third-party client.

serve_file also slept half a second after the last byte so the downloader could
drain before the socket closed. With a cap that is time no other peer can use,
and 64 uploads over 10 slots paid it six times over. A half-close says "that is
the whole file" and TCP still delivers everything written before the FIN.

Measured with 64 waiting peers: 2 slots 24.2s, 8 slots 6.2s, 32 slots 3.1s; at
10 slots the linger alone was 5.4s to 3.05s.
2026-07-26 18:25:50 +02:00
..

Concurrency benchmark

Answers one question: how much simultaneous Soulseek activity can the client absorb before it starts losing work?

scripts/stress.sh           # run the default profile
scripts/stress.sh --save    # ...and store it as the new baseline if it wins

A run takes about 7 seconds. It spawns a local soulfind, surrounds one real Client with a swarm of mock peers, and drives searches, downloads, uploads and browses at the same time — the mixing is the point, since every ceiling found so far only appeared under a mixed load.

The score

One number out of 100, weighted across five dimensions:

Dimension Weight Measures
downloads 0.30 completed / started
uploads 0.20 served / requested
searches 0.20 queries that got at least one result
browses 0.15 browse requests answered
throughput 0.15 aggregate MiB/s against TARGET_MBPS (500)

The four functional dimensions should stay at 100%: anything less means work was silently dropped, which is the failure mode that matters. Throughput is the term with headroom, so the score still moves when transfer handling regresses.

--save only overwrites stress-baseline.json when the run beats it, so the baseline ratchets upward. Without --save a run just prints the delta.

Tuning the load

Every knob is an env var; the defaults are in Config::from_env.

STRESS_PEERS=2048 STRESS_FILE_KB=8192 scripts/stress.sh

STRESS_PEERS STRESS_SEARCHES STRESS_DOWNLOADS STRESS_LEECHERS STRESS_BROWSES STRESS_FILE_KB STRESS_TIMEOUT

Reading a failure

downloads completed 480/512 is the headline, but the two lines under it say where to look:

  • download states — a straggler on Queued never got its transfer negotiated; one on InProgress is merely slow. These are different bugs.
  • mocks never online — mock peers the harness could not bring up. That is a harness/server limit, not the client, and it is reported separately so it cannot quietly shrink a denominator and flatter the score.

peak threads counts the whole process — client and mock swarm — so treat it as a trend, not a client measurement.

LOG_LEVEL=debug scripts/stress.sh correlates a stuck peer's name against the client's own trace, which is how each ceiling below was actually found.

Ceilings found and removed

Symptom Cause
Hard stop at ~15 peers; later peers never start Every PeerActor held a ThreadPool worker for life, so the pool capped peers at one per core
Downloads stuck on Queued under load TransferResponse was sent before the client recorded the peer's token, so the file connection raced its own bookkeeping
One download vanishing per few hundred Token was md5(filename), and the store deletes every entry with a token — same filename from two peers destroyed both
Uploads dropped after the peer accepted Server answered GetPeerAddress with port 0; the client cached it and dialled it

Requirements

A soulfind binary (SOULFIND_BIN, defaulted in scripts/stress.sh), or SOULSEEK_TEST_SERVER=host:port to point at a running server. The script raises the descriptor limit — a swarm holds a lot of sockets open.