foundationdb/env.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! 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.
use std::collections::hash_map::RandomState;
use std::fmt;
use std::hash::{BuildHasher, Hasher};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
/// Where time comes from.
///
/// See the [module documentation](self) for why time is a dependency rather
/// than something to reach for, and for the rule that decides between the two
/// readings: measure with [`monotonic`](Self::monotonic), persist with
/// [`wall`](Self::wall).
pub trait Clock: fmt::Debug + Send + Sync {
/// A monotonic reading, for measuring how much time has passed.
///
/// Successive readings never decrease, which makes this the reading to use
/// for elapsed times, deadlines, budgets and backoff.
///
/// <div class="warning">
///
/// The epoch is arbitrary and **private to this instance**. Never persist a
/// monotonic reading, and never compare readings taken from two different
/// `Clock` instances: only the difference between two readings of the *same*
/// instance carries meaning.
///
/// </div>
fn monotonic(&self) -> Duration;
/// A wall-clock reading, time elapsed since the UNIX epoch.
///
/// This is a real timestamp, comparable across processes and machines, so it
/// is the reading to persist in the database or put in a message. In
/// production it can jump forwards or backwards when the machine clock is
/// adjusted (NTP, a leap second, an operator), so never measure a duration
/// with it. Under simulation it is deterministic.
fn wall(&self) -> Duration;
}
/// Where pseudo-random values come from.
///
/// See the [module documentation](self) for why randomness is a dependency
/// rather than something to reach for, and for the pattern to follow when a lot
/// of randomness is needed.
///
/// <div class="warning">
///
/// Implementations are **not** cryptographically secure and must not be used to
/// derive secrets, tokens or keys.
///
/// </div>
pub trait Rng: fmt::Debug + Send + Sync {
/// The next value of the sequence, uniform over the whole `u64` range.
///
/// This takes `&self` so that a generator can be shared: an implementation
/// advances its state through interior mutability.
fn next_u64(&self) -> u64;
/// The next value of the sequence, uniform over the whole `u32` range.
///
/// The default implementation truncates [`next_u64`](Self::next_u64).
fn next_u32(&self) -> u32 {
self.next_u64() as u32
}
}
/// The [`Clock`] of production code, reading the clocks of the machine.
///
/// [`monotonic`](Clock::monotonic) counts from the moment the instance was
/// built, so the first reading of a fresh `WallClock` is close to zero.
#[derive(Debug, Clone)]
pub struct WallClock {
anchor: Instant,
}
impl WallClock {
/// Builds a clock whose monotonic epoch is now.
#[cfg_attr(feature = "trace", tracing::instrument(level = "debug"))]
pub fn new() -> Self {
Self {
anchor: Instant::now(),
}
}
}
impl Default for WallClock {
fn default() -> Self {
Self::new()
}
}
impl Clock for WallClock {
fn monotonic(&self) -> Duration {
self.anchor.elapsed()
}
fn wall(&self) -> Duration {
// A system clock set before 1970 is the only way this fails, and no
// caller can do anything useful with an error here.
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
}
}
/// The [`Rng`] of production code: SplitMix64, seeded once.
///
/// [`new`](Self::new) takes the seed, which makes the whole sequence
/// reproducible: the same seed always yields the same values in the same order.
/// [`Default`] takes its seed from the entropy the standard library uses to seed
/// `HashMap`, which is best effort, unpredictable in practice but neither
/// reproducible nor guaranteed by anything.
///
/// <div class="warning">
///
/// SplitMix64 is a fast statistical generator, **not** a cryptographic one.
///
/// </div>
#[derive(Debug)]
pub struct SeededRng {
state: AtomicU64,
}
/// The golden-ratio increment of SplitMix64.
const SPLITMIX_GAMMA: u64 = 0x9E37_79B9_7F4A_7C15;
impl SeededRng {
/// Builds a generator producing the sequence of `seed`, reproducibly.
#[cfg_attr(feature = "trace", tracing::instrument(level = "debug"))]
pub fn new(seed: u64) -> Self {
Self {
state: AtomicU64::new(seed),
}
}
}
impl Default for SeededRng {
fn default() -> Self {
Self::new(RandomState::new().build_hasher().finish())
}
}
impl Rng for SeededRng {
fn next_u64(&self) -> u64 {
// fetch_add hands back the state this call consumed and leaves the next
// one in place, so concurrent callers each finalize a distinct value.
let z = self.state.fetch_add(SPLITMIX_GAMMA, Ordering::Relaxed);
let z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
let z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
}
/// The effects a layer needs but must not reach for: time and randomness.
///
/// Cloning is cheap, both sources sit behind an `Arc`, so an `Environment` is
/// meant to be passed by value into the layers and recipes that need it and kept
/// alongside the [`Database`](crate::Database).
///
/// # Example
///
/// ```
/// # use foundationdb::env::Environment;
/// // Production: machine clock, generator seeded from entropy.
/// let env = Environment::default();
///
/// // A reproducible run: the same seed replays the same sequence.
/// let env = Environment::with_seed(42);
/// let first = env.rng().next_u64();
/// assert_eq!(Environment::with_seed(42).rng().next_u64(), first);
/// ```
#[derive(Clone, Debug)]
pub struct Environment {
clock: Arc<dyn Clock>,
rng: Arc<dyn Rng>,
}
impl Environment {
/// Bundles a clock and a generator.
#[cfg_attr(
feature = "trace",
tracing::instrument(level = "debug", skip(clock, rng))
)]
pub fn new(clock: Arc<dyn Clock>, rng: Arc<dyn Rng>) -> Self {
Self { clock, rng }
}
/// The machine clock, with a generator replaying the sequence of `seed`.
#[cfg_attr(feature = "trace", tracing::instrument(level = "debug"))]
pub fn with_seed(seed: u64) -> Self {
Self::new(Arc::new(WallClock::new()), Arc::new(SeededRng::new(seed)))
}
/// The clock of this environment.
pub fn clock(&self) -> &Arc<dyn Clock> {
&self.clock
}
/// The pseudo-random generator of this environment.
pub fn rng(&self) -> &Arc<dyn Rng> {
&self.rng
}
}
impl Default for Environment {
fn default() -> Self {
Self::new(Arc::new(WallClock::new()), Arc::new(SeededRng::default()))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn take(rng: &SeededRng, count: usize) -> Vec<u64> {
(0..count).map(|_| rng.next_u64()).collect()
}
#[test]
fn the_same_seed_produces_the_same_sequence() {
let expected = take(&SeededRng::new(42), 16);
assert_eq!(take(&SeededRng::new(42), 16), expected);
assert!(
expected.windows(2).any(|pair| pair[0] != pair[1]),
"the sequence is constant: {expected:?}"
);
}
#[test]
fn different_seeds_produce_different_sequences() {
assert_ne!(take(&SeededRng::new(42), 16), take(&SeededRng::new(43), 16));
}
#[test]
fn next_u32_truncates_next_u64() {
let wide = SeededRng::new(7);
let narrow = SeededRng::new(7);
for _ in 0..16 {
assert_eq!(narrow.next_u32(), wide.next_u64() as u32);
}
}
#[test]
fn wall_clock_monotonic_never_goes_backwards() {
let clock = WallClock::new();
let mut previous = clock.monotonic();
for _ in 0..1_000 {
let now = clock.monotonic();
assert!(now >= previous, "{now:?} is before {previous:?}");
previous = now;
}
}
#[test]
fn wall_clock_wall_is_a_real_timestamp() {
// Any machine running this test has a clock set past 2020.
const YEAR_2020: Duration = Duration::from_secs(1_577_836_800);
assert!(WallClock::new().wall() > YEAR_2020);
}
#[test]
fn the_default_environment_is_usable() {
let env = Environment::default();
assert!(env.clock().monotonic() < Duration::from_secs(1));
let drawn: Vec<u64> = (0..4).map(|_| env.rng().next_u64()).collect();
assert!(drawn.windows(2).any(|pair| pair[0] != pair[1]));
// Cloning shares the very same sources.
let clone = env.clone();
assert!(Arc::ptr_eq(clone.clock(), env.clock()));
assert!(Arc::ptr_eq(clone.rng(), env.rng()));
}
#[test]
fn a_seeded_environment_replays_its_sequence() {
let expected: Vec<u64> = (0..8)
.map(|_| Environment::with_seed(99).rng().next_u64())
.collect();
// Every fresh environment restarts the same sequence.
assert!(expected.iter().all(|value| *value == expected[0]));
let env = Environment::with_seed(99);
assert_eq!(env.rng().next_u64(), expected[0]);
assert_ne!(
env.rng().next_u64(),
expected[0],
"the sequence must advance"
);
}
#[test]
fn debug_output_names_the_implementations() {
assert!(format!("{:?}", WallClock::new()).contains("WallClock"));
assert!(format!("{:?}", SeededRng::new(1)).contains("SeededRng"));
let debug = format!("{:?}", Environment::with_seed(1));
assert!(debug.contains("Environment"), "{debug}");
assert!(debug.contains("WallClock"), "{debug}");
assert!(debug.contains("SeededRng"), "{debug}");
}
}