|
ravel
Deterministic simulation testing for C++. Seed a bug, replay it exact.
|
Cooperative, single-threaded task scheduler. More...
#include <scheduler.hpp>
Classes | |
| class | SleepAwaiter |
What sleep() returns. It is only ever co_awaited. More... | |
| class | YieldAwaiter |
What yield() returns. It is only ever co_awaited. More... | |
Public Member Functions | |
| Scheduler (VirtualClock &clock, VirtualRng &rng, Trace &trace) noexcept | |
| Created by Simulation; you do not construct one yourself. | |
| Scheduler (const Scheduler &)=delete | |
| Tasks hold references into the scheduler, so it must never move. | |
| Scheduler & | operator= (const Scheduler &)=delete |
| void | spawn (std::string name, TaskFactory factory) |
| Registers a task. | |
| YieldAwaiter | yield () noexcept |
co_await scheduler.yield(); lets the scheduler pick who runs next. | |
| SleepAwaiter | sleep (VirtualClock::Tick duration) noexcept |
co_await scheduler.sleep(50); suspends the task for 50 virtual ticks. | |
| RunReport | run_until_quiescent (std::uint64_t max_steps, VirtualClock::Tick time_limit=std::numeric_limits< VirtualClock::Tick >::max()) |
Runs until nothing is left to happen, a task throws, max_steps steps have been taken, or the next timer falls after time_limit on the virtual clock. | |
| const std::string & | task_name (TaskId id) const |
| The name a task was spawned with. | |
| VirtualClock::Tick | now () const noexcept |
| The current virtual time. | |
| TaskId | current_task () const noexcept |
| The task being resumed right now. Valid only inside a task. | |
| void | make_runnable (TaskId id) |
| Lets a suspended task run again on a later step. | |
| void | call_after (VirtualClock::Tick delay, std::function< void()> action) |
Runs action once the clock reaches now() + delay. | |
Cooperative, single-threaded task scheduler.
At every step it resumes one runnable task chosen by the seeded VirtualRng, so which task runs when depends only on the seed: never on wall-clock timing, thread scheduling, or hash-table iteration order. That is what makes a failing seed replayable, and what explores different interleavings across different seeds.
A simulation is single-threaded: while it runs, spawn() and call_after() (and so Channel::send and Disk operations) throw std::logic_error if called from any thread other than the one running it. That catches code under test that starts real threads and touches the simulation from them.
Time is virtual. When every task is asleep the clock jumps straight to the earliest wake-up, so simulated waiting costs no real time.
| void ravel::Scheduler::call_after | ( | VirtualClock::Tick | delay, |
| std::function< void()> | action | ||
| ) |
Runs action once the clock reaches now() + delay.
Actions due at the same time run in the order they were scheduled.
| void ravel::Scheduler::spawn | ( | std::string | name, |
| TaskFactory | factory | ||
| ) |
Registers a task.
It first runs on a later step of run_until_quiescent() (or on the current run, if called from inside a task).