Line data Source code
1 : use std::panic::AssertUnwindSafe;
2 : use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU32, Ordering};
3 : use std::sync::{Arc, OnceLock, mpsc};
4 : use std::thread::JoinHandle;
5 :
6 : use tracing::{debug, error, trace};
7 :
8 : use crate::time::Timing;
9 :
10 : /// Stores status of the running threads. Threads are registered in the runtime upon creation
11 : /// and deregistered upon termination.
12 : pub struct Runtime {
13 : // stores handles to all threads that are currently running
14 : threads: Vec<ThreadHandle>,
15 : // stores current time and pending wakeups
16 : clock: Arc<Timing>,
17 : // thread counter
18 : thread_counter: AtomicU32,
19 : // Thread step counter -- how many times all threads has been actually
20 : // stepped (note that all world/time/executor/thread have slightly different
21 : // meaning of steps). For observability.
22 : pub step_counter: u64,
23 : }
24 :
25 : impl Runtime {
26 : /// Init new runtime, no running threads.
27 528 : pub fn new(clock: Arc<Timing>) -> Self {
28 528 : Self {
29 528 : threads: Vec::new(),
30 528 : clock,
31 528 : thread_counter: AtomicU32::new(0),
32 528 : step_counter: 0,
33 528 : }
34 528 : }
35 :
36 : /// Spawn a new thread and register it in the runtime.
37 20250 : pub fn spawn<F>(&mut self, f: F) -> ExternalHandle
38 20250 : where
39 20250 : F: FnOnce() + Send + 'static,
40 20250 : {
41 20250 : let (tx, rx) = mpsc::channel();
42 20250 :
43 20250 : let clock = self.clock.clone();
44 20250 : let tid = self.thread_counter.fetch_add(1, Ordering::SeqCst);
45 20250 : debug!("spawning thread-{}", tid);
46 :
47 20250 : let join = std::thread::spawn(move || {
48 20250 : let _guard = tracing::info_span!("", tid).entered();
49 20250 :
50 20250 : let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
51 20250 : with_thread_context(|ctx| {
52 20250 : assert!(ctx.clock.set(clock).is_ok());
53 20250 : ctx.id.store(tid, Ordering::SeqCst);
54 20250 : tx.send(ctx.clone()).expect("failed to send thread context");
55 20250 : // suspend thread to put it to `threads` in sleeping state
56 20250 : ctx.yield_me(0);
57 20250 : });
58 20250 :
59 20250 : // start user-provided function
60 20250 : f();
61 20250 : }));
62 20250 : debug!("thread finished");
63 :
64 20192 : if let Err(e) = res {
65 20172 : with_thread_context(|ctx| {
66 20172 : if !ctx.allow_panic.load(std::sync::atomic::Ordering::SeqCst) {
67 0 : error!("thread panicked, terminating the process: {:?}", e);
68 0 : std::process::exit(1);
69 20172 : }
70 20172 :
71 20172 : debug!("thread panicked: {:?}", e);
72 20172 : let mut result = ctx.result.lock();
73 20172 : if result.0 == -1 {
74 19767 : *result = (256, format!("thread panicked: {:?}", e));
75 19767 : }
76 20172 : });
77 20172 : }
78 :
79 20192 : with_thread_context(|ctx| {
80 20192 : ctx.finish_me();
81 20192 : });
82 20250 : });
83 20250 :
84 20250 : let ctx = rx.recv().expect("failed to receive thread context");
85 20250 : let handle = ThreadHandle::new(ctx.clone(), join);
86 20250 :
87 20250 : self.threads.push(handle);
88 20250 :
89 20250 : ExternalHandle { ctx }
90 20250 : }
91 :
92 : /// Returns true if there are any unfinished activity, such as running thread or pending events.
93 : /// Otherwise returns false, which means all threads are blocked forever.
94 410741 : pub fn step(&mut self) -> bool {
95 410741 : trace!("runtime step");
96 :
97 : // have we run any thread?
98 410741 : let mut ran = false;
99 410741 :
100 2050150 : self.threads.retain(|thread: &ThreadHandle| {
101 2050150 : let res = thread.ctx.wakeup.compare_exchange(
102 2050150 : PENDING_WAKEUP,
103 2050150 : NO_WAKEUP,
104 2050150 : Ordering::SeqCst,
105 2050150 : Ordering::SeqCst,
106 2050150 : );
107 2050150 : if res.is_err() {
108 : // thread has no pending wakeups, leaving as is
109 1766710 : return true;
110 283440 : }
111 283440 : ran = true;
112 283440 :
113 283440 : trace!("entering thread-{}", thread.ctx.tid());
114 283440 : let status = thread.step();
115 283440 : self.step_counter += 1;
116 283440 : trace!(
117 0 : "out of thread-{} with status {:?}",
118 0 : thread.ctx.tid(),
119 : status
120 : );
121 :
122 283440 : if status == Status::Sleep {
123 263248 : true
124 : } else {
125 20192 : trace!("thread has finished");
126 : // removing the thread from the list
127 20192 : false
128 : }
129 2050150 : });
130 410741 :
131 410741 : if !ran {
132 217117 : trace!("no threads were run, stepping clock");
133 217117 : if let Some(ctx_to_wake) = self.clock.step() {
134 216569 : trace!("waking up thread-{}", ctx_to_wake.tid());
135 216569 : ctx_to_wake.inc_wake();
136 : } else {
137 548 : return false;
138 : }
139 193624 : }
140 :
141 410193 : true
142 410741 : }
143 :
144 : /// Kill all threads. This is done by setting a flag in each thread context and waking it up.
145 1008 : pub fn crash_all_threads(&mut self) {
146 2870 : for thread in self.threads.iter() {
147 2870 : thread.ctx.crash_stop();
148 2870 : }
149 :
150 : // all threads should be finished after a few steps
151 1512 : while !self.threads.is_empty() {
152 504 : self.step();
153 504 : }
154 1008 : }
155 : }
156 :
157 : impl Drop for Runtime {
158 503 : fn drop(&mut self) {
159 503 : debug!("dropping the runtime");
160 503 : self.crash_all_threads();
161 503 : }
162 : }
163 :
164 : #[derive(Clone)]
165 : pub struct ExternalHandle {
166 : ctx: Arc<ThreadContext>,
167 : }
168 :
169 : impl ExternalHandle {
170 : /// Returns true if thread has finished execution.
171 425817 : pub fn is_finished(&self) -> bool {
172 425817 : let status = self.ctx.mutex.lock();
173 425817 : *status == Status::Finished
174 425817 : }
175 :
176 : /// Returns exitcode and message, which is available after thread has finished execution.
177 393 : pub fn result(&self) -> (i32, String) {
178 393 : let result = self.ctx.result.lock();
179 393 : result.clone()
180 393 : }
181 :
182 : /// Returns thread id.
183 16 : pub fn id(&self) -> u32 {
184 16 : self.ctx.id.load(Ordering::SeqCst)
185 16 : }
186 :
187 : /// Sets a flag to crash thread on the next wakeup.
188 17259 : pub fn crash_stop(&self) {
189 17259 : self.ctx.crash_stop();
190 17259 : }
191 : }
192 :
193 : struct ThreadHandle {
194 : ctx: Arc<ThreadContext>,
195 : _join: JoinHandle<()>,
196 : }
197 :
198 : impl ThreadHandle {
199 : /// Create a new [`ThreadHandle`] and wait until thread will enter [`Status::Sleep`] state.
200 20250 : fn new(ctx: Arc<ThreadContext>, join: JoinHandle<()>) -> Self {
201 20250 : let mut status = ctx.mutex.lock();
202 : // wait until thread will go into the first yield
203 20302 : while *status != Status::Sleep {
204 52 : ctx.condvar.wait(&mut status);
205 52 : }
206 20250 : drop(status);
207 20250 :
208 20250 : Self { ctx, _join: join }
209 20250 : }
210 :
211 : /// Allows thread to execute one step of its execution.
212 : /// Returns [`Status`] of the thread after the step.
213 283440 : fn step(&self) -> Status {
214 283440 : let mut status = self.ctx.mutex.lock();
215 283440 : assert!(matches!(*status, Status::Sleep));
216 :
217 283440 : *status = Status::Running;
218 283440 : self.ctx.condvar.notify_all();
219 :
220 566880 : while *status == Status::Running {
221 283440 : self.ctx.condvar.wait(&mut status);
222 283440 : }
223 :
224 283440 : *status
225 283440 : }
226 : }
227 :
228 : #[derive(Clone, Copy, Debug, PartialEq, Eq)]
229 : enum Status {
230 : /// Thread is running.
231 : Running,
232 : /// Waiting for event to complete, will be resumed by the executor step, once wakeup flag is set.
233 : Sleep,
234 : /// Thread finished execution.
235 : Finished,
236 : }
237 :
238 : const NO_WAKEUP: u8 = 0;
239 : const PENDING_WAKEUP: u8 = 1;
240 :
241 : pub struct ThreadContext {
242 : id: AtomicU32,
243 : // used to block thread until it is woken up
244 : mutex: parking_lot::Mutex<Status>,
245 : condvar: parking_lot::Condvar,
246 : // used as a flag to indicate runtime that thread is ready to be woken up
247 : wakeup: AtomicU8,
248 : clock: OnceLock<Arc<Timing>>,
249 : // execution result, set by exit() call
250 : result: parking_lot::Mutex<(i32, String)>,
251 : // determines if process should be killed on receiving panic
252 : allow_panic: AtomicBool,
253 : // acts as a signal that thread should crash itself on the next wakeup
254 : crash_request: AtomicBool,
255 : }
256 :
257 : impl ThreadContext {
258 20778 : pub(crate) fn new() -> Self {
259 20778 : Self {
260 20778 : id: AtomicU32::new(0),
261 20778 : mutex: parking_lot::Mutex::new(Status::Running),
262 20778 : condvar: parking_lot::Condvar::new(),
263 20778 : wakeup: AtomicU8::new(NO_WAKEUP),
264 20778 : clock: OnceLock::new(),
265 20778 : result: parking_lot::Mutex::new((-1, String::new())),
266 20778 : allow_panic: AtomicBool::new(false),
267 20778 : crash_request: AtomicBool::new(false),
268 20778 : }
269 20778 : }
270 : }
271 :
272 : // Functions for executor to control thread execution.
273 : impl ThreadContext {
274 : /// Set atomic flag to indicate that thread is ready to be woken up.
275 679392 : fn inc_wake(&self) {
276 679392 : self.wakeup.store(PENDING_WAKEUP, Ordering::SeqCst);
277 679392 : }
278 :
279 : /// Internal function used for event queues.
280 177720 : pub(crate) fn schedule_wakeup(self: &Arc<Self>, after_ms: u64) {
281 177720 : self.clock
282 177720 : .get()
283 177720 : .unwrap()
284 177720 : .schedule_wakeup(after_ms, self.clone());
285 177720 : }
286 :
287 1 : fn tid(&self) -> u32 {
288 1 : self.id.load(Ordering::SeqCst)
289 1 : }
290 :
291 20129 : fn crash_stop(&self) {
292 20129 : let status = self.mutex.lock();
293 20129 : if *status == Status::Finished {
294 11 : debug!(
295 0 : "trying to crash thread-{}, which is already finished",
296 0 : self.tid()
297 : );
298 11 : return;
299 20118 : }
300 20118 : assert!(matches!(*status, Status::Sleep));
301 20118 : drop(status);
302 20118 :
303 20118 : self.allow_panic.store(true, Ordering::SeqCst);
304 20118 : self.crash_request.store(true, Ordering::SeqCst);
305 20118 : // set a wakeup
306 20118 : self.inc_wake();
307 : // it will panic on the next wakeup
308 20129 : }
309 : }
310 :
311 : // Internal functions.
312 : impl ThreadContext {
313 : /// Blocks thread until it's woken up by the executor. If `after_ms` is 0, is will be
314 : /// woken on the next step. If `after_ms` > 0, wakeup is scheduled after that time.
315 : /// Otherwise wakeup is not scheduled inside `yield_me`, and should be arranged before
316 : /// calling this function.
317 283498 : fn yield_me(self: &Arc<Self>, after_ms: i64) {
318 283498 : let mut status = self.mutex.lock();
319 283498 : assert!(matches!(*status, Status::Running));
320 :
321 283498 : match after_ms.cmp(&0) {
322 234454 : std::cmp::Ordering::Less => {
323 234454 : // block until something wakes us up
324 234454 : }
325 21739 : std::cmp::Ordering::Equal => {
326 21739 : // tell executor that we are ready to be woken up
327 21739 : self.inc_wake();
328 21739 : }
329 27305 : std::cmp::Ordering::Greater => {
330 27305 : // schedule wakeup
331 27305 : self.clock
332 27305 : .get()
333 27305 : .unwrap()
334 27305 : .schedule_wakeup(after_ms as u64, self.clone());
335 27305 : }
336 : }
337 :
338 283498 : *status = Status::Sleep;
339 283498 : self.condvar.notify_all();
340 :
341 : // wait until executor wakes us up
342 566996 : while *status != Status::Running {
343 283498 : self.condvar.wait(&mut status);
344 283498 : }
345 :
346 283498 : if self.crash_request.load(Ordering::SeqCst) {
347 19767 : panic!("crashed by request");
348 263731 : }
349 263731 : }
350 :
351 : /// Called only once, exactly before thread finishes execution.
352 20192 : fn finish_me(&self) {
353 20192 : let mut status = self.mutex.lock();
354 20192 : assert!(matches!(*status, Status::Running));
355 :
356 20192 : *status = Status::Finished;
357 20192 : {
358 20192 : let mut result = self.result.lock();
359 20192 : if result.0 == -1 {
360 20 : *result = (0, "finished normally".to_owned());
361 20172 : }
362 : }
363 20192 : self.condvar.notify_all();
364 20192 : }
365 : }
366 :
367 : /// Invokes the given closure with a reference to the current thread [`ThreadContext`].
368 : #[inline(always)]
369 1835405 : fn with_thread_context<T>(f: impl FnOnce(&Arc<ThreadContext>) -> T) -> T {
370 1835405 : thread_local!(static THREAD_DATA: Arc<ThreadContext> = Arc::new(ThreadContext::new()));
371 1835405 : THREAD_DATA.with(f)
372 1835405 : }
373 :
374 : /// Waker is used to wake up threads that are blocked on condition.
375 : /// It keeps track of contexts [`Arc<ThreadContext>`] and can increment the counter
376 : /// of several contexts to send a notification.
377 : pub struct Waker {
378 : // contexts that are waiting for a notification
379 : contexts: parking_lot::Mutex<smallvec::SmallVec<[Arc<ThreadContext>; 8]>>,
380 : }
381 :
382 : impl Default for Waker {
383 0 : fn default() -> Self {
384 0 : Self::new()
385 0 : }
386 : }
387 :
388 : impl Waker {
389 81723 : pub fn new() -> Self {
390 81723 : Self {
391 81723 : contexts: parking_lot::Mutex::new(smallvec::SmallVec::new()),
392 81723 : }
393 81723 : }
394 :
395 : /// Subscribe current thread to receive a wake notification later.
396 805146 : pub fn wake_me_later(&self) {
397 805146 : with_thread_context(|ctx| {
398 805146 : self.contexts.lock().push(ctx.clone());
399 805146 : });
400 805146 : }
401 :
402 : /// Wake up all threads that are waiting for a notification and clear the list.
403 124216 : pub fn wake_all(&self) {
404 124216 : let mut v = self.contexts.lock();
405 420966 : for ctx in v.iter() {
406 420966 : ctx.inc_wake();
407 420966 : }
408 124216 : v.clear();
409 124216 : }
410 : }
411 :
412 : /// See [`ThreadContext::yield_me`].
413 263248 : pub fn yield_me(after_ms: i64) {
414 263248 : with_thread_context(|ctx| ctx.yield_me(after_ms))
415 263248 : }
416 :
417 : /// Get current time.
418 705464 : pub fn now() -> u64 {
419 705464 : with_thread_context(|ctx| ctx.clock.get().unwrap().now())
420 705464 : }
421 :
422 405 : pub fn exit(code: i32, msg: String) {
423 405 : with_thread_context(|ctx| {
424 405 : ctx.allow_panic.store(true, Ordering::SeqCst);
425 405 : let mut result = ctx.result.lock();
426 405 : *result = (code, msg);
427 405 : panic!("exit");
428 405 : });
429 : }
430 :
431 528 : pub(crate) fn get_thread_ctx() -> Arc<ThreadContext> {
432 528 : with_thread_context(|ctx| ctx.clone())
433 528 : }
434 :
435 : /// Trait for polling channels until they have something.
436 : pub trait PollSome {
437 : /// Schedule wakeup for message arrival.
438 : fn wake_me(&self);
439 :
440 : /// Check if channel has a ready message.
441 : fn has_some(&self) -> bool;
442 : }
443 :
444 : /// Blocks current thread until one of the channels has a ready message. Returns
445 : /// index of the channel that has a message. If timeout is reached, returns None.
446 : ///
447 : /// Negative timeout means block forever. Zero timeout means check channels and return
448 : /// immediately. Positive timeout means block until timeout is reached.
449 105696 : pub fn epoll_chans(chans: &[Box<dyn PollSome>], timeout: i64) -> Option<usize> {
450 105696 : let deadline = if timeout < 0 {
451 77467 : 0
452 : } else {
453 28229 : now() + timeout as u64
454 : };
455 :
456 : loop {
457 1002009 : for chan in chans {
458 802342 : chan.wake_me()
459 : }
460 :
461 650067 : for (i, chan) in chans.iter().enumerate() {
462 650067 : if chan.has_some() {
463 83963 : return Some(i);
464 566104 : }
465 : }
466 :
467 96839 : if timeout < 0 {
468 66666 : // block until wakeup
469 66666 : yield_me(-1);
470 66666 : } else {
471 30173 : let current_time = now();
472 30173 : if current_time >= deadline {
473 2868 : return None;
474 27305 : }
475 27305 :
476 27305 : yield_me((deadline - current_time) as i64);
477 : }
478 : }
479 86831 : }
|