Repository navigation
feat: deterministic clock, entropy, spawn_blocking and timers under the simulator (D1-D3) - #267
Conversation
…resh thread per run, strict mode
… and the direct tokio::time message (D3)
…-backend failure under sim
🤖 Codex Code Review
Tests could not run because rustup attempted to write to a read-only directory. Automated review by OpenAI Codex · gpt-6.1-sol · custom prompt |
🤖 Claude Code ReviewReview: PR #267, Determinism D1–D3This is solid work overall. The unsafe code is well isolated in High1.
2. HashMap-key seeding depends on construction order (
3.
4. Thread-per-run drops caller thread-locals (
Medium5. Duplicate
6. Interposers are not async-signal-safe (
7. Sim and Tokio API surfaces diverge (
8.
9.
10. MSRV
11. Breaking change in normal builds
Low / Nits
Automated review by Claude · claude-opus-5-5 · custom prompt |
🤖 Gemini Code ReviewThis PR is very well-structured and thoroughly tested. Isolating the A few small issues and suggestions below: 1.
|
🤖 Grok Code ReviewI'll verify a few high-risk spots in the simulator—time mirroring, Automated review by Grok (xAI) · grok-4.7 · custom prompt |
🤖 DeepSeek Code ReviewAutomated review by DeepSeek |
…join_next keeps wakers, cloneable AbortHandle, run-thread stack and HashMap-key seeding, Tokio runtime built on the real clock); lockfile from the pinned toolchain
🤖 Kimi Code ReviewOverall, the simulator changes are well documented and tested. I found no blocking actor-lifecycle or synchronization issues, but a few non-blocking concerns:
The fresh-thread harness, watchdog use of the real clock, and propagation of detached-task timer/nested- Automated review by Kimi (Moonshot AI) · kimi-k3.1-pro · custom prompt |
…ropy requests to libc
Determinism backlog D1-D3 (ROADMAP tier 3,
docs/research/determinism-audit.md). All of it applies under--cfg spawned_simonly; normal builds change only by the newspawned_rt::tasksitems and theSyncbound onsim_testbodies.D1: wall clock and OS entropy (interposition, plus strict mode)
Design: interposition (madsim's technique), with strict mode on top. A new crate,
spawned-sim-libc, definesclock_gettime,gettimeofday,getrandom,getentropy, on Linux thesyscallentry forSYS_getrandom(thegetrandom0.2 crate behind rand 0.8'sthread_rng/OsRng), and on macOSCCRandomGenerateBytes(std'sHashMapkeys there). The test binary's references (std'sInstant/SystemTime, std's weak-linkedgetrandomforHashMapkeys,rand,chrono) bind to them at link time. Each one asks hooks thatspawned-rtinstalls, and otherwise calls libc throughdlsym(RTLD_NEXT).block_on),Instant::now()readssim::VIRTUAL_BOOT(1 day) plus virtual time andSystemTime::now()readssim::VIRTUAL_EPOCH(2000-01-01) plus virtual time. A real Tokio runtime driven on the same thread outside the simulation keeps real time.spawned_rt::time::entropy.run_once/explore/sim_testrun each seed on a fresh OS thread. std'sHashMapkeys andrand::thread_rngare seeded once per thread, so this seeds them inside the run: a seed in a sweep, the same seed alone, a tape replay and a separate process see the same values. The cost is aSyncbound on bodies (MIGRATION.md).sim::wall_now(),real_clock).SPAWNED_SIM_STRICT=1/sim::set_strict(true)records the first direct read with its backtrace, and the next scheduling step fails the run with the caller's frames, the way a directtokio::spawnfails today.HashMapkey generation (GRND_INSECURE) is never reported.Unsafe containment.
spawned-sim-libcis#![cfg(spawned_sim)]at its root, so it compiles to an empty crate in every other build.spawned-rtdepends on it only under[target.'cfg(spawned_sim)'.dependencies].spawned-rt,spawned-concurrency,spawned-macrosandspawned-testkeep#![forbid(unsafe_code)]in every build. Inside the crate:deny(unsafe_op_in_unsafe_fn), a// SAFETY:on every block, hooks behindcatch_unwind(a panic falls back to libc), a re-entrancy flag so a hook's own reads go to libc, andconstthread-locals that are safe during thread teardown. It is published alongsidespawned-rt(release.yaml and the packaging check updated).Limits (TESTING.md):
syscallentry on x86_64/aarch64) and macOS are interposed (sim::INTERPOSED).getrandom0.3+ on Linux finds the symbol withdlsym(RTLD_DEFAULT)and is not covered unless the binary exports it.D2:
spawn_blockinginlinespawn_blocking(andHandle::spawn_blocking,JoinSet::spawn_blocking, actorspawn_blocking_helpers) runs its closure as the single poll of a new simulated task. That is one atomic step, never concurrent with anything else in the world.block_in_placecalls its closure. A nestedblock_onnow fails the run even when its panic lands in a task nobody joins, so aBackend::Blockingactor fails the run instead of silently never starting.D3: timers and task sets
spawned_rt::tasks::{sleep_until, interval, interval_at, Interval, MissedTickBehavior, JoinSet, AbortHandle, JoinError}are virtual under the simulator and Tokio's otherwise, with Tokio's semantics. Under the simulator,join_nextbreaks ties by task id. Tokio's "timers are disabled" panic, from the root or a detached task, now fails the run with a message namingspawned_rt::tasks::{sleep, sleep_until, timeout, interval}.Tests
rt/tests/sim_determinism.rs:Instant(absolute and steps),SystemTime,thread_rng,OsRng,getrandomandHashMap/HashSetorder across runs, across a sweep versus alone, across a tape replay, and across two separate processes.rt/tests/blocking_and_timers.rsruns on Tokio and on the simulator. Under the simulator it also checks exact virtual instants for Burst/Delay/Skip, inlinespawn_blocking, the nested-block_onfailure, deterministicJoinSetorder, and the directtokio::timemessage.concurrency/tests/sim.rs:spawn_blockingruns inline, and a Blocking-backend actor fails the run.Model: no change because this changes only the simulator backend (spawned-rt) and its tests, not modelled library code.