ManagedRef — ref T payloads¶
lockfree v0.1.0 stores ref T payloads in queue slots directly. No
ptr T wrapping. No -d:allowNonLockFreeQueueItems escape hatch.
This page is the user-facing story.
If you only want to push and pop ref values, the short version is:
type Node = ref object
value: int
var q = newSpscQueue[Node, 16]()
discard q.push(Node(value: 42))
let n = q.pop() # Option[Node]: some(Node(value: 42))
That works under all four supported memory managers (orc, arc,
atomicArc, refc). It does not work under --mm:none —
--mm:none is strict bit transport and forbids destructor-bearing
payloads. See Memory management.
How it works (briefly)¶
Internally, each queue slot is an 8-byte SlotEncoding[T] token. For
ref T, the token is a distinct uint carrying the ref's raw
pointer; the actual ref payload lives wherever the MM placed it
(orc / arc / atomicArc heap, refc heap).
The semantic is:
- Push consumes one reference from the caller (
sink-style move into the queue). The slot now holds the only reference. - Pop transfers the reference back to the caller. The slot is cleared. Refcount net change is zero across the round-trip.
- Destroy on still-occupied slot drops the queue's reference,
invoking
=destroyfor theref Tvia the per-MM hook.
The name ManagedRef[X] is the internal slot-representation type;
end-users do not write ManagedRef[Foo] in their code. The package
documents it for clarity about what the slot holds, not for direct
use.
Per-MM behavior¶
| MM | Slot publish path | Slot clear path |
|---|---|---|
orc |
nimIncRef on box; encode pointer |
nimDecRef via destroy-walk |
arc |
nimIncRef on box; encode pointer |
nimDecRef via destroy-walk |
atomicArc |
Atomic nimIncRef on box |
Atomic nimDecRef via destroy-walk |
refc |
nimGCRefNoCycle on box |
nimGCUnrefNoCycle via destroy-walk |
none |
rejected at compile time | n/a |
Under all four supported managers, the queue counts the push-and-pop round-trip as zero net refcount change: push transfers +1, pop transfers −1, and the destroy-walk handles cleanup of any slots still occupied at queue teardown.
Cleanup and reclamation¶
For the bounded arms (BQueue), slot cleanup happens at queue
destruction. The queue walks its slot array, invokes
disposeSlotEncoded on each occupied slot, and frees the slot
storage.
For the unbounded multi-consumer arms (Queue), slot cleanup is more
subtle. When a consumer claims a slot, the slot's payload is moved
out and the queue's per-slot ownership is dropped immediately. The
segment itself is retired via nebr once every slot
in the segment has been claimed; nebr's reclaim pass eventually frees
the segment after every reading thread has moved on.
This means: a ref T payload is dropped at pop time, not at
segment-reclaim time. The segment-reclaim pass frees only the
segment metadata, not the payload. The payload's lifetime ends at
pop.
Cycle-collector interaction¶
Under --mm:orc, ref cycles involving queue-resident payloads
trigger the cycle collector when the queue is destroyed. The cycle
collector's traversal pauses other threads briefly; if your queue
holds many cyclic ref payloads, mark the type {.acyclic.}:
{.acyclic.} skips the cycle collector for this type. Only mark
types that genuinely cannot form a cycle through their ref fields.
Under --mm:arc, cycles leak; the manager does not collect them. Use
--mm:arc only when payloads are known acyclic.
Under --mm:atomicArc, cycle behavior matches arc: cycles leak.
Under --mm:refc, cycles are collected by the mark-and-sweep pass at
the cost of a full heap traversal.
Composition matrix (informational)¶
The full ref T composition matrix is enforced by src/lockfree/internal/path_c_admit.nim
and documented in Memory management.
It is a 25-row enumeration of ref of X shapes (ref int,
ref Object, ref ref T, ref array[N, T], ref tuple, ref proc,
ref UncheckedArray, closure environments, etc.) with an ACCEPT or
REJECT verdict per row.
User-facing summary:
ref Objectandref int— ACCEPT under all managed MMs.ref ref T— ACCEPT; the innerrefis part of the payload.ref array[N, T]with small N — ACCEPT.ref UncheckedArray[T]— ACCEPT butTmust be POD.ref proc— ACCEPT; closures carry environment refs.ref Tunder--mm:none— REJECT (compile-time).
If your ref shape falls outside this informal summary, consult the
design-doc matrix.
Examples¶
examples/03_managed_ref.nim— round-trip aref Nodethrough a bounded SPSC queue.examples/job_scheduler.nim— historicalptr Tjob-scheduler pattern; the v0.1.0 equivalent usesrefdirectly.
Further reading¶
- ManagedSlice —
string/seq[T]payloads. - Memory management — per-MM publish / cleanup details.
- Typestates — the endpoint lifecycle around
ref-bearing queues.