Cancellation token - leaf module.
The cancellation token is a tiny two-atom record that lets a UI
thread (TUI, REPL caller) cooperatively abort an
in-flight turn! AND interrupt the worker future hosting the
blocking provider call. The cooperative side is checked at every
iteration boundary; the future side hard-cancels any HTTP call
that has already started.
Public API:
(cancellation-token) - fresh token
(cancellation-atom token) - cooperative flag atom (pass to turn!)
(cancellation-set-future! token fut) - register the worker future
(cancel! token) - set flag + interrupt registered future
(cancel! token reason) - same, stamping WHO cancelled
(cancel-reason token) - the recorded origin of the cancel
(cancelled? token) - true once cancel! has been called
(cancellation? throwable) - true if exception was caused by cancel!
This namespace has zero side effects at load time and depends only on Java interop - channels and the runtime can require it directly without pulling in the rest of the SDK.
Cancellation token - leaf module. The cancellation token is a tiny two-atom record that lets a UI thread (TUI, REPL caller) cooperatively abort an in-flight `turn!` AND interrupt the worker future hosting the blocking provider call. The cooperative side is checked at every iteration boundary; the future side hard-cancels any HTTP call that has already started. Public API: `(cancellation-token)` - fresh token `(cancellation-atom token)` - cooperative flag atom (pass to `turn!`) `(cancellation-set-future! token fut)` - register the worker future `(cancel! token)` - set flag + interrupt registered future `(cancel! token reason)` - same, stamping WHO cancelled `(cancel-reason token)` - the recorded origin of the cancel `(cancelled? token)` - true once `cancel!` has been called `(cancellation? throwable)` - true if exception was caused by `cancel!` This namespace has zero side effects at load time and depends only on Java interop - channels and the runtime can require it directly without pulling in the rest of the SDK.
(cancel! token)(cancel! token reason)Abort the in-flight turn. Flips the cooperative flag AND runs every
registered on-cancel! callback. Each callback is wrapped in its
own try/catch so one bad consumer cannot starve the rest.
Safe to call multiple times — on the second call the flag is
already set, and the callback list has typically drained because
workers disposed themselves.
reason names the ORIGIN (:client-cancel-turn, :stall-watchdog,
:gateway-shutdown, …). The first one wins — a shutdown that lands on
an already user-cancelled turn must not rewrite history — and it is read
back with cancel-reason. The one-arity call records :unspecified
rather than nothing, so an unattributed cancel is still visibly a cancel.
Abort the in-flight turn. Flips the cooperative flag AND runs every registered `on-cancel!` callback. Each callback is wrapped in its own `try`/`catch` so one bad consumer cannot starve the rest. Safe to call multiple times — on the second call the flag is already set, and the callback list has typically drained because workers disposed themselves. `reason` names the ORIGIN (`:client-cancel-turn`, `:stall-watchdog`, `:gateway-shutdown`, …). The first one wins — a shutdown that lands on an already user-cancelled turn must not rewrite history — and it is read back with [[cancel-reason]]. The one-arity call records `:unspecified` rather than nothing, so an unattributed cancel is still visibly a cancel.
(cancel-reason token)Why this token was cancelled — the reason of the FIRST cancel! call,
or nil when it was never cancelled (or came from a hand-built token).
Cancellation is the one turn outcome with no stack trace worth reading: the loop only sees an interrupt. Callers log this so a post mortem can tell a user stop from the daemon cancelling its own turn.
Why this token was cancelled — the `reason` of the FIRST `cancel!` call, or nil when it was never cancelled (or came from a hand-built token). Cancellation is the one turn outcome with no stack trace worth reading: the loop only sees an interrupt. Callers log this so a post mortem can tell a user stop from the daemon cancelling its own turn.
(cancellation-atom token)Cooperative flag atom — read with @ at iteration boundaries when
the consumer can return without external help.
Cooperative flag atom — read with `@` at iteration boundaries when the consumer can return without external help.
(cancellation-set-future! token fut)Register a worker Future so cancel! interrupts it. Thin
convenience over on-cancel!: wraps .cancel(true) in a thunk
and discards the returned dispose! (the future's own completion
makes the second cancel a no-op).
Returns the future for convenient threading.
Register a worker `Future` so `cancel!` interrupts it. Thin convenience over `on-cancel!`: wraps `.cancel(true)` in a thunk and discards the returned `dispose!` (the future's own completion makes the second cancel a no-op). Returns the future for convenient threading.
(cancellation-token)Construct a fresh cancellation token.
The token bundles three things every cancellable boundary needs:
::flag — cooperative boolean atom, polled at iteration
boundaries by callers that can return
gracefully.::callbacks — vec of [id thunk] pairs run by cancel! so
any number of in-flight workers (provider
HTTP call, Python eval future, voice recorder)
can register their own hard-cancel hook.::reason — WHO fired the cancel, stamped by cancel! and read
back with cancel-reason. Downstream every cancel
looks identical (a thread interrupt), so without
this stamp a self-inflicted cancel (stall watchdog,
shutdown) is indistinguishable from a user Esc.cancellation-set-future! (legacy single-future API) is kept for
call sites that have not migrated yet; it now routes through the
callback registry too so behaviour stays identical.
Construct a fresh cancellation token.
The token bundles three things every cancellable boundary needs:
- `::flag` — cooperative boolean atom, polled at iteration
boundaries by callers that can return
gracefully.
- `::callbacks` — vec of `[id thunk]` pairs run by `cancel!` so
any number of in-flight workers (provider
HTTP call, Python eval future, voice recorder)
can register their own hard-cancel hook.
- `::reason` — WHO fired the cancel, stamped by `cancel!` and read
back with `cancel-reason`. Downstream every cancel
looks identical (a thread interrupt), so without
this stamp a self-inflicted cancel (stall watchdog,
shutdown) is indistinguishable from a user Esc.
`cancellation-set-future!` (legacy single-future API) is kept for
call sites that have not migrated yet; it now routes through the
callback registry too so behaviour stays identical.(cancellation? e)True if the given throwable was caused by a cancel! call. Channels
should treat this as a normal (cancelled) outcome rather than an
error and avoid showing stack traces.
True if the given throwable was caused by a `cancel!` call. Channels should treat this as a normal (cancelled) outcome rather than an error and avoid showing stack traces.
(cancelled? token)True once cancel! has been called on this token.
True once `cancel!` has been called on this token.
(on-cancel! token thunk)Register a no-arg thunk to fire the moment cancel! is invoked
on token. Returns a dispose! thunk the caller MUST invoke when
the cancellable work finishes normally — otherwise callbacks
accumulate for the token's lifetime.
If cancel! has already fired on this token, thunk runs
synchronously here and dispose! is a no-op. This matches the
contract every consumer wants: registering AFTER cancellation
must still cancel, not silently swallow the request.
Replaces the atom-watch pattern earlier eval boundaries hand-rolled: one shared callback list, no per-consumer add-watch / remove-watch plumbing, no risk of leaving a watch on the flag after the worker finishes.
Register a no-arg `thunk` to fire the moment `cancel!` is invoked on `token`. Returns a `dispose!` thunk the caller MUST invoke when the cancellable work finishes normally — otherwise callbacks accumulate for the token's lifetime. If `cancel!` has already fired on this token, `thunk` runs synchronously here and `dispose!` is a no-op. This matches the contract every consumer wants: registering AFTER cancellation must still cancel, not silently swallow the request. Replaces the atom-watch pattern earlier eval boundaries hand-rolled: one shared callback list, no per-consumer add-watch / remove-watch plumbing, no risk of leaving a watch on the flag after the worker finishes.
(virtual-threads-available?)True when this JVM exposes Java virtual-thread APIs. Reflection keeps source compatible with older runtimes.
True when this JVM exposes Java virtual-thread APIs. Reflection keeps source compatible with older runtimes.
(worker-future f)(worker-future name f)Run f on a cancellable worker Future. Uses a virtual thread when the JVM
supports it, otherwise falls back to a named daemon platform thread.
The returned value implements java.util.concurrent.Future plus Clojure
deref/realized? protocols so legacy future call sites can migrate without
losing timeout/cancellation behavior.
Run `f` on a cancellable worker Future. Uses a virtual thread when the JVM supports it, otherwise falls back to a named daemon platform thread. The returned value implements java.util.concurrent.Future plus Clojure deref/realized? protocols so legacy `future` call sites can migrate without losing timeout/cancellation behavior.
(worker-runtime)Runtime probe for worker execution. :worker-helper is stable metadata for
diagnostics; :virtual-threads? reports whether new worker tasks will use
Java virtual threads.
Runtime probe for worker execution. `:worker-helper` is stable metadata for diagnostics; `:virtual-threads?` reports whether new worker tasks will use Java virtual threads.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |