ferrule

deterministic scheduling

β
mock-clockmock-rngdeterministic-schedulerinterleaving-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

FeaturePurpose
mock_clockdeterministic time
mock_rngdeterministic randomness
deterministic_schedulerreproducible task ordering
explore_interleavingssystematic testing
race detectionfind data races in tests

On this page