Skip to content

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

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):

  1. Unpinned. The calling thread MUST have exited all pin scopes before calling. The slot's pinned flag must be clear. Calling from inside a critical section is a programming error.
  2. 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 mechanism tryReclaim) until it returns 0. The slot's currentBag and limboBagTail must be nil. 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 tryReclaim walk 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 = true would 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