feat: lift the client's concurrency ceiling from ~15 peers to 1024
The client had never been driven at scale. A stress harness that mixes searches, downloads, uploads and browses against a local soulfind found four ceilings, three of which lose work silently rather than merely being slow. Peers were capped at one per core. ActorSystem ran each actor's whole lifetime as one job on a fixed-size ThreadPool, and actor loops block until stopped, so the pool acted as a semaphore: past the cap an actor never started, never connected and never errored, and uploads stopped working entirely. The pool had exactly one consumer and every job it received was non-terminating, so it is gone; actors get their own thread. TransferResponse raced the bookkeeping it depends on. It was sent straight back to the peer while UpdateDownloadTokens was still queued for the client loop — but the response is what invites the peer's file connection, and that connection is matched by token. Under load the client loop lost, the file connection matched nothing, and the download sat on Queued forever. The response now goes out after the token is recorded. Download tokens were md5(filename) truncated to 20 bits, and the store deletes every entry matching a token, so two downloads sharing one destroyed each other — which fetching the same filename from two peers does every time. They now come from a counter. Port 0 from GetPeerAddress means the server does not know where a user listens. It was cached and dialled, and the upload waiting on it was dropped. It is no longer cached or served against; the connect attempt stays, because its failure is what makes an unreachable peer fall back to server brokering. Mixed profile at 1024 peers: 512/512 downloads, 256/256 uploads, 400-850 MiB/s in ~7s, against 71/96 downloads at 7.9 MiB/s and a 60s timeout before.
This commit is contained in:
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run the concurrency stress benchmark against a local soulfind.
|
||||
#
|
||||
# scripts/stress.sh # run with the default load profile
|
||||
# scripts/stress.sh --save # store the run as the new baseline if it wins
|
||||
# STRESS_PEERS=128 scripts/stress.sh
|
||||
#
|
||||
# Knobs: STRESS_PEERS STRESS_SEARCHES STRESS_DOWNLOADS STRESS_LEECHERS
|
||||
# STRESS_BROWSES STRESS_FILE_KB STRESS_TIMEOUT
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# soulfind is a D binary linked against Homebrew's SQLite.
|
||||
export SOULFIND_BIN="${SOULFIND_BIN:-$HOME/src/soulseek_download/soulfind/bin/soulfind}"
|
||||
export DYLD_LIBRARY_PATH="${DYLD_LIBRARY_PATH:-/opt/homebrew/opt/sqlite/lib}"
|
||||
|
||||
if [ ! -x "$SOULFIND_BIN" ]; then
|
||||
echo "stress: no soulfind at $SOULFIND_BIN — set SOULFIND_BIN" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Stale databases from a killed run make the next login behave oddly.
|
||||
rm -f "${TMPDIR:-/tmp}"/soulfind-stress-*.db
|
||||
|
||||
# Every mock peer holds sockets open; the default 256 descriptors is nowhere near
|
||||
# enough for a swarm.
|
||||
ulimit -n 20000 2>/dev/null || true
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--save) export STRESS_SAVE=1 ;;
|
||||
*) echo "stress: unknown argument $arg" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
exec cargo run --release --quiet -p soulseek-rs-lib --example stress
|
||||
Reference in New Issue
Block a user