Liking cljdoc? Tell your friends :D

de.levering-it.missionary-testkit


*is-deterministic*clj/s

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).
raw docstring

*scheduler*clj/s

Dynamically bound to the current TestScheduler in deterministic tests.

Dynamically bound to the current TestScheduler in deterministic tests.
raw docstring

advance!clj/s

(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.
raw docstring

advance-to!clj/s

(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.
raw docstring

budget-exceededclj/s


cancel!clj/s

(cancel! x)

Cancel a Job handle.

Cancel a Job handle.
raw docstring

cancel-microtask!clj/s

(cancel-microtask! sched microtask-id)

Cancel an enqueued microtask by its ID, removing it from the microtask queue.

This is a scheduler-level cancellation that completely removes the microtask from the queue, so it won't consume a scheduler step or advance virtual time.

Returns true if the microtask was found and removed, false otherwise.

Usage: (let [id (mt/enqueue-microtask! sched work-fn {:label "work"})] ;; Later, before it executes: (mt/cancel-microtask! sched id))

Note: For yield/timeout/via-call, the cancel thunk returned at creation time will call this internally. Direct use is for advanced scenarios where you store the microtask ID and want to cancel before execution.

Cancel an enqueued microtask by its ID, removing it from the microtask queue.

This is a scheduler-level cancellation that completely removes the microtask
from the queue, so it won't consume a scheduler step or advance virtual time.

Returns true if the microtask was found and removed, false otherwise.

Usage:
  (let [id (mt/enqueue-microtask! sched work-fn {:label "work"})]
    ;; Later, before it executes:
    (mt/cancel-microtask! sched id))

Note: For yield/timeout/via-call, the cancel thunk returned at creation time
will call this internally. Direct use is for advanced scenarios where you
store the microtask ID and want to cancel before execution.
raw docstring

check-interleavingclj/s

(check-interleaving task-fn
                    {:keys [num-tests seed property max-steps max-time-ms
                            duration-range initial-ms timer-policy cpu-threads]
                     :or {num-tests 100
                          max-steps 10000
                          max-time-ms 60000
                          initial-ms 0
                          timer-policy :promote-first
                          cpu-threads 8}})

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)
  • :duration-range - [lo hi] virtual duration range for microtasks (default nil) When set, each microtask gets a random duration in [lo,hi]ms. This enables realistic timeout races and timing exploration.
  • :timer-policy - :promote-first (default) or :microtasks-first Controls whether timers compete with microtasks at same time.
  • :cpu-threads - CPU thread pool size (default 8) Controls max concurrent :cpu lane tasks.

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 (for RNG replay) :schedule schedule that caused failure (for replay) :duration-range [lo hi] if timing fuzz was enabled (for replay) :timer-policy timer policy if non-default (for replay) :cpu-threads cpu thread pool size if non-default (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.

The failure bundle contains all information needed to reproduce the exact failure using mt/replay, including timing and thread pool configuration.

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)
- :duration-range - [lo hi] virtual duration range for microtasks (default nil)
                    When set, each microtask gets a random duration in [lo,hi]ms.
                    This enables realistic timeout races and timing exploration.
- :timer-policy   - :promote-first (default) or :microtasks-first
                    Controls whether timers compete with microtasks at same time.
- :cpu-threads    - CPU thread pool size (default 8)
                    Controls max concurrent :cpu lane tasks.

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 (for RNG replay)
 :schedule       schedule that caused failure (for replay)
 :duration-range [lo hi] if timing fuzz was enabled (for replay)
 :timer-policy   timer policy if non-default (for replay)
 :cpu-threads    cpu thread pool size if non-default (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.

The failure bundle contains all information needed to reproduce the exact
failure using mt/replay, including timing and thread pool configuration.
raw docstring

clockclj/s

(clock)

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

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})
raw docstring

collectclj/s

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

  • If :timeout-ms is provided, wraps with mt/timeout and returns ::mt/timeout on expiry.
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.
raw docstring

