Skip to content

Commit 95b637b

Browse files
author
Dylan Wolff
committed
Adding TimeModels to Shuttle
1 parent 668c1e0 commit 95b637b

File tree

12 files changed

+1562
-32
lines changed

12 files changed

+1562
-32
lines changed

shuttle/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ cfg-if = "1.0"
1515
generator = "0.8.1"
1616
hex = "0.4.2"
1717
owo-colors = "3.5.0"
18+
pin-project = "1.1.3"
1819
rand_core = "0.6.4"
1920
rand = "0.8.5"
2021
rand_pcg = "0.3.1"
@@ -38,7 +39,6 @@ tempfile = "3.2.0"
3839
test-log = { version = "0.2.8", default-features = false, features = ["trace"] }
3940
tracing-subscriber = { version = "0.3.9", features = ["env-filter"] }
4041
trybuild = "1.0"
41-
pin-project = "1.1.3"
4242
# The following line is necessary to ensure vector-clocks are used for integration tests
4343
# To run performance tests without vector clocks using `cargo bench`, this line must be commented out
4444
shuttle = { path = ".", features = ["vector-clocks"] }
@@ -55,6 +55,7 @@ annotation = ["dep:serde", "dep:serde_json", "dep:regex"]
5555
# are otherwise always enabled via a dev-dependency to ensure all *test* assertions utilizing vector
5656
# clocks behave correctly during testing
5757
bench-no-vector-clocks = []
58+
advanced-time-models = []
5859

5960
[[bench]]
6061
name = "lock"

