nebr (SMR)¶
lockfree/smr/nebr is the umbrella's safe-memory-reclamation backend.
It is the renamed-in-v0.1.0 successor to the upstream nim-debra
DEBRA+ implementation (Brown 2015, with the R6 attribution
correction tracked in the umbrella's working substrate).
nebr exposes the NebrManager, the per-thread handle, the
attach / detach lifecycle, and the retire / reclaim entry points used
by the umbrella's multi-producer / multi-consumer queue shapes and
by the managed-payload types.
See also¶
- SMR concept guide — high-level motivation and lifecycle.
- nebr user guide — usage patterns and attach/detach examples.
- docs/legacy/nim-debra/ — upstream DEBRA documentation preserved at the T0 merge point.
nebr
¶
lockfree/smr/nebr: NEBR Safe Memory Reclamation
NEBR = Neutralization-Enhanced Bounded Reclamation. This module is the in-tree fork of nim-debra. Semantically identical to upstream nim-debra except for import paths.
Public facade re-exporting the manager + neutralize substrate plus the thread-registration, epoch, and client-binding helpers that downstream consumers (Queue[T] with ref payloads, the destructor walk) need.
Design refs: nim-debra design §3.1, §3.7.
registerThread raises ¶
proc registerThread(manager: var DebraManager[MaxThreads, CC]): ThreadHandle[MaxThreads, CC]
Register current thread with the NEBR manager.
Must be called once per thread before any epoch operations. Raises DebraRegistrationError if max threads already registered.
Parameters
-
manager(var DebraManager[MaxThreads, CC])
Returns
ThreadHandle[MaxThreads, CC]
Raises
DebraRegistrationError
unregisterThread raises ¶
proc unregisterThread(manager: var DebraManager[MaxThreads, CC]; handle: ThreadHandle[MaxThreads, CC])
Unregister the current thread from the NEBR manager, releasing the
slot it claimed via registerThread so a future registerThread may
re-claim that slot index for a different thread.
Caller contract (preconditions). Both must hold or this proc fails a
doAssert (see "Failure behavior" below):
- Unpinned. The calling thread MUST have exited all pin scopes
before calling. The slot's
pinnedflag must be clear. Calling from inside a critical section is a programming error. - Drained limbo. The calling thread MUST have drained its own
retired/limbo objects before calling, i.e. run reclamation until it
reclaims nothing —
reclaimNow(handle)(the convenience entry point; underlying mechanismtryReclaim) until it returns0. The slot'scurrentBagandlimboBagTailmust benil. Unregistering with pending limbo is a programming error.
Why the contract exists. Releasing the slot lets registerThread
re-claim THIS slot index for a DIFFERENT thread; register reuses the
slot in place and does not re-initialise its epoch/pinned/limbo state.
So any state left here is inherited verbatim by the next owner:
- A departing thread's still-pending retired objects are NOT necessarily
epoch-safe to free yet, and NEBR keeps NO manager-level orphan-reclaim
list — it cannot adopt them. Eagerly freeing them here would be a
premature-free UAF; leaving them on a reused slot would let the new
owner's
tryReclaimwalk them under a different epoch (a stale-slot use-after-free / double-free). Requiring the caller to drain first is the conservative resolution of both hazards. - A stale
pinned = truewould make reclamation observe this slot as pinned forever, stalling ALL reclamation manager-wide.
Failure behavior. Contract violations are reported via doAssert
(this proc is {.raises: [].}, a compile-time-pinned contract, so it
cannot raise). In debug builds a violation aborts loudly. Under
-d:danger assertions are compiled out, so violating the contract is
undefined behavior (the very slot-reuse UAF / double-free this contract
prevents) rather than a loud abort. Treat the contract as mandatory in
all builds, not merely as a debug aid.
A handle with an out-of-range index, a slot whose activeThreadMask bit
is already clear (double-unregister), or a thread-affinity mismatch is
handled before the precondition checks: the first two return silently;
the affinity mismatch is its own doAssert.
Parameters
-
manager(var DebraManager[MaxThreads, CC]) -
handle(ThreadHandle[MaxThreads, CC])
neutralizeStalled ¶
proc neutralizeStalled(manager: var DebraManager[MaxThreads, CC]; epochsBeforeNeutralize: uint64 = 2): int
Signal all stalled threads. Returns number of signals sent.
Parameters
-
manager(var DebraManager[MaxThreads, CC]) -
epochsBeforeNeutralize(uint64)
Returns
int
advance inline ¶
proc advance(manager: var DebraManager[MaxThreads, CC])
Advance the global epoch.
Parameters
-
manager(var DebraManager[MaxThreads, CC])
currentEpoch inline ¶
proc currentEpoch(manager: var DebraManager[MaxThreads, CC]): uint64
Get current global epoch.
Parameters
-
manager(var DebraManager[MaxThreads, CC])
Returns
uint64
bindClient inline ¶
proc bindClient(manager: var DebraManager[MaxThreads, CC])
Register a client as bound to this manager. Increments boundClients.
Parameters
-
manager(var DebraManager[MaxThreads, CC])
unbindClient inline ¶
proc unbindClient(manager: var DebraManager[MaxThreads, CC])
Unregister a client previously bound via bindClient.
Parameters
-
manager(var DebraManager[MaxThreads, CC])
clientCount inline ¶
proc clientCount(manager: var DebraManager[MaxThreads, CC]): int
Number of clients currently bound to this manager.
Parameters
-
manager(var DebraManager[MaxThreads, CC])
Returns
int