deadlockclj/s


deterministic-via-call*clj/s≠

(deterministic-via-call* exec thunk)
clj

Deterministic replacement for missionary.core/via-call.

Contract (deterministic mode):

  • This is a 'scheduler hop' - thunk runs on the microtask queue, not a real executor.
  • Thunk SHOULD be pure or non-blocking. m/? inside thunk works but blocks the scheduler.
  • Thunk is executed synchronously once its microtask starts; no real concurrency.
  • Duration-range applies: thunk gets a virtual duration for timeout race testing.

Cancellation semantics:

  • Before microtask starts: task is removed from queue, no CPU/time consumed.
  • During microtask execution: NOT interruptible (single-threaded scheduler).
  • After microtask completes: result discarded if cancel races with completion.
  • Always fails with missionary.Cancelled when cancelled.

Limitations vs production:

  • No JVM thread interrupt - cancellation is cooperative only.
  • Thunk cannot be interrupted mid-execution (use yield points for interleaving).
  • Real I/O in thunk will break determinism (same as any real I/O in tests).
Deterministic replacement for missionary.core/via-call.

Contract (deterministic mode):
- This is a 'scheduler hop' - thunk runs on the microtask queue, not a real executor.
- Thunk SHOULD be pure or non-blocking. m/? inside thunk works but blocks the scheduler.
- Thunk is executed synchronously once its microtask starts; no real concurrency.
- Duration-range applies: thunk gets a virtual duration for timeout race testing.

Cancellation semantics:
- Before microtask starts: task is removed from queue, no CPU/time consumed.
- During microtask execution: NOT interruptible (single-threaded scheduler).
- After microtask completes: result discarded if cancel races with completion.
- Always fails with missionary.Cancelled when cancelled.

Limitations vs production:
- No JVM thread interrupt - cancellation is cooperative only.
- Thunk cannot be interrupted mid-execution (use yield points for interleaving).
- Real I/O in thunk will break determinism (same as any real I/O in tests).
raw docstring

done?clj/s

(done? job)

Has the job completed (success or failure)?

Has the job completed (success or failure)?
raw docstring

explore-interleavingsclj/s

(explore-interleavings task-fn
                       {:keys [num-samples seed max-steps duration-range
                               timer-policy cpu-threads]
                        :or {num-samples 100
                             max-steps 10000
                             timer-policy :promote-first
                             cpu-threads 8}})

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)
  • :duration-range - [lo hi] virtual duration range for microtasks (default nil) When set, each microtask gets a random duration in [lo,hi]ms. This enables realistic timeout races and timing exploration.
  • :timer-policy - :promote-first (default) or :microtasks-first Controls whether timers compete with microtasks at same time.
  • :cpu-threads - CPU thread pool size (default 8) Controls max concurrent :cpu lane tasks.

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)
- :duration-range - [lo hi] virtual duration range for microtasks (default nil)
                    When set, each microtask gets a random duration in [lo,hi]ms.
                    This enables realistic timeout races and timing exploration.
- :timer-policy   - :promote-first (default) or :microtasks-first
                    Controls whether timers compete with microtasks at same time.
- :cpu-threads    - CPU thread pool size (default 8)
                    Controls max concurrent :cpu lane tasks.

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.
raw docstring

ICancellableclj/sprotocol

-cancel!clj/s

(-cancel! x)

idleclj/s


idle-awaiting-timeclj/s


idle-blockedclj/s


idle-detailsclj/s

(idle-details x)

Get detailed diagnostics from an idle step! result. Returns nil if not idle, or a map with:

  • :reason - why idle (see idle-reason)
  • :blocked-lanes - map of {lane {:blocked-count n :active a :max m :waiting [ids]}}
  • :pending-count - number of pending microtasks
  • :timer-count - number of pending timers
  • :in-flight-count - number of in-flight tasks
  • :next-time - next event time (timer or in-flight completion), or nil