shuttle/src/runtime/execution.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::runtime::task::labels::Labels;
55
use crate::runtime::task::{ChildLabelFn, Task, TaskId, TaskName, TaskSignature, DEFAULT_INLINE_TASKS};
66
use crate::runtime::thread::continuation::PooledContinuation;
77
use crate::scheduler::{Schedule, Scheduler};
8+
use crate::sync::time::TimeModel;
89
use crate::sync::{ResourceSignature, ResourceType};
910
use crate::thread::thread_fn;
1011
use crate::{Config, MaxSteps};
@@ -48,15 +49,21 @@ thread_local! {
4849
/// static variable, but clients get access to it by calling `ExecutionState::with`.
4950
pub(crate) struct Execution {
5051
scheduler: Rc<RefCell<dyn Scheduler>>,
52+
time_model: Rc<RefCell<dyn TimeModel>>,
5153
initial_schedule: Schedule,
5254
}
5355

5456
impl Execution {
5557
/// Construct a new execution that will use the given scheduler. The execution should then be
5658
/// invoked via its `run` method, which takes as input the closure for task 0.
57-
pub(crate) fn new(scheduler: Rc<RefCell<dyn Scheduler>>, initial_schedule: Schedule) -> Self {
59+
pub(crate) fn new(
60+
scheduler: Rc<RefCell<dyn Scheduler>>,
61+
initial_schedule: Schedule,
62+
time_model: Rc<RefCell<dyn TimeModel>>,
63+
) -> Self {
5864
Self {
5965
scheduler,
66+
time_model,
6067
initial_schedule,
6168
}
6269
}
@@ -77,6 +84,7 @@ impl Execution {
7784
let state = RefCell::new(ExecutionState::new(
7885
config.clone(),
7986
Rc::clone(&self.scheduler),
87+
Rc::clone(&self.time_model),
8088
self.initial_schedule.clone(),
8189
));
8290

@@ -95,6 +103,7 @@ impl Execution {
95103

96104
// Cleanup the state before it goes out of `EXECUTION_STATE` scope
97105
ExecutionState::cleanup();
106+
self.time_model.borrow_mut().reset();
98107
});
99108
}
100109

@@ -107,6 +116,13 @@ impl Execution {
107116
Finished,
108117
}
109118

119+
// While there are no runnable tasks and tasks are able to be woken by the time model, continue waking tasks
120+
while ExecutionState::num_runnable() == 0
121+
&& ExecutionState::with(|s| Rc::clone(&s.time_model))
122+
.borrow_mut()
123+
.wake_next()
124+
{}
125+
110126
let next_step = ExecutionState::with(|state| {
111127
if let Err(msg) = state.schedule() {
112128
return NextStep::Failure(msg, state.current_schedule.clone());
@@ -281,6 +297,10 @@ pub(crate) struct ExecutionState {
281297
// Persistent Vec used as a bump allocator for references to runnable tasks to avoid slow allocation
282298
// on each scheduling decision. Should not be used outside of the `schedule` function
283299
runnable_tasks: Vec<*const Task>,
300+
301+
// Counter for unique timing resource ids (Sleeps, Timeouts and Intervals)
302+
pub(crate) timer_id_counter: u64,
303+
pub(crate) time_model: Rc<RefCell<dyn TimeModel>>,
284304
}
285305

286306
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
@@ -305,7 +325,12 @@ impl ScheduledTask {
305325
}
306326

307327
impl ExecutionState {
308-
fn new(config: Config, scheduler: Rc<RefCell<dyn Scheduler>>, initial_schedule: Schedule) -> Self {
328+
fn new(
329+
config: Config,
330+
scheduler: Rc<RefCell<dyn Scheduler>>,
331+
time_model: Rc<RefCell<dyn TimeModel>>,
332+
initial_schedule: Schedule,
333+
) -> Self {
309334
Self {
310335
config,
311336
tasks: SmallVec::new(),
@@ -322,17 +347,21 @@ impl ExecutionState {
322347
has_cleaned_up: false,
323348
top_level_span: tracing::Span::current(),
324349
runnable_tasks: Vec::with_capacity(DEFAULT_INLINE_TASKS),
350+
time_model,
351+
timer_id_counter: 0,
325352
}
326353
}
327354

328355
/// Invoke a closure with access to the current execution state. Library code uses this to gain
329356
/// access to the state of the execution to influence scheduling (e.g. to register a task as
330357
/// blocked).
331358
#[inline]
359+
#[track_caller]
332360
pub(crate) fn with<F, T>(f: F) -> T
333361
where
334362
F: FnOnce(&mut ExecutionState) -> T,
335363
{
364+
trace!("ExecutionState::with from {}", Location::caller());
336365
Self::try_with(f).expect("Shuttle internal error: cannot access ExecutionState. are you trying to access a Shuttle primitive from outside a Shuttle test?")
337366
}
338367

@@ -550,6 +579,8 @@ impl ExecutionState {
550579
TASK_ID_TO_TAGS.with(|cell| cell.borrow_mut().clear());
551580
LABELS.with(|cell| cell.borrow_mut().clear());
552581

582+
Self::with(|s| s.timer_id_counter = 0);
583+
553584
#[cfg(debug_assertions)]
554585
Self::with(|state| state.has_cleaned_up = true);
555586

@@ -565,6 +596,13 @@ impl ExecutionState {
565596
/// is different from the currently running task, indicating that the current task should yield
566597
/// its execution.
567598
pub(crate) fn maybe_yield() -> bool {
599+
// While there are no runnable tasks and tasks are able to be woken by the time model, continue waking tasks
600+
while ExecutionState::num_runnable() == 0
601+
&& ExecutionState::with(|s| Rc::clone(&s.time_model))
602+
.borrow_mut()
603+
.wake_next()
604+
{}
605+
568606
Self::with(|state| {
569607
debug_assert!(
570608
matches!(state.current_task, ScheduledTask::Some(_)) && state.next_task == ScheduledTask::None,
@@ -666,11 +704,19 @@ impl ExecutionState {
666704
Self::with(|state| state.context_switches)
667705
}
668706

707+
pub(crate) fn num_tasks(&self) -> usize {
708+
self.tasks.len()
709+
}
710+
669711
#[track_caller]
670712
pub(crate) fn new_resource_signature(resource_type: ResourceType) -> ResourceSignature {
671713
ExecutionState::with(|s| s.current_mut().signature.new_resource(resource_type))
672714
}
673715

716+
pub(crate) fn num_runnable() -> usize {
717+
Self::with(|state| state.tasks.iter().filter(|t| t.runnable()).count())
718+
}
719+
674720
pub(crate) fn get_storage<K: Into<StorageKey>, T: 'static>(&self, key: K) -> Option<&T> {
675721
self.storage
676722
.get(key.into())

shuttle/src/runtime/runner.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::runtime::task::{Task, TaskId};
33
use crate::runtime::thread::continuation::{ContinuationPool, CONTINUATION_POOL};
44
use crate::scheduler::metrics::MetricsScheduler;
55
use crate::scheduler::{Schedule, Scheduler};
6+
use crate::sync::time::{frozen::FrozenTimeModel, TimeModel};
67
use crate::Config;
78
use std::cell::RefCell;
89
use std::fmt;
@@ -52,18 +53,33 @@ impl Drop for ResetSpanOnDrop {
5253
/// function as many times as dictated by the scheduler; each execution has its scheduling decisions
5354
/// resolved by the scheduler, which can make different choices for each execution.
5455
#[derive(Debug)]
55-
pub struct Runner<S: ?Sized + Scheduler> {
56+
pub struct Runner<S: ?Sized + Scheduler, T: TimeModel> {
5657
scheduler: Rc<RefCell<MetricsScheduler<S>>>,
58+
time_model: Rc<RefCell<T>>,
5759
config: Config,
5860
}
5961

60-
impl<S: Scheduler + 'static> Runner<S> {
62+
impl<S: Scheduler + 'static> Runner<S, FrozenTimeModel> {
6163
/// Construct a new `Runner` that will use the given `Scheduler` to control the test.
6264
pub fn new(scheduler: S, config: Config) -> Self {
6365
let metrics_scheduler = MetricsScheduler::new(scheduler);
6466

6567
Self {
6668
scheduler: Rc::new(RefCell::new(metrics_scheduler)),
69+
time_model: Rc::new(RefCell::new(FrozenTimeModel::new())),
70+
config,
71+
}
72+
}
73+
}
74+
75+
impl<S: Scheduler + 'static, T: TimeModel + 'static> Runner<S, T> {
76+
/// Construct a new `Runner` that will use the given `Scheduler` to control the test.
77+
pub fn new_with_time_model(scheduler: S, time_model: T, config: Config) -> Self {
78+
let metrics_scheduler = MetricsScheduler::new(scheduler);
79+
80+
Self {
81+
scheduler: Rc::new(RefCell::new(metrics_scheduler)),
82+
time_model: Rc::new(RefCell::new(time_model)),
6783
config,
6884
}
6985
}
@@ -96,7 +112,7 @@ impl<S: Scheduler + 'static> Runner<S> {
96112
Some(s) => s,
97113
};
98114

99-
let execution = Execution::new(self.scheduler.clone(), schedule);
115+
let execution = Execution::new(self.scheduler.clone(), schedule, self.time_model.clone());
100116
let f = Arc::clone(&f);
101117

102118
// This is a slightly lazy way to ensure that everything outside of the "execution" span gets
@@ -122,7 +138,6 @@ pub struct PortfolioRunner {
122138
stop_on_first_failure: bool,
123139
config: Config,
124140
}
125-
126141
impl PortfolioRunner {
127142
/// Construct a new `PortfolioRunner` with no schedulers. If `stop_on_first_failure` is true,
128143
/// all schedulers will be terminated as soon as any fails; if false, they will keep running

shuttle/src/runtime/thread/continuation.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,10 +257,12 @@ unsafe impl Send for PooledContinuation {}
257257
/// Possibly yield back to the executor to perform a context switch.
258258
pub(crate) fn switch() {
259259
crate::annotations::record_tick();
260+
260261
if ExecutionState::maybe_yield() {
261262
let r = generator::yield_(ContinuationOutput::Yielded).unwrap();
262263
assert!(matches!(r, ContinuationInput::Resume));
263264
}
265+
ExecutionState::with(|s| Rc::clone(&s.time_model)).borrow_mut().step();
264266
}
265267

266268
#[cfg(test)]

shuttle/src/sync/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub mod mpsc;
77
mod mutex;
88
mod once;
99
mod rwlock;
10+
pub mod time;
1011

1112
pub use barrier::{Barrier, BarrierWaitResult};
1213
pub use condvar::{Condvar, WaitTimeoutResult};

0 commit comments

Comments
 (0)