Chronos¶
Chronos is a managed temporal payload type — an owned timestamp /
monotonic-clock handle suitable for use as a queue payload where the
producer needs to stamp an event and the consumer needs to observe
that stamp with the same lifetime guarantees as the rest of the
payload.
It is part of the managed-payload family (ManagedRef,
ManagedSlice, Chronos) and follows the same single-owner,
move-tracked lifecycle.
See also¶
- ManagedRef, ManagedSlice — sibling managed payload types.
- SMR / nebr — the reclamation backend.
chronos
¶
src/lockfree/chronos.nim
Tier 3 chronos async adapter for lockfree queues. This is the only
async tier shipping in v0.1.0, built on the hybrid optional-dep
pattern.
chronos is intentionally NOT listed unconditionally in
lockfree.nimble requires. The library is flag-only opt-in: users
who want the async adapter pass -d:lockfreeChronos AND install
chronos themselves (or rely on lockfree.nimble's
when defined(lockfreeChronos): requires "chronos >= 4.0.0, < 5.0.0"
conditional dep). The library never transitively pulls chronos in
for users who do not need it, and chronos is never auto-detected at
compile time.
Activation matrix (flag-only opt-in):
-d:lockfreeChronos |
chronos installed | outcome |
|---|---|---|
| No | (irrelevant) | (d) import succeeds; module |
| body skipped; AsyncQueue/ | ||
| AsyncBQueue invisible. | ||
| Yes | Yes | (b) opt-in path; module body |
| activates; types exported. | ||
| Yes | No | (c) {.error.} fires with a |
| precise install hint | ||
| referencing | ||
docs/api/chronos.md. |
The previous auto-detect arm — a public lockfreeChronosAvailable*
constant that callers could when-branch on to silently enable the
adapter without the flag — was removed: silent activation based on
whether chronos happens to be installed in a user's package set
violates the flag-only opt-in contract. An internal
(non-exported) chronosReachable probe is retained ONLY to drive
the precise install-hint {.error.} arm; the module body never
activates without -d:lockfreeChronos, regardless of whether
chronos is reachable.
NOTE on chronos import form: the obvious import chronos self-shadows
inside this module —
our own file IS src/lockfree/chronos.nim, which Nim resolves first
on the import search path, breaking the compiles do: probe used to
emit the precise install-hint error. We import the chronos
submodule chronos/asyncsync (which transitively re-exports
asyncloop -> asyncfutures + asyncmacro, covering AsyncEvent, Future,
async/await, waitFor, CancelledError) and we use the bracket-array
form chronos/[asyncsync]. The bracket form is what makes the
resolver locate the chronos package despite the local module's name.
A leading throw-away compiles do: import chronos/asyncsync primes
Nim's package resolver so the bracket form binds correctly on the
next call — without that prime the bracket form returns false on the
very first invocation in a module whose own name is "chronos". This
priming probe is deliberate and is required to defeat the
self-shadowing; it is NOT an auto-detect arm, and its result is
discarded.
AsyncBQueue
¶
type AsyncBQueue[T; ccProd, ccCons: static PinScopeCardinality; N, P, C: static int] = ref object
Bounded async-adapter queue. Wraps a sync
BQueue and adds a single chronos AsyncEvent for the
consumer-wakeup signal. Lock-freedom on the producer side is
preserved (fire is a non-blocking flag set on the chronos
event-loop thread); the consumer opts into blocking via
await event.wait().
Storage is ref object rather than a plain object
because chronos's {.async.} macro lifts pop into a
closure-bearing iterator, and Nim refuses to capture a var
receiver across the await suspension point. Boxing the wrapper
resolves the capture without introducing extra atomic state.
This matches chronos's own asyncsync.AsyncQueue[T] which is
also ref object of RootRef.
Fields
-
queueBQueue[T, ccProd, ccCons, N, P, C] -
eventAsyncEvent
AsyncQueue
¶
type AsyncQueue[T; ccProd, ccCons: static PinScopeCardinality; ST: static DeallocationStrategy;
S, MaxThreads: static int] = ref object
Unbounded async-adapter queue. Same shape as
AsyncBQueue but over the unbounded Queue. Note that
chronos's own chronos/asyncsync.AsyncQueue[T] (a ref object
with a single generic parameter) lives in a different scope;
downstream code that imports both should qualify or alias one
side. See the test suite (tests/t_chronos.nim) for the
recommended from chronos import nil pattern.
Fields
-
queueQueue[T, ccProd, ccCons, ST, S, MaxThreads] -
eventAsyncEvent
AsyncQueueSpsc
¶
type AsyncQueueSpsc[T; S, MaxThreads: static int] = AsyncQueue[T, ccSingle, ccSingle, stEager, S, MaxThreads]
Convenience alias for the SPSC-absorbed unbounded async queue
(debra-free; no pinscope; trivially safe across async-await
boundaries — pin scope is closed inside the
inner pop before any await).
newAsyncBQueue ¶
proc newAsyncBQueue(): AsyncBQueue[T, ccProd, ccCons, N, P, C]
Construct an AsyncBQueue. Allocates a fresh AsyncEvent and
forwards to newBQueue for the inner queue. Returns a heap-
allocated wrapper (see the type doc on why the wrapper is
ref object).
Returns
AsyncBQueue[T, ccProd, ccCons, N, P, C]
newAsyncQueue ¶
proc newAsyncQueue(_: typedesc[AsyncQueue[T, ccProd, ccCons, ST, S, MaxThreads]]): AsyncQueue[T, ccProd, ccCons, ST, S, MaxThreads]
Construct an AsyncQueue via the typedesc-only sync ctor
(delegates to newQueue(typedesc[Queue[...]])). For the
spsc-absorbed (ccSingle, ccSingle) shape this is debra-free.
For other cardinalities the sync ctor allocates a private
DebraManager; the async wrapper inherits that ownership.
Parameters
-
_(typedesc[AsyncQueue[T, ccProd, ccCons, ST, S, MaxThreads]])
Returns
AsyncQueue[T, ccProd, ccCons, ST, S, MaxThreads]
push ¶
proc push(self: AsyncBQueue[T, ccSingle, ccSingle, N, 0, 0]; item: sink T): bool
SPSC async-adapter push. Forwards to the sync BQueue.push and
fires the AsyncEvent on success so an awaiting pop wakes.
Returns the underlying push outcome so the user
can implement back-pressure (a false return means the bounded
queue is full; the event is NOT fired in that case).
Parameters
-
self(AsyncBQueue[T, ccSingle, ccSingle, N, 0, 0]) -
item(sink T)
Returns
bool
pop async ¶
proc pop(self: AsyncBQueue[T, ccSingle, ccSingle, N, 0, 0]): Future[Option[T]]
SPSC async-adapter pop. Loops the canonical sticky-flag
condition pattern:
clear flag -> non-blocking pop -> if some, return -> else await flag -> repeat
The clear precedes the pop so a producer push that interleaves
between the clear and the await still leaves the flag set,
and the subsequent await event.wait() returns immediately
(chronos AsyncEvent.wait short-circuits when the flag is
already true — chronos 4.x AsyncEvent.wait). This eliminates
the lost-wakeup race without introducing extra atomic state on
the queue side.
Cancellation discipline: the sync pop
body for the SPSC arm is debra-free and holds no pin across the
await. The try/finally here is the structural guard that
also covers future expansion to cardinalities where an inner pop
DOES enter and exit a pin scope (the design guarantees that pin
acquisition/release happens entirely inside q.queue.pop(), so
no pin is held across the await event.wait() line below; the
try/finally captures the cancellation propagation contract
regardless).
Parameters
-
self(AsyncBQueue[T, ccSingle, ccSingle, N, 0, 0])
Returns
Future[Option[T]]
pop async ¶
proc pop(self: AsyncQueue[T, ccSingle, ccSingle, ST, S, MaxThreads]): Future[Option[T]]
SPSC async-adapter pop for the unbounded queue. Same loop and
cancellation contract as the bounded SPSC pop above.
Parameters
-
self(AsyncQueue[T, ccSingle, ccSingle, ST, S, MaxThreads])
Returns
Future[Option[T]]
asyncEvent ¶
template asyncEvent(self: AsyncBQueue[T, ccProd, ccCons, N, P, C]): var AsyncEvent
Returns a mutable view of the wrapper's AsyncEvent. Exposed so
downstream asyncPop-on-Bound implementations
can share the same event flag without reaching through q.event
directly.
Parameters
-
self(AsyncBQueue[T, ccProd, ccCons, N, P, C])
Returns
var AsyncEvent
asyncEvent ¶
template asyncEvent(self: AsyncQueue[T, ccProd, ccCons, ST, S, MaxThreads]): var AsyncEvent
Unbounded analog of the bounded asyncEvent helper above.
Parameters
-
self(AsyncQueue[T, ccProd, ccCons, ST, S, MaxThreads])
Returns
var AsyncEvent
Optional: chronos async-adapter integration build¶
src/lockfree/chronos.nim ALSO ships the
chronos async-adapter
surface (AsyncQueue / AsyncBQueue) for callers who want an await-
shaped pop on top of the lock-free queues. The adapter is flag-only
opt-in (CRITICAL-4): the library never auto-detects chronos, and never
pulls it in transitively. To enable the adapter, install chronos in the
supported version range AND build with the lockfreeChronos define:
nimble install "chronos >= 4.0.0, < 5.0.0"
nim c -d:lockfreeChronos --threads:on --mm:orc your_app.nim
Both pieces are required:
- Building with
-d:lockfreeChronoswithout chronos installed emits a compile-time{.error: ...}that points back to this section and thenimble installcommand above. - Building without the flag skips the adapter entirely; the
AsyncQueue/AsyncBQueueexports are invisible and chronos is NOT pulled in.
lockfree.nimble carries a when defined(lockfreeChronos): requires
"chronos >= 4.0.0 & < 5.0.0" conditional dep so downstream users who
pass the define get the version constraint enforced by nimble's
resolver.