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
Rngof production code: SplitMix64, seeded once. - The
Clockof production code, reading the clocks of the machine.
Traits§
- Where time comes from.
- Where pseudo-random values come from.