True when inside with-determinism scope. Use this to check if deterministic mode is active (m/sleep, m/timeout, m/cpu, m/blk are virtualized).
True when inside with-determinism scope. Use this to check if deterministic mode is active (m/sleep, m/timeout, m/cpu, m/blk are virtualized).
Dynamically bound to the current TestScheduler in deterministic tests.
Dynamically bound to the current TestScheduler in deterministic tests.
(advance! sched dt-ms)Advance virtual time by dt-ms (>=0), enqueue due timers, then tick.
Binds scheduler to sched for the duration of execution.
Advance virtual time by dt-ms (>=0), enqueue due timers, then tick. Binds *scheduler* to sched for the duration of execution.
(advance-to! sched t)Set time to t (>= now), enqueue due timers, then tick. Returns number of microtasks executed by tick.
Binds scheduler to sched for the duration of execution.
Set time to t (>= now), enqueue due timers, then tick. Returns number of microtasks executed by tick. Binds *scheduler* to sched for the duration of execution.
(blk-executor)Deterministic blocking executor for m/via. Lane :blk retained for introspection/trace. Must be called inside with-determinism with a scheduler bound to scheduler.
Deterministic blocking executor for m/via. Lane :blk retained for introspection/trace. Must be called inside with-determinism with a scheduler bound to *scheduler*.
(check-interleaving task-fn
{:keys [num-tests seed property max-steps max-time-ms]
:or {num-tests 100 max-steps 10000 max-time-ms 60000}})Run a task with many different interleavings to find failures.
IMPORTANT: Must be called inside a with-determinism body.
task-fn should be a 0-arg function that returns a fresh task for each test. This ensures mutable state (like atoms) is reset between iterations.
Options:
Returns on success: {:ok? true :seed base seed used (for reproducibility) :iterations-run number of iterations completed}
Returns on failure: {:ok? false :kind :exception | :property-failed :seed seed used for this iteration :schedule schedule that caused failure (for replay) :trace full trace :iteration which iteration failed :error exception (present when :kind is :exception) :value result value (present when :kind is :property-failed)}
Note: For reproducible tests, always specify :seed. Without it, the current system time is used, making results non-reproducible across runs.
Run a task with many different interleavings to find failures.
IMPORTANT: Must be called inside a with-determinism body.
task-fn should be a 0-arg function that returns a fresh task for each test.
This ensures mutable state (like atoms) is reset between iterations.
Options:
- :num-tests - number of different interleavings to try (default 100)
- :seed - base seed for RNG (default: current time)
- :property - (fn [result] boolean) - returns true if result is valid
- :max-steps - max scheduler steps per run (default 10000)
- :max-time-ms - max virtual time per run (default 60000)
Returns on success:
{:ok? true
:seed base seed used (for reproducibility)
:iterations-run number of iterations completed}
Returns on failure:
{:ok? false
:kind :exception | :property-failed
:seed seed used for this iteration
:schedule schedule that caused failure (for replay)
:trace full trace
:iteration which iteration failed
:error exception (present when :kind is :exception)
:value result value (present when :kind is :property-failed)}
Note: For reproducible tests, always specify :seed. Without it, the current
system time is used, making results non-reproducible across runs.(clock)Returns the current time in milliseconds.
Use this in production code that needs timestamps, so tests can control time:
(defn log-with-timestamp [msg] {:time (mt/clock) :msg msg})
Returns the current time in milliseconds.
- Production (outside with-determinism): System/currentTimeMillis (JVM) or js/Date.now (CLJS)
- Test mode (inside with-determinism): virtual time from the scheduler
Use this in production code that needs timestamps, so tests can control time:
(defn log-with-timestamp [msg]
{:time (mt/clock) :msg msg})(collect flow)(collect flow {:keys [xf timeout-ms] :or {xf nil}})Convenience: consume a flow into a task yielding a vector (or reduced value).
(mt/collect flow {:xf (take 10) :timeout-ms 1000 :label "optional"})
Notes:
Convenience: consume a flow into a task yielding a vector (or reduced value).
(mt/collect flow {:xf (take 10)
:timeout-ms 1000
:label "optional"})
Notes:
- If :timeout-ms is provided, wraps with mt/timeout and returns ::mt/timeout on expiry.(cpu-executor)Deterministic CPU executor for m/via. Lane :cpu retained for introspection/trace. Must be called inside with-determinism with a scheduler bound to scheduler.
Deterministic CPU executor for m/via. Lane :cpu retained for introspection/trace. Must be called inside with-determinism with a scheduler bound to *scheduler*.
(done? job)Has the job completed (success or failure)?
Has the job completed (success or failure)?
(executor)Deterministic java.util.concurrent.Executor. Enqueues runnables as scheduler microtasks. Must be called inside with-determinism with a scheduler bound to scheduler.
Deterministic java.util.concurrent.Executor. Enqueues runnables as scheduler microtasks. Must be called inside with-determinism with a scheduler bound to *scheduler*.
(explore-interleavings task-fn
{:keys [num-samples seed max-steps]
:or {num-samples 100 max-steps 10000}})Explore different interleavings of a task and return a summary.
IMPORTANT: Must be called inside a with-determinism body.
task-fn should be a 0-arg function that returns a fresh task for each test. This ensures mutable state (like atoms) is reset between iterations.
Options:
Returns: {:unique-results - count of distinct results seen :results - vector of {:result r :micro-schedule s} maps :seed - the base seed used (for reproducibility)}
Note: For reproducible tests, always specify :seed. Without it, the current system time is used, making results non-reproducible across runs.
Explore different interleavings of a task and return a summary.
IMPORTANT: Must be called inside a with-determinism body.
task-fn should be a 0-arg function that returns a fresh task for each test.
This ensures mutable state (like atoms) is reset between iterations.
Options:
- :num-samples - number of different interleavings to try (default 100)
- :seed - base seed for RNG (default: current time)
- :max-steps - max scheduler steps per run (default 10000)
Returns:
{:unique-results - count of distinct results seen
:results - vector of {:result r :micro-schedule s} maps
:seed - the base seed used (for reproducibility)}
Note: For reproducible tests, always specify :seed. Without it, the current
system time is used, making results non-reproducible across runs.(-cancel! x)(make-scheduler)(make-scheduler {:keys [initial-ms seed trace? micro-schedule]
:or {initial-ms 0 trace? false micro-schedule nil}})Create a deterministic TestScheduler.
Options: {:initial-ms 0 :seed nil | number ; nil (default) = FIFO ordering ; number = random ordering with that seed :trace? true|false :micro-schedule nil | vector ; explicit selection decisions (overrides seed)}
Ordering behavior:
The :micro-schedule option provides explicit control over microtask selection when you need to replay a specific interleaving or test a particular order.
Thread safety: On JVM, all scheduler operations must be performed from a single thread. Cross-thread callbacks will throw an error to catch accidental nondeterminism.
Create a deterministic TestScheduler.
Options:
{:initial-ms 0
:seed nil | number ; nil (default) = FIFO ordering
; number = random ordering with that seed
:trace? true|false
:micro-schedule nil | vector ; explicit selection decisions (overrides seed)}
Ordering behavior:
- No seed (or nil): FIFO ordering for both timers and microtasks.
Predictable, good for unit tests with specific expected order.
- With seed: Random ordering (seeded RNG) for both timers and microtasks.
Deterministic but shuffled, good for fuzz/property testing.
The :micro-schedule option provides explicit control over microtask selection
when you need to replay a specific interleaving or test a particular order.
Thread safety: On JVM, all scheduler operations must be performed from a single
thread. Cross-thread callbacks will throw an error to catch accidental nondeterminism.(next-event sched)Returns info about what would execute next, or nil if idle.
Useful for stepwise debugging and agent introspection.
Returns:
Note: This reflects FIFO order. Actual selection may differ if a seed is provided (random ordering) or an explicit micro-schedule is configured.
Returns info about what would execute next, or nil if idle.
Useful for stepwise debugging and agent introspection.
Returns:
- {:type :microtask :id ... :kind ... :label ... :lane ...}
when a microtask is ready to run
- {:type :timer :id ... :kind ... :label ... :at-ms ... :lane ...}
when no microtasks but a timer is pending
- nil when scheduler is idle (no work pending)
Note: This reflects FIFO order. Actual selection may differ if a seed is
provided (random ordering) or an explicit micro-schedule is configured.(next-tasks sched)Returns vector of available microtasks that can be selected for execution.
Use this for manual stepping to see which tasks are available and their IDs. Each task map contains :id :kind :label :lane keys.
Usage for manual stepping: (mt/start! sched task) (let [tasks (mt/next-tasks sched)] (println "Available:" (mapv :id tasks)) (mt/step! sched (-> tasks first :id))) ; step specific task
Returns empty vector if no microtasks are ready (check timers with next-event).
Returns vector of available microtasks that can be selected for execution.
Use this for manual stepping to see which tasks are available and their IDs.
Each task map contains :id :kind :label :lane keys.
Usage for manual stepping:
(mt/start! sched task)
(let [tasks (mt/next-tasks sched)]
(println "Available:" (mapv :id tasks))
(mt/step! sched (-> tasks first :id))) ; step specific task
Returns empty vector if no microtasks are ready (check timers with next-event).(now-ms sched)Current virtual time in milliseconds.
Current virtual time in milliseconds.
(pending sched)Stable, printable data describing queued microtasks and timers.
Stable, printable data describing queued microtasks and timers.
(replay task-fn failure)(replay task-fn failure opts)Replay a failure from check-interleaving.
IMPORTANT: Must be called inside a with-determinism body.
Takes a failure bundle (from check-interleaving) and a task factory, re-runs the task with the same schedule that caused the failure.
Usage: (let [result (mt/check-interleaving make-task {:seed 42 :property valid?})] (when-not (:ok? result) (mt/replay make-task result)))
Options (merged with failure bundle):
Returns the task result (same as replay-schedule).
Replay a failure from check-interleaving.
IMPORTANT: Must be called inside a with-determinism body.
Takes a failure bundle (from check-interleaving) and a task factory,
re-runs the task with the same schedule that caused the failure.
Usage:
(let [result (mt/check-interleaving make-task {:seed 42 :property valid?})]
(when-not (:ok? result)
(mt/replay make-task result)))
Options (merged with failure bundle):
- :trace? - whether to record trace (default true)
- :max-steps - max scheduler steps (default 100000)
- :max-time-ms - max virtual time (default 60000)
Returns the task result (same as replay-schedule).(replay-schedule task schedule)(replay-schedule task
schedule
{:keys [trace? max-steps max-time-ms]
:or {trace? true max-steps 100000 max-time-ms 60000}})Run a task with the exact schedule from a previous trace. Returns the task result.
IMPORTANT: Must be called inside a with-determinism body.
Usage: (def original-trace (mt/trace sched)) (with-determinism (mt/replay-schedule (make-task) (mt/trace->schedule original-trace)))
Run a task with the exact schedule from a previous trace.
Returns the task result.
IMPORTANT: Must be called inside a with-determinism body.
Usage:
(def original-trace (mt/trace sched))
(with-determinism
(mt/replay-schedule (make-task) (mt/trace->schedule original-trace)))(result job)Returns job value, throws job failure, or ::pending.
Returns job value, throws job failure, or ::pending.
(run sched task)(run sched task opts)Run task deterministically to completion (or throw).
Automatically binds scheduler to sched for the duration of execution, so m/via with m/cpu or m/blk works correctly.
JVM: returns value or throws. CLJS: returns a js/Promise that resolves/rejects.
Run task deterministically to completion (or throw). Automatically binds *scheduler* to sched for the duration of execution, so m/via with m/cpu or m/blk works correctly. JVM: returns value or throws. CLJS: returns a js/Promise that resolves/rejects.
(scheduled-flow sched flow)(scheduled-flow sched flow {:keys [label]})Wrap a flow to marshal readiness/termination signals through the scheduler.
(mt/scheduled-flow sched flow {:label ...})
Wrap a flow to marshal readiness/termination signals through the scheduler.
(mt/scheduled-flow sched flow {:label ...})(sleep ms)(sleep ms x)Virtual sleep task.
(mt/sleep ms) (mt/sleep ms x)
Semantics:
Virtual sleep task. (mt/sleep ms) (mt/sleep ms x) Semantics: - completes after delay with x (or nil) - cancelling fails immediately with missionary.Cancelled
(start! sched task)(start! sched task {:keys [label] :as _opts})Start a Missionary task under the scheduler and return a Job handle.
Automatically binds scheduler to sched for the task invocation.
(def job (mt/start! sched task {:label "optional"}))
Start a Missionary task under the scheduler and return a Job handle.
Automatically binds *scheduler* to sched for the task invocation.
(def job (mt/start! sched task {:label "optional"}))(step! sched)(step! sched task-id)Run exactly 1 microtask. Returns ::idle if no microtasks.
(step! sched) - select next task per schedule/FIFO/random (step! sched task-id) - run specific task by ID (for manual stepping)
Binds scheduler to sched for the duration of execution.
Run exactly 1 microtask. Returns ::idle if no microtasks. (step! sched) - select next task per schedule/FIFO/random (step! sched task-id) - run specific task by ID (for manual stepping) Binds *scheduler* to sched for the duration of execution.
(tick! sched)Drain all microtasks at current virtual time. Returns number of microtasks executed.
Binds scheduler to sched for the duration of execution.
Drain all microtasks at current virtual time. Returns number of microtasks executed. Binds *scheduler* to sched for the duration of execution.
(timeout task ms)(timeout task ms x)Virtual timeout wrapper task.
(mt/timeout task ms) (mt/timeout task ms x)
Semantics:
Virtual timeout wrapper task. (mt/timeout task ms) (mt/timeout task ms x) Semantics: - if input completes before ms, propagate success/failure - else, cancel input task and succeed with x (default nil) - cancelling the timeout task fails with missionary.Cancelled (and cancels input).
(trace sched)Vector of trace events if enabled, else nil.
Vector of trace events if enabled, else nil.
(trace->schedule trace)Extract the sequence of task IDs from a trace for replay. Returns a vector of task IDs [id1 id2 id3 ...] that can be used to replay the exact same execution order.
Usage: (def schedule (mt/trace->schedule (mt/trace sched))) ;; => [2 4 3] ; bare task IDs ;; User can inspect/modify: [2 3 4] ; different order (mt/replay-schedule (make-task) schedule)
Extract the sequence of task IDs from a trace for replay. Returns a vector of task IDs [id1 id2 id3 ...] that can be used to replay the exact same execution order. Usage: (def schedule (mt/trace->schedule (mt/trace sched))) ;; => [2 4 3] ; bare task IDs ;; User can inspect/modify: [2 3 4] ; different order (mt/replay-schedule (make-task) schedule)
(with-determinism & body)Scope deterministic behavior to a test body by rebinding/redefining Missionary vars.
IMPORTANT: This macro is the entry point to deterministic behavior. All flows and tasks under test MUST be created inside the macro body (or by factory functions called from within the body). Tasks/flows created BEFORE or OUTSIDE the macro will capture the real (non-virtual) primitives and will NOT be deterministic.
Usage: (with-determinism (let [sched (mt/make-scheduler)] (mt/run sched (m/sp (m/? (m/sleep 100)) :done))))
Also correct (factory function called inside): (defn make-task [] (m/sp (m/? (m/sleep 100)) :done)) (with-determinism (let [sched (mt/make-scheduler)] (mt/run sched (make-task))))
WRONG (task created outside - will use real time!): (def my-task (m/sp (m/? (m/sleep 100)) :done)) ; WRONG: created outside (with-determinism (let [sched (mt/make-scheduler)] (mt/run sched my-task))) ; m/sleep was NOT rebound when task was created
Effects:
NOTE: m/via with m/cpu or m/blk works correctly because these executors are rebound to run work as scheduler microtasks. Do NOT use real executors (e.g., Executors/newFixedThreadPool) inside with-determinism.
NOTE: mt/run and mt/start! automatically bind scheduler to sched, so you don't need any explicit binding - just pass the scheduler as an argument.
INTERRUPT BEHAVIOR: When a via task is cancelled before its microtask executes, the via body will run with Thread.interrupted() returning true. Blocking calls in the via body will throw InterruptedException. The interrupt flag is cleared after the via body completes, so the scheduler remains usable.
CONCURRENCY: Uses reference counting to make var rebinding safe for parallel test runs. Multiple tests can run concurrently - first acquires the rebindings, last restores originals.
Scope deterministic behavior to a test body by rebinding/redefining Missionary vars.
IMPORTANT: This macro is the entry point to deterministic behavior. All flows
and tasks under test MUST be created inside the macro body (or by factory
functions called from within the body). Tasks/flows created BEFORE or OUTSIDE
the macro will capture the real (non-virtual) primitives and will NOT be
deterministic.
Usage:
(with-determinism
(let [sched (mt/make-scheduler)]
(mt/run sched
(m/sp (m/? (m/sleep 100)) :done))))
Also correct (factory function called inside):
(defn make-task [] (m/sp (m/? (m/sleep 100)) :done))
(with-determinism
(let [sched (mt/make-scheduler)]
(mt/run sched (make-task))))
WRONG (task created outside - will use real time!):
(def my-task (m/sp (m/? (m/sleep 100)) :done)) ; WRONG: created outside
(with-determinism
(let [sched (mt/make-scheduler)]
(mt/run sched my-task))) ; m/sleep was NOT rebound when task was created
Effects:
- Sets *is-deterministic* to true
- missionary.core/sleep -> mt/sleep
- missionary.core/timeout -> mt/timeout
- missionary.core/cpu -> deterministic executor (JVM only)
- missionary.core/blk -> deterministic executor (JVM only)
NOTE: m/via with m/cpu or m/blk works correctly because these executors
are rebound to run work as scheduler microtasks. Do NOT use real executors
(e.g., Executors/newFixedThreadPool) inside with-determinism.
NOTE: mt/run and mt/start! automatically bind *scheduler* to sched, so you
don't need any explicit binding - just pass the scheduler as an argument.
INTERRUPT BEHAVIOR: When a via task is cancelled before its microtask executes,
the via body will run with Thread.interrupted() returning true. Blocking calls
in the via body will throw InterruptedException. The interrupt flag is cleared
after the via body completes, so the scheduler remains usable.
CONCURRENCY: Uses reference counting to make var rebinding safe for parallel test runs.
Multiple tests can run concurrently - first acquires the rebindings, last restores originals.(yield)(yield x)Yield point task for testing interleavings.
(mt/yield) (mt/yield x)
In production (outside with-determinism): completes immediately with x (or nil). In test mode (inside with-determinism): creates a scheduling point that allows other concurrent tasks to interleave, then completes with x.
This is useful for:
Example: ;; In production, this just returns :done immediately (m/? (mt/yield :done))
;; In tests with check-interleaving, different orderings are explored (mt/check-interleaving (fn [] (let [result (atom [])] (m/sp (m/? (m/join vector (m/sp (swap! result conj :a) (m/? (mt/yield)) (swap! result conj :a2)) (m/sp (swap! result conj :b) (m/? (mt/yield)) (swap! result conj :b2)))) @result))) {:property (fn [r] (= 4 (count r)))})
Yield point task for testing interleavings.
(mt/yield)
(mt/yield x)
In production (outside with-determinism): completes immediately with x (or nil).
In test mode (inside with-determinism): creates a scheduling point that allows
other concurrent tasks to interleave, then completes with x.
This is useful for:
- Testing concurrent code under different task orderings
- Creating explicit interleaving points without time delays
- Simulating cooperative multitasking yield points
Example:
;; In production, this just returns :done immediately
(m/? (mt/yield :done))
;; In tests with check-interleaving, different orderings are explored
(mt/check-interleaving
(fn []
(let [result (atom [])]
(m/sp
(m/? (m/join vector
(m/sp (swap! result conj :a) (m/? (mt/yield)) (swap! result conj :a2))
(m/sp (swap! result conj :b) (m/? (mt/yield)) (swap! result conj :b2))))
@result)))
{:property (fn [r] (= 4 (count r)))})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 |