shuttle_engine/runtime/
runner.rs1use crate::config::Config;
2use crate::runtime::execution::Execution;
3use crate::runtime::task::{Task, TaskId};
4use crate::runtime::thread::continuation::{ContinuationPool, CONTINUATION_POOL};
5use crate::scheduler::metrics::MetricsScheduler;
6use crate::scheduler::{Schedule, Scheduler};
7use std::cell::RefCell;
8use std::fmt;
9use std::panic::{self, Location};
10use std::rc::Rc;
11use std::sync::atomic::{AtomicBool, Ordering};
12use std::sync::mpsc;
13use std::sync::Arc;
14use std::thread;
15use std::time::Instant;
16use tracing::{span, Level};
17
18#[must_use]
23struct ResetSpanOnDrop {
24 span: tracing::Span,
25}
26
27impl ResetSpanOnDrop {
28 fn new() -> Self {
29 Self {
30 span: tracing::Span::current().clone(),
31 }
32 }
33}
34
35impl Drop for ResetSpanOnDrop {
36 fn drop(&mut self) {
38 tracing::dispatcher::get_default(|subscriber| {
39 while let Some(span_id) = tracing::Span::current().id().as_ref() {
40 subscriber.exit(span_id);
41 }
42 if let Some(span_id) = self.span.id().as_ref() {
43 subscriber.enter(span_id);
44 }
45 });
46 }
47}
48
49#[derive(Debug)]
55pub struct Runner<S: ?Sized + Scheduler> {
56 scheduler: Rc<RefCell<MetricsScheduler<S>>>,
57 config: Config,
58}
59
60impl<S: Scheduler + 'static> Runner<S> {
61 pub fn new(scheduler: S, config: Config) -> Self {
63 let metrics_scheduler = MetricsScheduler::new(scheduler);
64
65 Self {
66 scheduler: Rc::new(RefCell::new(metrics_scheduler)),
67 config,
68 }
69 }
70
71 #[track_caller]
74 pub fn run<F>(self, f: F) -> usize
75 where
76 F: Fn() + Send + Sync + 'static,
77 {
78 let _span_drop_guard = ResetSpanOnDrop::new();
79 CONTINUATION_POOL.set(&ContinuationPool::new(), || {
83 let f = Arc::new(f);
84
85 let start = Instant::now();
86
87 let mut i = 0;
88
89 loop {
90 if self.config.max_time.map(|t| start.elapsed() > t).unwrap_or(false) {
91 break;
92 }
93
94 let schedule = match self.scheduler.borrow_mut().new_execution() {
95 None => break,
96 Some(s) => s,
97 };
98
99 let execution = Execution::new(self.scheduler.clone(), schedule);
100 let f = Arc::clone(&f);
101
102 let _span_drop_guard2 = ResetSpanOnDrop::new();
106
107 span!(Level::ERROR, "execution", i)
108 .in_scope(|| execution.run(&self.config, move || f(), Location::caller()));
109
110 i += 1;
111 }
112 i
113 })
114 }
115}
116
117pub struct PortfolioRunner {
121 schedulers: Vec<Box<dyn Scheduler + Send + 'static>>,
122 stop_on_first_failure: bool,
123 config: Config,
124}
125
126impl PortfolioRunner {
127 pub fn new(stop_on_first_failure: bool, config: Config) -> Self {
131 Self {
132 schedulers: Vec::new(),
133 stop_on_first_failure,
134 config,
135 }
136 }
137
138 pub fn add(&mut self, scheduler: impl Scheduler + Send + 'static) {
140 self.schedulers.push(Box::new(scheduler));
141 }
142
143 pub fn run<F>(self, f: F)
146 where
147 F: Fn() + Send + Sync + 'static,
148 {
149 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
150 enum ThreadResult {
151 Passed,
152 Failed,
153 }
154
155 let (tx, rx) = mpsc::sync_channel::<ThreadResult>(0);
156 let stop_signal = Arc::new(AtomicBool::new(false));
157 let config = self.config;
158 let f = Arc::new(f);
159
160 let threads = self
161 .schedulers
162 .into_iter()
163 .enumerate()
164 .map(|(i, scheduler)| {
165 let f = Arc::clone(&f);
166 let tx = tx.clone();
167 let stop_signal = stop_signal.clone();
168 let config = config.clone();
169
170 thread::spawn(move || {
171 let scheduler = PortfolioStoppableScheduler { scheduler, stop_signal };
172
173 let runner = Runner::new(scheduler, config);
174
175 span!(Level::ERROR, "job", i).in_scope(|| {
176 let ret = panic::catch_unwind(panic::AssertUnwindSafe(|| runner.run(move || f())));
177
178 match ret {
179 Ok(_) => tx.send(ThreadResult::Passed),
180 Err(e) => {
181 tx.send(ThreadResult::Failed).unwrap();
182 panic::resume_unwind(e);
183 }
184 }
185 })
186 })
187 })
188 .collect::<Vec<_>>();
189
190 for _ in 0..threads.len() {
192 if rx.recv().unwrap() == ThreadResult::Failed && self.stop_on_first_failure {
193 stop_signal.store(true, Ordering::SeqCst);
194 }
195 }
196
197 let mut panic = None;
201 for thread in threads {
202 if let Err(e) = thread.join() {
203 panic = Some(e);
204 }
205 }
206 assert!(stop_signal.load(Ordering::SeqCst) == panic.is_some());
207 if let Some(e) = panic {
208 std::panic::resume_unwind(e);
209 }
210 }
211}
212
213impl fmt::Debug for PortfolioRunner {
214 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
215 f.debug_struct("PortfolioRunner")
216 .field("schedulers", &self.schedulers.len())
217 .field("stop_on_first_failure", &self.stop_on_first_failure)
218 .field("config", &self.config)
219 .finish()
220 }
221}
222
223#[derive(Debug)]
226struct PortfolioStoppableScheduler<S> {
227 scheduler: S,
228 stop_signal: Arc<AtomicBool>,
229}
230
231impl<S: Scheduler> Scheduler for PortfolioStoppableScheduler<S> {
232 fn new_execution(&mut self) -> Option<Schedule> {
233 if self.stop_signal.load(Ordering::SeqCst) {
234 None
235 } else {
236 self.scheduler.new_execution()
237 }
238 }
239
240 fn next_task(
241 &mut self,
242 runnable_tasks: &[&Task],
243 current_task: Option<TaskId>,
244 is_yielding: bool,
245 ) -> Option<TaskId> {
246 if self.stop_signal.load(Ordering::SeqCst) {
247 None
248 } else {
249 self.scheduler.next_task(runnable_tasks, current_task, is_yielding)
250 }
251 }
252
253 fn next_u64(&mut self) -> u64 {
254 self.scheduler.next_u64()
255 }
256}