vj_sim/sched/thread_hooks.rs
1//! The embedder's view of thread life cycle (IEEE 1800-2023 37.44, 38.36.1:
2//! `cbStartOfThread`, `cbEnterThread`, `cbEndOfThread`).
3//!
4//! A *thread* is a process the scheduler runs: a static procedure
5//! (`initial`/`always*`/`final`) or one branch of a `fork`. The scheduler
6//! reports three moments of a thread's life to one installed hook:
7//!
8//! * [`ThreadEvent::Start`]: the thread is created. A static process is
9//! created when it first runs; a fork branch when its `fork` executes
10//! (before the branch runs, and before the parent continues).
11//! * [`ThreadEvent::Enter`]: the thread resumes after having been suspended
12//! (a delay elapsed, an event fired, a join was satisfied, ...). Its first
13//! run is its start, not a resume.
14//! * [`ThreadEvent::End`]: the thread is deleted, because it finished or was
15//! killed (`disable`, `disable fork`, `process::kill`).
16//!
17//! The hook is per OS thread, like the rest of a run's ambient state, and
18//! absent unless an embedder installs it: with none installed every point is
19//! one thread-local read.
20
21use std::cell::Cell;
22
23use super::{ProcessId, SchedulerOps, SpawnSpec};
24
25/// One moment of a thread's life.
26#[derive(Clone, Debug, PartialEq, Eq)]
27pub enum ThreadEvent {
28 /// `pid` was created; `parent` is the process whose `fork` created it
29 /// and `spawn` the branch it runs (a static process has neither).
30 Start {
31 pid: ProcessId,
32 parent: Option<ProcessId>,
33 spawn: Option<SpawnSpec>,
34 },
35 /// `pid` is about to run again after a suspension.
36 Enter { pid: ProcessId },
37 /// `pid` is gone; `killed` is false when it ran to completion.
38 End { pid: ProcessId, killed: bool },
39}
40
41/// The installed hook; it gets the engine's ops, which are also installed as
42/// the current ops for the call, so a callback it runs can write.
43pub type ThreadHook = fn(&dyn SchedulerOps, ThreadEvent);
44
45thread_local! {
46 static HOOK: Cell<Option<ThreadHook>> = const { Cell::new(None) };
47}
48
49/// Installs (or, with `None`, removes) this thread's hook.
50pub fn set_thread_hook(hook: Option<ThreadHook>) {
51 HOOK.with(|h| h.set(hook));
52}
53
54/// Whether a hook is installed: the guard of every scheduler call site.
55#[inline]
56pub fn thread_hook_installed() -> bool {
57 HOOK.with(|h| h.get().is_some())
58}
59
60/// Reports `event` to the installed hook, if any.
61pub fn fire_thread_event(ops: &dyn SchedulerOps, event: ThreadEvent) {
62 if let Some(hook) = HOOK.with(Cell::get) {
63 let pid = match event {
64 ThreadEvent::Start { pid, .. }
65 | ThreadEvent::Enter { pid }
66 | ThreadEvent::End { pid, .. } => pid,
67 };
68 // The thread the event is about is the current one for the hook's
69 // duration (what `vpi_handle(vpiThread, NULL)` answers).
70 super::with_scheduler_ops(ops, || {
71 super::with_current_process(pid, || hook(ops, event));
72 });
73 }
74}