foundationdb

Module env

Source
Expand description

Pluggable sources for the effects that must stay deterministic under FoundationDB simulation: time and randomness.

§Why control flow must not read ambient time or randomness

The deterministic simulator virtualizes both: it decides what time it is and which pseudo-random choices are made, which is precisely what lets it replay a failing run from a seed. Code that calls Instant::now() or draws from a thread-local generator steps outside that control, so its behavior changes from run to run and a bug found in simulation cannot be reproduced.

The way out is to take time and randomness as dependencies instead of reaching for them: Clock and Rng, bundled in an Environment. Production code uses WallClock and SeededRng, which do the obvious thing. Simulation workloads pass implementations backed by the simulator, provided by the foundationdb-simulation crate.

Ambient wall-clock time remains fine for purely observational output, metrics durations or log timestamps that nothing branches on.

§Measure with monotonic, persist with wall

Clock offers two readings and they are not interchangeable. Clock::monotonic never goes backwards but counts from an epoch private to that one instance, so it is the right and only choice for durations, deadlines, budgets and backoff. Clock::wall is a real timestamp, counted from the UNIX epoch, so it is the right choice for anything written to the database or compared across processes, at the cost of being able to jump when the machine clock is adjusted.

Never persist a monotonic reading and never compare two of them coming from different Clock instances: the values are meaningless outside the instance that produced them.

§Drawing a lot of randomness

Rng is deliberately minimal, one u64 per call through a shared reference. A component that needs a large volume of random data, or distributions this trait does not offer, should draw a single u64 and use it to seed its own local generator (rand_chacha::ChaCha8Rng::seed_from_u64 for instance). The run stays reproducible, since the seed came from the environment, and the heavy sampling stays local and fast.

§Non-goals

This module supplies values, never control over execution: there is no sleep and no spawn capability here. Under simulation the caller owns the schedule: it advances simulated time and re-invokes the deterministic unit of work. An asynchronous Timer capability is planned surface and is deliberately deferred, so do not work around its absence by sleeping on the ambient runtime inside code that must be simulable.

§Known limitation

Under simulation, wall time is derived from simulated time and therefore never jumps. Bugs that only appear when the machine clock is stepped by NTP, a leap second or an operator, cannot be exercised in simulation: exercise those with a purpose-built Clock in a unit test instead.

Structs§

  • The effects a layer needs but must not reach for: time and randomness.
  • The Rng of production code: SplitMix64, seeded once.
  • The Clock of production code, reading the clocks of the machine.

Traits§

  • Where time comes from.
  • Where pseudo-random values come from.