Round-robin dispatcher for multi-session slot pools.
A slot configured with session_pool_size: N > 1 runs N independent
Pkcs11ex.Slot.Server workers, each holding its own PKCS#11 session.
Sign/verify calls round-robin across workers so unrelated requests run
concurrently rather than serializing through one mailbox.
When pooling helps
Cloud HSMs (e.g., GCP Cloud HSM via libkmsp11) make every operation a
remote call. A single GenServer mailbox + single session means request
N+1 waits for request N's network round-trip. With session_pool_size: 4, four signs land on four sessions in parallel — practical 4× lower
tail latency for bursty workloads.
When pooling does NOT help and is forbidden
PIN-protected token slots: login state lives on the session, not on
the token. With multiple sessions, every worker would need to login
independently — fine for pin_callback-driven flows but error-prone
for explicit login/2, and the configured-PIN tests across the
library all assume one logged-in session per slot.
Cross-field config validation enforces session_pool_size: 1 (the
default) for type: :token slots.
Concurrency model
ETS-backed counter (named table, write_concurrency on). Round-robin via
:ets.update_counter/4 — atomic, lock-free in the BEAM ETS path. No
GenServer round-trip on the hot path; only register/2 (called once
at slot startup) holds the GenServer.
Summary
Functions
Returns a specification to start this module under a supervisor.
Returns the next worker index in the range 1..pool_size(slot_ref),
using atomic round-robin.
Pool size for slot_ref. Defaults to 1 (single-worker, no pool) when
the slot hasn't been registered — keeps the dispatch path uniform for
ad-hoc test setups that start a Slot.Server without registering a
pool size.
Register a slot's pool size. Idempotent — re-registering with the same size is a no-op; with a different size, overwrites AND logs a warning so a config drift between two startup paths can't slip through silently.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec next_worker_index(atom()) :: pos_integer()
Returns the next worker index in the range 1..pool_size(slot_ref),
using atomic round-robin.
For pool_size = 1, always returns 1 without touching ETS — the
fast path for non-pooled slots.
@spec pool_size(atom()) :: pos_integer()
Pool size for slot_ref. Defaults to 1 (single-worker, no pool) when
the slot hasn't been registered — keeps the dispatch path uniform for
ad-hoc test setups that start a Slot.Server without registering a
pool size.
@spec register(atom(), pos_integer()) :: :ok
Register a slot's pool size. Idempotent — re-registering with the same size is a no-op; with a different size, overwrites AND logs a warning so a config drift between two startup paths can't slip through silently.
Called once per slot from Pkcs11ex.SlotSupervisor.init/1.