hrx/execution/
completion.rs1use crate::{Error, Result};
2use std::{
3 future::Future,
4 pin::Pin,
5 sync::{
6 Arc, Condvar, Mutex,
7 atomic::{AtomicBool, AtomicUsize, Ordering},
8 },
9 task::{Context, Poll, Waker},
10 time::{Duration, Instant},
11};
12
13pub(super) struct State {
14 submitted: Option<Instant>,
15 started: Option<Instant>,
16 finished: Option<Instant>,
17 pub done: bool,
18 pub failure: Option<Arc<Error>>,
19 pub cancelled: bool,
20 wakers: [Option<Waker>; 4],
21 overflow_wakers: Vec<Waker>,
22}
23pub(super) struct Signal {
24 pub state: Mutex<State>,
25 changed: Condvar,
26 pub cancel: AtomicBool,
27 pub observers: AtomicUsize,
29}
30impl Signal {
31 pub fn new() -> Arc<Self> {
32 Arc::new(Self {
33 state: Mutex::new(State {
34 submitted: None,
35 started: None,
36 finished: None,
37 done: true,
38 failure: None,
39 cancelled: false,
40 wakers: std::array::from_fn(|_| None),
41 overflow_wakers: Vec::new(),
42 }),
43 changed: Condvar::new(),
44 cancel: AtomicBool::new(false),
45 observers: AtomicUsize::new(0),
46 })
47 }
48 pub fn reset(&self) {
49 let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
50 state.done = false;
51 state.submitted = Some(Instant::now());
52 state.started = None;
53 state.finished = None;
54 state.failure = None;
55 state.cancelled = false;
56 state.wakers.fill(None);
57 state.overflow_wakers.clear();
58 self.cancel.store(false, Ordering::Release);
59 }
60 #[cfg(test)]
61 pub fn finish(&self, failure: Option<Arc<Error>>) {
62 self.publish(failure)();
63 }
64 pub fn publish(&self, failure: Option<Arc<Error>>) -> impl FnOnce() + '_ {
65 let (wakers, overflow) = {
66 let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
67 state.failure = failure;
68 state.cancelled = self.cancel.load(Ordering::Acquire);
69 state.done = true;
70 state.finished = Some(Instant::now());
71 (
72 std::mem::replace(&mut state.wakers, std::array::from_fn(|_| None)),
73 std::mem::take(&mut state.overflow_wakers),
74 )
75 };
76 move || {
77 self.changed.notify_all();
78 for waker in wakers.into_iter().flatten().chain(overflow) {
79 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| waker.wake()));
81 }
82 }
83 }
84}
85impl Signal {
86 pub fn started(&self, now: Instant) {
87 self.state
88 .lock()
89 .unwrap_or_else(|e| e.into_inner())
90 .started
91 .get_or_insert(now);
92 }
93}
94
95#[derive(Clone, Copy, Debug, serde::Serialize)]
98pub struct CompletionProfile {
99 pub elapsed_us: f64,
101 pub queue_us: Option<f64>,
103}
104
105fn outcome(state: &State) -> Result<()> {
106 if let Some(message) = &state.failure {
107 return Err(Error::Execution {
108 source: message.clone(),
109 });
110 }
111 if state.cancelled {
112 return Err(Error::Cancelled);
113 }
114 Ok(())
115}
116pub struct Completion {
120 pub(super) signal: Arc<Signal>,
121 pub(super) core: std::sync::Weak<super::scheduler::Core>,
122}
123impl Clone for Completion {
124 fn clone(&self) -> Self {
125 Self::new(self.signal.clone(), self.core.clone())
126 }
127}
128impl Drop for Completion {
129 fn drop(&mut self) {
130 let previous = self.signal.observers.fetch_sub(1, Ordering::AcqRel);
131 debug_assert!(previous > 0);
132 }
133}
134impl Completion {
135 pub(super) fn new(signal: Arc<Signal>, core: std::sync::Weak<super::scheduler::Core>) -> Self {
136 signal.observers.fetch_add(1, Ordering::AcqRel);
137 Self { signal, core }
138 }
139 pub fn finished_at(&self) -> Option<Instant> {
143 self.signal
144 .state
145 .lock()
146 .unwrap_or_else(|e| e.into_inner())
147 .finished
148 }
149 pub fn profile(&self) -> Option<CompletionProfile> {
151 let state = self.signal.state.lock().unwrap_or_else(|e| e.into_inner());
152 let submitted = state.submitted?;
153 Some(CompletionProfile {
154 elapsed_us: state.finished?.duration_since(submitted).as_secs_f64() * 1e6,
155 queue_us: state
156 .started
157 .map(|start| start.duration_since(submitted).as_secs_f64() * 1e6),
158 })
159 }
160 pub(crate) fn validate_runtime(&self, runtime: &super::Runtime) -> Result<()> {
161 if let Some(owner) = self.core.upgrade() {
162 if !Arc::ptr_eq(&runtime.inner.core, &owner) {
163 return Err(Error::Message(
164 "completion belongs to another runtime".into(),
165 ));
166 }
167 } else if !self.is_complete() {
168 return Err(Error::Message("orphaned incomplete producer".into()));
169 }
170 Ok(())
171 }
172 pub fn result(&self) -> Option<Result<()>> {
174 let state = self.signal.state.lock().unwrap_or_else(|e| e.into_inner());
175 state.done.then(|| outcome(&state))
176 }
177
178 pub(crate) fn ready() -> Self {
179 Self::new(Signal::new(), std::sync::Weak::new())
180 }
181
182 pub fn is_complete(&self) -> bool {
184 self.signal
185 .state
186 .lock()
187 .unwrap_or_else(|e| e.into_inner())
188 .done
189 }
190 pub fn wait(&self) -> Result<()> {
194 if let Some(core) = self.core.upgrade() {
195 super::scheduler::help(&core, &self.signal);
196 }
197 let mut state = self.signal.state.lock().unwrap_or_else(|e| e.into_inner());
198 while !state.done {
199 state = self
200 .signal
201 .changed
202 .wait(state)
203 .unwrap_or_else(|e| e.into_inner());
204 }
205 outcome(&state)
206 }
207 pub fn wait_timeout(&self, timeout: Duration) -> Result<bool> {
209 let start = Instant::now();
210 let mut state = self.signal.state.lock().unwrap_or_else(|e| e.into_inner());
211 while !state.done {
212 let Some(left) = timeout.checked_sub(start.elapsed()) else {
213 return Ok(false);
214 };
215 let (next, _) = self
216 .signal
217 .changed
218 .wait_timeout(state, left)
219 .unwrap_or_else(|e| e.into_inner());
220 state = next;
221 }
222 outcome(&state)?;
223 Ok(true)
224 }
225 pub fn cancel(&self) {
228 self.signal.cancel.store(true, Ordering::Release);
229 if let Some(core) = self.core.upgrade() {
230 core.changed.notify_all();
231 }
232 }
233}
234impl Future for Completion {
235 type Output = Result<()>;
236 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
237 let mut state = self.signal.state.lock().unwrap_or_else(|e| e.into_inner());
238 if state.done {
239 return Poll::Ready(outcome(&state));
240 }
241 if !state
242 .wakers
243 .iter()
244 .flatten()
245 .chain(&state.overflow_wakers)
246 .any(|w| w.will_wake(context.waker()))
247 {
248 if let Some(slot) = state.wakers.iter_mut().find(|slot| slot.is_none()) {
249 *slot = Some(context.waker().clone());
250 } else {
251 state.overflow_wakers.push(context.waker().clone());
252 }
253 }
254 Poll::Pending
255 }
256}
257#[cfg(test)]
258mod tests {
259 use super::*;
260 #[test]
261 fn timeout_does_not_cancel_and_detach_retains_state() {
262 let signal = Signal::new();
263 signal.reset();
264 let completion = Completion::new(signal.clone(), std::sync::Weak::new());
265 assert!(!completion.wait_timeout(Duration::ZERO).unwrap());
266 assert!(!signal.cancel.load(Ordering::Acquire));
267 drop(completion);
268 signal.finish(None);
269 Completion::new(signal, std::sync::Weak::new())
270 .wait()
271 .unwrap();
272 }
273 #[test]
274 fn future_wakeup_and_error() {
275 use std::task::Wake;
276 struct Counter(std::sync::atomic::AtomicUsize);
277 impl Wake for Counter {
278 fn wake(self: Arc<Self>) {
279 self.0.fetch_add(1, Ordering::Relaxed);
280 }
281 }
282 let counter = Arc::new(Counter(std::sync::atomic::AtomicUsize::new(0)));
283 let waker = Waker::from(counter.clone());
284 let mut context = Context::from_waker(&waker);
285 let signal = Signal::new();
286 signal.reset();
287 let mut completion = Completion::new(signal.clone(), std::sync::Weak::new());
288 assert!(Pin::new(&mut completion).poll(&mut context).is_pending());
289 signal.finish(Some(Arc::new(Error::DeviceLost(
290 "injected native failure".into(),
291 ))));
292 assert_eq!(counter.0.load(Ordering::Relaxed), 1);
293 assert!(matches!(
294 Pin::new(&mut completion).poll(&mut context),
295 Poll::Ready(Err(Error::Execution { .. }))
296 ));
297 }
298}