blackbull.server.affinity¶
blackbull.server.affinity
¶
Per-worker CPU placement.
Pinning a worker's event loop to one core keeps its hot state — the header line table, the HPACK dynamic tables, the connection dict — resident in that core's L1/L2 instead of following the thread around the machine. That is the whole of the upside, and it is real; the care in this module is all about the three ways a naive pin does damage:
Never widen the mask we were given. sched_setaffinity lets a process
move itself onto CPUs its own mask excludes, so a pin computed against
os.cpu_count() silently overrides taskset, numactl, and any
cpuset the operator placed us in. Every placement here is drawn from
sched_getaffinity — an operator's confinement is an input, not an
obstacle.
Never pin the thread pool. Linux threads inherit the creating thread's
affinity mask. Pinning the main thread and then offloading compression or a
file read to the default executor puts that work on the one core the event
loop is already saturating, which is precisely backwards.
:func:make_offload_executor hands each pool thread the full mask back.
Always be switchable off. On a shared or externally-orchestrated host the right number of pinning decisions for a framework to make is zero.
apply_worker_affinity(worker_id, spec)
¶
Pin this thread for worker worker_id; return the mask it had before.
The return value is what :func:make_offload_executor needs — the
placement the operator gave us, which offloaded work should keep even
though the event loop no longer does. None means nothing was pinned
and no executor override is warranted.
make_offload_executor(allowed, max_workers=None)
¶
A thread pool whose threads run on allowed, not on the loop's pin.
A thread that cannot set the mask still runs its work: losing the spread costs throughput, refusing to start the thread costs the request.
resolve_worker_cpus(spec, worker_id, allowed)
¶
CPUs worker worker_id should run on, or None to leave it alone.
allowed is the mask the process already carries — the placement the operator chose. The result is always a subset of it.