1 use std::sync::atomic::{AtomicBool, Ordering};
2 use std::sync::Arc;
3 use std::time::Duration;
4 
5 /// A `SyncWaiter` is a handle to coordinate or execute some testing with respect to its
6 /// corresponding `Syncpoint`.
7 ///
8 /// A `SyncWaiter` corresponds to one `Syncpoint` and is created by its `wait_at`. A `SyncWaiter`
9 /// can only be waited at once, which is why both `wait_and_then` and `wait` consume the waiter.
10 pub struct SyncWaiter {
11     arrived: Arc<AtomicBool>,
12     proceed: Arc<AtomicBool>,
13 }
14 
15 impl SyncWaiter {
16     /// Wait for the corresponding `Syncpoint` to be reached, then continue.
wait(self)17     pub fn wait(self) {
18         self.wait_and_then(|| {})
19     }
20 
21     /// Wait for the corresponding `Syncpoint` to be reached, run the provided function, then
22     /// continue. This is useful for causing race conditions where a `Syncpoint` guarantees the
23     /// program under test has stopped at a location of interest, so the function provided to
24     /// `wait_and_then` is free to "race" with complete determinism.
wait_and_then<U, F: FnOnce() -> U>(self, f: F) -> U25     pub fn wait_and_then<U, F: FnOnce() -> U>(self, f: F) -> U {
26         let resumption = self.pause();
27 
28         let res = f();
29 
30         resumption.resume();
31 
32         res
33     }
34 
35     /// Wait for the corresponding `Syncpoint` to be reached, then return without permitting it to
36     /// continue. *If you do not resume from this `SyncWaiter` at some point you will likely
37     /// deadlock your test!*
38     #[must_use]
pause(self) -> Self39     pub fn pause(self) -> Self {
40         while !self.arrived.load(Ordering::SeqCst) {
41             std::thread::sleep(Duration::from_millis(10));
42         }
43 
44         self
45     }
46 
47     /// Resume this `SyncWaiter`, consuming it as can have no further effect. A `SyncWaiter` may be
48     /// resumed before it is reached, in which case this behaves similarly to having never called
49     /// `wait_at()` on the corresponding `Syncpoint`.
resume(self)50     pub fn resume(self) {
51         self.proceed.store(true, Ordering::SeqCst);
52     }
53 }
54 
55 /// A `Syncpoint` is a tool to coordinate testing at specific locations in Lucet.
56 ///
57 /// When `lock_testpoints` are compiled in, lucet-runtime will `check` unconditionally, where by
58 /// default this is functionally a no-op. For `Syncpoint`s a test has indicated interest in, with
59 /// `wait_at`, `check` becomes blocking until the test allows continuation through the
60 /// corresponding `SyncWaiter` that `wait_at` constructed. This allows tests to be written that
61 /// check race conditions in a deterministic manner: the runtime can execute "enter a guest", be
62 /// blocked at a Syncpoint just before guest entry, and a test that termination is correct in this
63 /// otherwise-unlikely circumstance can be performed.
64 pub struct Syncpoint {
65     arrived: Arc<AtomicBool>,
66     proceed: Arc<AtomicBool>,
67 }
68 
69 impl Syncpoint {
new() -> Self70     pub fn new() -> Self {
71         Self {
72             arrived: Arc::new(AtomicBool::new(false)),
73             proceed: Arc::new(AtomicBool::new(true)),
74         }
75     }
76 
wait_at(&self) -> SyncWaiter77     pub fn wait_at(&self) -> SyncWaiter {
78         let arrived = Arc::clone(&self.arrived);
79         let proceed = Arc::clone(&self.proceed);
80 
81         proceed.store(false, Ordering::SeqCst);
82 
83         SyncWaiter { arrived, proceed }
84     }
85 
check(&self)86     pub fn check(&self) {
87         self.arrived.store(true, Ordering::SeqCst);
88 
89         while !self.proceed.load(Ordering::SeqCst) {
90             std::thread::sleep(Duration::from_millis(10));
91         }
92     }
93 }
94 
95 pub struct LockTestpoints {
96     pub instance_after_clearing_current_instance: Syncpoint,
97     pub instance_entering_guest_after_domain_change: Syncpoint,
98     pub instance_entering_guest_before_domain_change: Syncpoint,
99     pub instance_entering_hostcall_after_domain_change: Syncpoint,
100     pub instance_entering_hostcall_before_domain_change: Syncpoint,
101     pub instance_exiting_guest_after_domain_change: Syncpoint,
102     pub instance_exiting_guest_before_acquiring_terminable: Syncpoint,
103     pub instance_exiting_guest_without_terminable: Syncpoint,
104     pub instance_exiting_hostcall_after_domain_change: Syncpoint,
105     pub instance_exiting_hostcall_before_domain_change: Syncpoint,
106     pub kill_switch_after_acquiring_domain_lock: Syncpoint,
107     pub kill_switch_after_acquiring_termination: Syncpoint,
108     pub kill_switch_after_forbidden_termination: Syncpoint,
109     pub kill_switch_after_guest_alarm: Syncpoint,
110     pub kill_switch_after_releasing_domain: Syncpoint,
111     pub kill_switch_before_disabling_termination: Syncpoint,
112     pub kill_switch_before_guest_alarm: Syncpoint,
113     pub kill_switch_before_guest_termination: Syncpoint,
114     pub kill_switch_before_hostcall_termination: Syncpoint,
115     pub kill_switch_before_releasing_domain: Syncpoint,
116     pub kill_switch_before_terminated_termination: Syncpoint,
117     pub signal_handler_after_disabling_termination: Syncpoint,
118     pub signal_handler_after_unable_to_disable_termination: Syncpoint,
119     pub signal_handler_before_checking_alarm: Syncpoint,
120     pub signal_handler_before_disabling_termination: Syncpoint,
121     pub signal_handler_before_returning: Syncpoint,
122 }
123 
124 impl LockTestpoints {
new() -> Self125     pub fn new() -> Self {
126         LockTestpoints {
127             instance_after_clearing_current_instance: Syncpoint::new(),
128             instance_entering_guest_after_domain_change: Syncpoint::new(),
129             instance_entering_guest_before_domain_change: Syncpoint::new(),
130             instance_entering_hostcall_after_domain_change: Syncpoint::new(),
131             instance_entering_hostcall_before_domain_change: Syncpoint::new(),
132             instance_exiting_guest_after_domain_change: Syncpoint::new(),
133             instance_exiting_guest_before_acquiring_terminable: Syncpoint::new(),
134             instance_exiting_guest_without_terminable: Syncpoint::new(),
135             instance_exiting_hostcall_after_domain_change: Syncpoint::new(),
136             instance_exiting_hostcall_before_domain_change: Syncpoint::new(),
137             kill_switch_after_acquiring_domain_lock: Syncpoint::new(),
138             kill_switch_after_acquiring_termination: Syncpoint::new(),
139             kill_switch_after_forbidden_termination: Syncpoint::new(),
140             kill_switch_after_guest_alarm: Syncpoint::new(),
141             kill_switch_after_releasing_domain: Syncpoint::new(),
142             kill_switch_before_disabling_termination: Syncpoint::new(),
143             kill_switch_before_guest_alarm: Syncpoint::new(),
144             kill_switch_before_guest_termination: Syncpoint::new(),
145             kill_switch_before_hostcall_termination: Syncpoint::new(),
146             kill_switch_before_releasing_domain: Syncpoint::new(),
147             kill_switch_before_terminated_termination: Syncpoint::new(),
148             signal_handler_after_disabling_termination: Syncpoint::new(),
149             signal_handler_after_unable_to_disable_termination: Syncpoint::new(),
150             signal_handler_before_checking_alarm: Syncpoint::new(),
151             signal_handler_before_disabling_termination: Syncpoint::new(),
152             signal_handler_before_returning: Syncpoint::new(),
153         }
154     }
155 }
156