Get detailed diagnostics from an idle step! result.
Returns nil if not idle, or a map with:
- :reason - why idle (see idle-reason)
- :blocked-lanes - map of {lane {:blocked-count n :active a :max m :waiting [ids]}}
- :pending-count - number of pending microtasks
- :timer-count - number of pending timers
- :in-flight-count - number of in-flight tasks
- :next-time - next event time (timer or in-flight completion), or nil
raw docstring

idle-emptyclj/s


idle-reasonclj/s

(idle-reason x)

Get the reason for idle state from a step! result. Returns nil if not idle, or one of:

  • ::idle-empty - truly nothing pending (queue empty, no in-flight)
  • ::idle-blocked - tasks pending but all their lanes are at capacity
  • ::idle-awaiting-time - waiting for time to advance (timers/in-flight pending)
Get the reason for idle state from a step! result.
Returns nil if not idle, or one of:
- ::idle-empty - truly nothing pending (queue empty, no in-flight)
- ::idle-blocked - tasks pending but all their lanes are at capacity
- ::idle-awaiting-time - waiting for time to advance (timers/in-flight pending)
raw docstring

idle?clj/s

(idle? x)

Check if a step! result indicates idle state. Works with both ::idle keyword and IdleStatus records.

IMPORTANT: Use this instead of (= ::idle result) for step! results. step! now returns IdleStatus records with diagnostic information.

Check if a step! result indicates idle state. Works with both ::idle keyword
and IdleStatus records.

IMPORTANT: Use this instead of (= ::idle result) for step! results.
step! now returns IdleStatus records with diagnostic information.
raw docstring

illegal-blocking-emissionclj/s


illegal-transferclj/s


make-schedulerclj/s

(make-scheduler)
(make-scheduler {:keys [initial-ms seed trace? micro-schedule duration-range
                        timer-policy cpu-threads]
                 :or {initial-ms 0
                      trace? false
                      micro-schedule nil
                      duration-range nil
                      timer-policy :promote-first
                      cpu-threads 8}})

Create a deterministic TestScheduler.

Options: {:initial-ms 0 :seed nil | number ; nil (default) = FIFO task selection ; number = random selection with that seed :trace? true|false :micro-schedule nil | vector ; explicit selection decisions (overrides seed) :duration-range nil | [lower upper] ; virtual duration range for microtasks ; nil (default) = 0ms duration (instant) ; [lo hi] = random duration in [lo,hi]ms per task :timer-policy :promote-first | :microtasks-first :cpu-threads 8} ; CPU thread pool size (default 8)

Task selection behavior:

  • No seed (or nil): FIFO selection from the microtask queue. Predictable, good for unit tests with specific expected order.
  • With seed: Random selection from the microtask queue (seeded RNG). Deterministic but shuffled, good for fuzz/property testing.

Timer promotion:

  • Timers are always promoted to the microtask queue in FIFO order (by id) when their at-ms is reached.
  • Random tie-breaking for same-time timers happens at selection time, not at promotion time. This is captured in :select-task trace events.

Duration behavior:

  • No duration-range (or nil): All microtasks complete instantly (0ms). Current behavior, backward compatible.
  • With duration-range [lo hi]: Each microtask gets a random duration in [lo,hi]ms at enqueue time. Before the callback runs, virtual time advances by that duration. This enables realistic timeout races and timing-dependent interleaving.

Timer policy (microtasks vs timers at same virtual time):

  • :promote-first (default) - promote due timers to microtask queue first, then select among all available. More adversarial; timers compete equally.
  • :microtasks-first - drain existing microtasks first, then promote/run timers due at that time. JS-like microtask priority; more realistic.

Thread pool modeling:

  • :cpu-threads (default 8) - simulates a fixed-size CPU thread pool. When cpu-threads tasks are in-flight, additional :cpu lane tasks must wait.
  • :blk lane - unlimited threads, each blocking call gets its own thread.
  • :default lane - single-threaded microtask queue (like JS event loop).
  • Tasks with duration > 0 occupy their thread for that duration.
  • Multiple in-flight tasks can have overlapping execution times.

