klukai_types/tripwire/
preempt.rs1use pin_project_lite::pin_project;
2use std::{
3 future::Future,
4 pin::Pin,
5 task::{Context, Poll},
6 time::Duration,
7};
8use tokio::time::Sleep;
9
10#[derive(Debug)]
12pub enum Outcome<P, C> {
13 Preempted(P),
15 Completed(C),
17}
18
19impl<P, C> Outcome<P, C> {
20 pub fn is_preempted(&self) -> bool {
22 matches!(self, Outcome::Preempted(_))
23 }
24
25 pub fn is_completed(&self) -> bool {
27 matches!(self, Outcome::Completed(_))
28 }
29}
30
31pin_project! {
32 pub struct PreemptibleFuture<P, C> {
34 #[pin]
35 preempt: P,
36
37 #[pin]
38 complete: C,
39 }
40}
41
42impl<P, C> Future for PreemptibleFuture<P, C>
43where
44 P: Future,
45 C: Future,
46{
47 type Output = Outcome<P::Output, C::Output>;
48
49 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
50 let this = self.project();
51
52 if let Poll::Ready(v) = this.preempt.poll(cx) {
53 return Poll::Ready(Outcome::Preempted(v));
54 }
55
56 this.complete.poll(cx).map(Outcome::Completed)
57 }
58}
59
60impl<P, C, T, E> PreemptibleFuture<P, C>
61where
62 P: Future,
63 C: Future<Output = Result<T, E>>,
64{
65 pub async fn complete_or_else(self, f: impl FnOnce(P::Output) -> E) -> Result<T, E> {
67 match self.await {
68 Outcome::Preempted(v) => Err(f(v)),
69 Outcome::Completed(res) => res,
70 }
71 }
72
73 pub async fn complete_or(self, err: E) -> Result<T, E> {
75 match self.await {
76 Outcome::Preempted(_) => Err(err),
77 Outcome::Completed(res) => res,
78 }
79 }
80}
81
82pub trait PreemptibleFutureExt<P>: Sized {
84 fn preemptible(self, done: P) -> PreemptibleFuture<P, Self>;
86}
87
88pub trait TimeoutFutureExt: Sized {
91 fn with_timeout(self, delay: Duration) -> PreemptibleFuture<Sleep, Self>;
93}
94
95impl<P, C> PreemptibleFutureExt<P> for C
96where
97 P: Future,
98 C: Future,
99{
100 fn preemptible(self, preempt: P) -> PreemptibleFuture<P, Self> {
101 PreemptibleFuture {
102 preempt,
103 complete: self,
104 }
105 }
106}
107
108impl<C> TimeoutFutureExt for C
109where
110 C: Future,
111{
112 fn with_timeout(self, delay: Duration) -> PreemptibleFuture<Sleep, Self> {
113 self.preemptible(tokio::time::sleep(delay))
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use std::{
120 sync::{
121 Arc,
122 atomic::{AtomicBool, Ordering},
123 },
124 time::Duration,
125 };
126
127 use crate::tripwire::{Outcome, PreemptibleFutureExt, Tripwire};
128 use assert2::let_assert;
129
130 #[tokio::test]
131 async fn preemptible_tripwire() {
132 let (tripwire, tripwire_worker, tripwire_tx) = Tripwire::new_simple();
133
134 {
135 let signal: Arc<AtomicBool> = Default::default();
136 let f = {
137 let signal = signal.clone();
138 async move {
139 tokio::time::sleep(Duration::from_millis(300)).await;
140 signal.store(true, Ordering::SeqCst);
141 }
142 .preemptible(tripwire.clone())
143 };
144 let_assert!(Outcome::Completed(_) = f.await);
145 assert!(signal.load(Ordering::SeqCst))
146 }
147
148 {
149 let signal: Arc<AtomicBool> = Default::default();
150 let f = {
151 let signal = signal.clone();
152 async move {
153 tokio::time::sleep(Duration::from_millis(300)).await;
154 signal.store(true, Ordering::SeqCst);
155 }
156 .preemptible(tripwire.clone())
157 };
158 tripwire_tx.send(()).await.ok();
159 tripwire_worker.await;
160 let_assert!(Outcome::Preempted(()) = f.await);
161 assert!(!signal.load(Ordering::SeqCst))
162 }
163 }
164}