deterministic scheduling
β◌ mock-clock◌ mock-rng◌ deterministic-scheduler◌ interleaving-exploration
deterministic scheduling and testing
this feature is planned for β. the spec describes what it will be, not what's implemented now.
Overview
Ferrule supports determinism on demand: the ability to replay exact execution sequences for testing and debugging.
Deterministic Mode
In test mode, the runtime:
- replaces schedulers with deterministic versions
- stubs time and RNG capabilities
- records/replays task interleavings
Time Capability Stubbing
function test_timeout_behavior() {
const mock_clock = testing.mock_clock(start = Time.epoch())
scope timeout(Duration.seconds(10), mock_clock) {
scope parallel {
// this operation will see mock time
const result = check fetch_with_timeout(url, net)
}
// advance time deterministically
mock_clock.advance(Duration.seconds(5))
// task still running...
mock_clock.advance(Duration.seconds(6))
// now past deadline, cancellation triggers
} on timeout {
// timeout handled
}
}RNG Capability Stubbing
function test_randomized_algorithm() {
const mock_rng = testing.mock_rng(seed = 12345)
// algorithm receives deterministic RNG
const result1 = shuffle(items, mock_rng)
// reset and replay
mock_rng.reset()
const result2 = shuffle(items, mock_rng)
// result1 === result2 (same seed, same sequence)
}Scheduler Instrumentation
The deterministic scheduler can:
- record all task switches
- replay exact interleavings
- detect data races (in debug builds)
function test_concurrent_access() {
const scheduler = testing.deterministic_scheduler(seed = 42)
scheduler.run(function() {
scope parallel {
check producer()
check consumer()
}
})
// replay with same seed produces identical behavior
}Race Detection
In deterministic mode, the runtime can instrument shared memory access:
function test_no_races() {
const scheduler = testing.deterministic_scheduler(
seed = 42,
detect_races = true
)
scheduler.run(function() {
// concurrent code...
})
// if races detected, test fails with detailed report
}Interleaving Exploration
Test multiple interleavings systematically:
function test_all_interleavings() {
testing.explore_interleavings(max_runs = 1000, function() {
scope parallel {
check task_a()
check task_b()
}
// assert invariants hold regardless of interleaving
assert(invariant_holds())
})
}Capability Injection Pattern
The capability system makes deterministic testing natural:
// production code
function process(cap clock: Clock, cap rng: Rng) -> Result
effects [time, rng, fail<ProcessError>]
{
const delay = rng.range(100, 500)
clock.sleep(Duration.ms(delay))
// ...
}
// test code
function test_process() {
const mock_clock = testing.mock_clock()
const mock_rng = testing.mock_rng(seed = 123)
// inject mocks, no code changes needed
const result = process(mock_clock, mock_rng)
// verify behavior with deterministic time/randomness
}Summary
| Feature | Purpose |
|---|---|
mock_clock | deterministic time |
mock_rng | deterministic randomness |
deterministic_scheduler | reproducible task ordering |
explore_interleavings | systematic testing |
| race detection | find data races in tests |