Expand description
§VUWorkerPool — Thread-per-core VU sharding (1 VU per dedicated thread)
Distributes VUs across a pool of current-thread tokio runtimes, each pinned to a dedicated OS thread. This gives each VU core-level isolation:
- No shared work-stealing — VUs on one core never steal work from another
- Better cache locality — each core’s VU data stays in its L1/L2 cache
- JS execution isolation — blocking JS on core 0 doesn’t stall VUs on core 1
sleep()safety — each VU owns its OS thread, so a blocking scriptsleep()(implemented withstd::thread::sleep) pauses only that VU. The pool grows on demand (spawn_vu), so no two VUs ever share a current-thread runtime — otherwise asleep()in one VU would freeze every VU co-located on the same worker.
§Scalability tradeoff
1 VU per OS thread is the closest Rust analog to k6’s goroutine-per-VU
model (without a GC), and it is what makes blocking sleep() safe. The
cost is one OS thread (plus a current-thread runtime) per VU, so very high
VU counts (e.g. 10k) are thread-heavy. That is the accepted tradeoff of
the 1-VU-per-task design; a future refinement could cap growth when a
script never calls sleep().
Hard ceiling: the pool never grows past MAX_WORKERS (10 000). For a
bounded executor with vus > 10 000 (an extreme 15k-VU constant test), VU
n and VU n+10_000 would silently share a worker, so a blocking
sleep() in one could freeze the other — the cap trades strict isolation
away at extreme VU counts to avoid exhausting the OS with one thread per
VU. Realistic tests stay far below the cap, where isolation is exact.
- Future safety — each JsContext is only used by its pinned thread, so we
could drop the
rquickjsparallelfeature (and its per-ctx.withmutex) ifJsContextwere made!Send
Structs§
- VUWorker
Pool - A pool of dedicated worker threads, each running a current-thread tokio
runtime. VU tasks are pinned to their own worker (
spawn_vu), so a VU is never co-located with another VU on the same runtime.