RNG streams:

  • Uses separate RNG streams for task selection and duration generation.
  • This means enabling :duration-range won't change interleaving order.
  • During replay, schedule + seed + duration-range produces identical 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 task selection
                                     ; number = random selection with that seed
 :trace?         true|false
 :micro-schedule nil | vector        ; explicit selection decisions (overrides seed)
 :duration-range nil | [lower upper] ; virtual duration range for microtasks
                                     ; nil (default) = 0ms duration (instant)
                                     ; [lo hi] = random duration in [lo,hi]ms per task
 :timer-policy   :promote-first | :microtasks-first
 :cpu-threads    8}                  ; CPU thread pool size (default 8)

Task selection behavior:
- No seed (or nil): FIFO selection from the microtask queue.
  Predictable, good for unit tests with specific expected order.
- With seed: Random selection from the microtask queue (seeded RNG).
  Deterministic but shuffled, good for fuzz/property testing.

Timer promotion:
- Timers are always promoted to the microtask queue in FIFO order (by id)
  when their at-ms is reached.
- Random tie-breaking for same-time timers happens at selection time,
  not at promotion time. This is captured in :select-task trace events.

Duration behavior:
- No duration-range (or nil): All microtasks complete instantly (0ms).
  Current behavior, backward compatible.
- With duration-range [lo hi]: Each microtask gets a random duration in [lo,hi]ms
  at enqueue time. Before the callback runs, virtual time advances by that duration.
  This enables realistic timeout races and timing-dependent interleaving.

Timer policy (microtasks vs timers at same virtual time):
- :promote-first (default) - promote due timers to microtask queue first,
  then select among all available. More adversarial; timers compete equally.
- :microtasks-first - drain existing microtasks first, then promote/run
  timers due at that time. JS-like microtask priority; more realistic.

Thread pool modeling:
- :cpu-threads (default 8) - simulates a fixed-size CPU thread pool.
  When cpu-threads tasks are in-flight, additional :cpu lane tasks must wait.
- :blk lane - unlimited threads, each blocking call gets its own thread.
- :default lane - single-threaded microtask queue (like JS event loop).
- Tasks with duration > 0 occupy their thread for that duration.
- Multiple in-flight tasks can have overlapping execution times.

RNG streams:
- Uses separate RNG streams for task selection and duration generation.
- This means enabling :duration-range won't change interleaving order.
- During replay, schedule + seed + duration-range produces identical 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.
raw docstring

microtask-exceptionclj/s


next-eventclj/s

(next-event sched)

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.

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.
raw docstring

next-tasksclj/s

(next-tasks sched)

Returns vector of available microtasks that can be selected for execution.

Only returns tasks whose lanes have capacity - tasks blocked by lane limits are excluded. Use (mt/pending sched) to see all pending tasks regardless of lane capacity.

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.

Only returns tasks whose lanes have capacity - tasks blocked by lane limits
are excluded. Use (mt/pending sched) to see all pending tasks regardless of
lane capacity.

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).
raw docstring

not-in-deterministic-modeclj/s


now-msclj/s

(now-ms sched)

Current virtual time in milliseconds.

Current virtual time in milliseconds.
raw docstring

off-scheduler-callbackclj/s


pendingclj/s

(pending sched)

Stable, printable data describing queued microtasks and timers.

Stable, printable data describing queued microtasks and timers.
raw docstring

replayclj/s

(replay task-fn failure)

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 exact schedule and configuration that caused the failure.

Usage: (let [result (mt/check-interleaving make-task {:seed 42 :property valid?})] (when-not (:ok? result) (mt/with-determinism (mt/replay make-task result))))

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 exact schedule and configuration that caused
the failure.

Usage:
  (let [result (mt/check-interleaving make-task {:seed 42 :property valid?})]
    (when-not (:ok? result)
      (mt/with-determinism
        (mt/replay make-task result))))
raw docstring

