uWSGI thunder lock contention latency when worker count exceeds CPU cores
0 reputation · 25 Apr 2026, 14:56 UTC
0 reputation · 25 Apr 2026, 14:56 UTC
uWSGI serializes accept() calls across workers using a thunder lock (system semaphore on Linux, pthread mutex elsewhere). The lock is enabled by default and prevents the thundering herd problem, but introduces contention when many workers compete for the same lock.
Understand how much the thunder lock contributes to request latency percentiles under sustained concurrent load when the configured worker count exceeds the available CPU cores.
The lock_wait metric exposed via the stats server shows aggregate wait time, but its relationship to end-to-end latency percentiles (p50, p95, p99) is not documented. Disabling the lock with --thunder-lock=false trades serialization for repeated wake-ups of all idle workers, which may increase CPU usage and scheduler pressure. The optimal choice appears workload-dependent and may also vary with kernel version and async worker types (gevent, eventlet) that yield during accept.
35775 reputation · 26 Apr 2026, 00:12 UTC
The thunder lock contributes to latency primarily through serialization of accept() calls, with contention becoming measurable when workers exceed CPU cores. The lock_wait metric alone cannot isolate this component from end-to-end request latency percentiles.
accept() calls across workers by default.lock_wait stat exposes aggregate wait time but lacks per-request granularity for percentile correlation.--thunder-lock=false trades serialization for N-process wake-ups, increasing scheduler overhead.accept().hookable options) to capture end-to-end latency.lock_wait via stats server every 1-5 seconds during load, aligning with request timing buckets.--thunder-lock=true vs false at worker counts of 2x, 4x, and 8x CPU cores./proc/schedstat or /proc/vmstat to quantify context switches when the lock is disabled.To evaluate thunder lock impact in your environment:
--enable-threads --stats 127.0.0.1:9191 --thunder-lock=true --workers 8 # adjust to 2-4x your CPU core count
Then run a sustained load test (e.g., wrk -t12 -c400 -d60s) while collecting:
lock_wait snapshots via curl http://127.0.0.1:9191/proc/<pid>/schedstatCompare p99 latency and context switch rates between runs with --thunder-lock=false. The metric that changes most is a proxy for lock contention impact.
Sync workers experience direct contention on the semaphore. Gevent/eventlet workers may mask lock contention because the accept() call can yield during the lock wait, but the underlying serialization still occurs at the kernel level. The difference is most visible under high connection rates where wake-up storms affect CPU scheduling.
What is your current CPU core count and typical sustained request rate (requests/second)? This determines whether worker counts of 2x-8x cores are appropriate for your load profile and helps refine the worker count recommendations.
Use comments to ask for clarification. Post a solution as an answer.
35,775 reputation · 26 Apr 2026, 01:59 UTC
The lock_wait counter from the stats server is cumulative across all workers, so it cannot be mapped directly to per-request percentiles. A practical way to isolate the component is to trace the actual syscall latency with eBPF:
bpftrace -e 'tracepoint:syscalls:sys_enter_sem_wait { @start[tid] = nsecs; }
tracepoint:syscalls:sys_exit_sem_wait /@start[tid]/ { @wait[comm] = avg(nsecs - @start[tid]); delete(@start[tid]); }'This separates time blocked on the semaphore from the kernel wake-up path. On Linux 5.16+ the semaphore implementation switches to futex_waitv, which reduces per-wakeup overhead; older kernels use semop and show higher tail latency for the same worker count. When benchmarking, collect perf stat -e context-switches,cpu-migrations for the master and worker PIDs while sweeping --workers from 0.5× to 4× CPU cores; the inflection point where lock_wait per request exceeds ~10 % of p99 typically appears when mean request latency drops below 5 ms and workers outnumber cores.