replay-scheduleclj/s

(replay-schedule task schedule)
(replay-schedule task schedule opts)

Run a task with the exact schedule and configuration from a previous run. Returns the task result.

IMPORTANT: Must be called inside a with-determinism body.

Typically you should use mt/replay instead, which automatically extracts the correct configuration from a check-interleaving failure bundle.

Run a task with the exact schedule and configuration from a previous run.
Returns the task result.

IMPORTANT: Must be called inside a with-determinism body.

Typically you should use mt/replay instead, which automatically extracts
the correct configuration from a check-interleaving failure bundle.
raw docstring

resultclj/s

(result job)

Returns job value, throws job failure, or ::pending.

Returns job value, throws job failure, or ::pending.
raw docstring

runclj/s

(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.
raw docstring

schedule-exhaustedclj/s


scheduled-flowclj/s

(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 ...})
raw docstring

sleepclj/s

(sleep ms)
(sleep ms x)

Sleep task that dispatches based on determinism mode.

(mt/sleep ms) (mt/sleep ms x)

Behavior:

  • In deterministic mode (is-deterministic true): uses virtual time via scheduler
  • In production mode: delegates to original missionary.core/sleep

The dispatch decision is made at task execution time, not at creation time. This allows tasks created outside with-determinism to still use virtual time when executed inside with-determinism.

Semantics (both modes):

  • completes after delay with x (or nil)
  • cancelling fails immediately with missionary.Cancelled
Sleep task that dispatches based on determinism mode.

(mt/sleep ms)
(mt/sleep ms x)

Behavior:
- In deterministic mode (*is-deterministic* true): uses virtual time via scheduler
- In production mode: delegates to original missionary.core/sleep

The dispatch decision is made at task execution time, not at creation time.
This allows tasks created outside with-determinism to still use virtual time
when executed inside with-determinism.

Semantics (both modes):
- completes after delay with x (or nil)
- cancelling fails immediately with missionary.Cancelled
raw docstring

start!clj/s

(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"}))
raw docstring

step!clj/s

(step! sched)
(step! sched task-id)

Execute one scheduler step: complete an in-flight task OR start a new one.

Thread pool model:

  • :default lane: single-threaded (max 1 concurrent task)
  • :cpu lane: fixed thread pool (configurable via :cpu-threads, default 8)
  • :blk lane: unlimited threads (each blocking call gets its own)

Step behavior:

  1. Promote due timers (based on :timer-policy)
  2. Complete any in-flight task whose end-ms <= now (runs completion callback)
  3. If no completions, start a new task from queue (if lane has capacity):
    • Tasks with duration > 0: start as in-flight, thread occupied until end-ms
    • Tasks with duration == 0: execute immediately (backward compatible)
  4. Return IdleStatus if nothing can be done (use idle? to check)

Return value:

  • On success: the executed task map
  • On idle: IdleStatus record with diagnostic information:
    • Use (idle? result) to check if idle
    • Use (idle-reason result) to get why: ::idle-empty, ::idle-blocked, ::idle-awaiting-time
    • Use (idle-details result) for full diagnostics including blocked-lanes, counts, next-time

Timer promotion behavior depends on :timer-policy:

  • :promote-first (default): promote due timers first, then select among all.
  • :microtasks-first: only promote timers when microtask queue is empty.

(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.

Execute one scheduler step: complete an in-flight task OR start a new one.

Thread pool model:
- :default lane: single-threaded (max 1 concurrent task)
- :cpu lane: fixed thread pool (configurable via :cpu-threads, default 8)
- :blk lane: unlimited threads (each blocking call gets its own)

Step behavior:
1. Promote due timers (based on :timer-policy)
2. Complete any in-flight task whose end-ms <= now (runs completion callback)
3. If no completions, start a new task from queue (if lane has capacity):
   - Tasks with duration > 0: start as in-flight, thread occupied until end-ms
   - Tasks with duration == 0: execute immediately (backward compatible)
4. Return IdleStatus if nothing can be done (use idle? to check)

Return value:
- On success: the executed task map
- On idle: IdleStatus record with diagnostic information:
  - Use (idle? result) to check if idle
  - Use (idle-reason result) to get why: ::idle-empty, ::idle-blocked, ::idle-awaiting-time
  - Use (idle-details result) for full diagnostics including blocked-lanes, counts, next-time

Timer promotion behavior depends on :timer-policy:
- :promote-first (default): promote due timers first, then select among all.
- :microtasks-first: only promote timers when microtask queue is empty.

(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.
raw docstring

task-id-not-foundclj/s


tick!clj/s

(tick! sched)

Drain all microtasks at current virtual time. Returns number of microtasks executed.

Timer promotion follows the scheduler's :timer-policy (see step! docs). With :promote-first (default), timers compete with microtasks. With :microtasks-first, microtasks drain before timers are promoted.

Binds scheduler to sched for the duration of execution.

Drain all microtasks at current virtual time. Returns number of microtasks executed.

Timer promotion follows the scheduler's :timer-policy (see step! docs).
With :promote-first (default), timers compete with microtasks.
With :microtasks-first, microtasks drain before timers are promoted.

Binds *scheduler* to sched for the duration of execution.
raw docstring

timeoutclj/s

(timeout task ms)
(timeout task ms x)

Timeout wrapper task that dispatches based on determinism mode.

(mt/timeout task ms) (mt/timeout task ms x)

Behavior:

  • In deterministic mode (is-deterministic true): uses virtual time via scheduler
  • In production mode: delegates to original missionary.core/timeout

The dispatch decision is made at task execution time, not at creation time. This allows tasks created outside with-determinism to still use virtual time when executed inside with-determinism.

Semantics (both modes):

  • 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).
Timeout wrapper task that dispatches based on determinism mode.

(mt/timeout task ms)
(mt/timeout task ms x)

Behavior:
- In deterministic mode (*is-deterministic* true): uses virtual time via scheduler
- In production mode: delegates to original missionary.core/timeout

The dispatch decision is made at task execution time, not at creation time.
This allows tasks created outside with-determinism to still use virtual time
when executed inside with-determinism.

Semantics (both modes):
- 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).
raw docstring

traceclj/s

(trace sched)

Vector of trace events if enabled, else nil.

Vector of trace events if enabled, else nil.
raw docstring

trace->scheduleclj/s

(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)
raw docstring

unknown-decisionclj/s


unsupported-executorclj/s


via-callclj/s

(via-call exec thunk)

Execute thunk on executor, with dispatch based on determinism mode.

In deterministic mode (is-deterministic true):

  • Thunk runs on the scheduler's microtask queue (not a real executor).
  • This is a 'scheduler hop' for deterministic testing, not real thread pool execution.
  • Thunk SHOULD be pure or non-blocking; m/? works but blocks the scheduler.
  • Duration-range applies: gets virtual duration for timeout race testing.
  • Cancellation removes from queue if not started, or races with completion.
  • Only m/cpu and m/blk executors are supported; others throw.

In production mode (is-deterministic false):

  • Delegates to original missionary.core/via-call.
  • Thunk runs on the actual executor with real concurrency.

Contract for deterministic testing:

  • Thunk cannot be interrupted mid-execution (no Thread.interrupt).
  • For interleaving at points within thunk, use mt/yield explicitly.
  • Tests passing under testkit may still have concurrency bugs in production if thunk relies on real thread interruption.

See deterministic-via-call* for detailed cancellation semantics.

Execute thunk on executor, with dispatch based on determinism mode.

In deterministic mode (*is-deterministic* true):
- Thunk runs on the scheduler's microtask queue (not a real executor).
- This is a 'scheduler hop' for deterministic testing, not real thread pool execution.
- Thunk SHOULD be pure or non-blocking; m/? works but blocks the scheduler.
- Duration-range applies: gets virtual duration for timeout race testing.
- Cancellation removes from queue if not started, or races with completion.
- Only m/cpu and m/blk executors are supported; others throw.

In production mode (*is-deterministic* false):
- Delegates to original missionary.core/via-call.
- Thunk runs on the actual executor with real concurrency.

Contract for deterministic testing:
- Thunk cannot be interrupted mid-execution (no Thread.interrupt).
- For interleaving at points within thunk, use mt/yield explicitly.
- Tests passing under testkit may still have concurrency bugs in production
  if thunk relies on real thread interruption.

See deterministic-via-call* for detailed cancellation semantics.
raw docstring

with-determinismcljmacro

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

  • Sets is-deterministic to true
  • missionary.core/sleep -> mt/sleep (dispatches to virtual or original based on is-deterministic)
  • missionary.core/timeout -> mt/timeout (dispatches to virtual or original based on is-deterministic)
  • missionary.core/cpu -> mt/cpu (dispatches to deterministic or original based on is-deterministic)
  • missionary.core/blk -> mt/blk (dispatches to deterministic or original based on is-deterministic)

DISPATCHING BEHAVIOR: mt/sleep, mt/timeout, mt/cpu, and mt/blk are wrapper functions/values that check is-deterministic at call/deref time. When true, they use virtual time via the scheduler. When false (outside with-determinism), they delegate to the original missionary implementations, allowing the same code to work in both test and production contexts.

NOTE: m/via with m/cpu or m/blk works correctly because these executors dispatch based on is-deterministic. 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.

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 (dispatches to virtual or original based on *is-deterministic*)
- missionary.core/timeout  -> mt/timeout (dispatches to virtual or original based on *is-deterministic*)
- missionary.core/cpu      -> mt/cpu (dispatches to deterministic or original based on *is-deterministic*)
- missionary.core/blk      -> mt/blk (dispatches to deterministic or original based on *is-deterministic*)

DISPATCHING BEHAVIOR: mt/sleep, mt/timeout, mt/cpu, and mt/blk are wrapper
functions/values that check *is-deterministic* at call/deref time. When true,
they use virtual time via the scheduler. When false (outside with-determinism),
they delegate to the original missionary implementations, allowing the same code
to work in both test and production contexts.

NOTE: m/via with m/cpu or m/blk works correctly because these executors
dispatch based on *is-deterministic*. 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.

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.
raw docstring

yieldclj/s

(yield)
(yield x)
(yield x opts)

Yield point task for testing interleavings.

(mt/yield) (mt/yield x) (mt/yield x opts)

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.

Options map (optional, only used in deterministic mode):

  • :duration-fn (fn [] ms) - called before enqueueing to get this yield's duration. Takes precedence over scheduler's :duration-range.

Run-then-complete model:

  • When duration > 0, yield occupies the lane immediately but delays result delivery
  • Work executes at start time, result delivered at completion time
  • For yield, there's no user work - it's a pure scheduling delay

This is useful for:

  • Testing concurrent code under different task orderings
  • Creating explicit interleaving points without time delays
  • Simulating cooperative multitasking yield points
  • Modeling specific work durations with :duration-fn

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 with explicit duration (m/? (mt/yield :result {:duration-fn (constantly 50)}))

Yield point task for testing interleavings.

(mt/yield)
(mt/yield x)
(mt/yield x opts)

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.

Options map (optional, only used in deterministic mode):
- :duration-fn  (fn [] ms) - called before enqueueing to get this yield's duration.
                Takes precedence over scheduler's :duration-range.

Run-then-complete model:
- When duration > 0, yield occupies the lane immediately but delays result delivery
- Work executes at start time, result delivered at completion time
- For yield, there's no user work - it's a pure scheduling delay

This is useful for:
- Testing concurrent code under different task orderings
- Creating explicit interleaving points without time delays
- Simulating cooperative multitasking yield points
- Modeling specific work durations with :duration-fn

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 with explicit duration
  (m/? (mt/yield :result {:duration-fn (constantly 50)}))
raw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close