libasync/
after_deadline.rs1use std::pin::Pin;
6use std::ptr::NonNull;
7use std::sync::Arc;
8use std::sync::atomic::{AtomicI32, Ordering};
9use std::task::{Context, Poll};
10
11use libasync_dispatcher::{AsyncDispatcher, DetectDispatcher, GetAsyncDispatcher};
12use libasync_sys::{async_cancel_task, async_dispatcher, async_post_task, async_task};
13
14use futures::task::AtomicWaker;
15use zx::Status;
16use zx::sys::{ZX_ERR_CANCELED, ZX_OK};
17
18use crate::callback_state::CallbackSharedState;
19
20type SharedState = CallbackSharedState<async_task, AfterDeadlineState>;
21
22pub trait DispatcherTimerExt {
24 fn after_deadline(&self, deadline: zx::MonotonicInstant) -> AfterDeadline;
39
40 fn try_after_deadline(&self, deadline: zx::MonotonicInstant) -> Option<AfterDeadline>;
47}
48
49impl<T> DispatcherTimerExt for T
50where
51 T: GetAsyncDispatcher,
52{
53 fn after_deadline(&self, deadline: zx::MonotonicInstant) -> AfterDeadline {
54 self.try_after_deadline(deadline).expect("No current dispatcher")
55 }
56
57 fn try_after_deadline(&self, deadline: zx::MonotonicInstant) -> Option<AfterDeadline> {
58 let dispatcher = self.try_get_async_dispatcher()?;
59 Some(AfterDeadline::new_on(dispatcher, deadline))
60 }
61}
62
63struct AfterDeadlineState {
64 async_dispatcher: NonNull<async_dispatcher>,
65 waker: AtomicWaker,
66 status: AtomicI32,
69}
70
71unsafe impl Send for AfterDeadlineState {}
73unsafe impl Sync for AfterDeadlineState {}
74
75impl AfterDeadlineState {
76 extern "C" fn call(_dispatcher: *mut async_dispatcher, task: *mut async_task, status: i32) {
77 debug_assert!(
78 status == ZX_OK || status == ZX_ERR_CANCELED,
79 "task callback called with status other than ok or canceled"
80 );
81 let state = unsafe { SharedState::from_raw_ptr(task) };
84 state.status.store(status, Ordering::Relaxed);
85 state.waker.wake();
86 }
87}
88
89pub struct AfterDeadline {
93 dispatcher: DetectDispatcher,
94 state: Option<Arc<SharedState>>,
95 deadline: zx::MonotonicInstant,
96}
97
98impl AfterDeadline {
99 pub fn new(deadline: zx::MonotonicInstant) -> Self {
104 let state = None;
105 let dispatcher = DetectDispatcher::default();
106 Self { dispatcher, state, deadline }
107 }
108
109 fn new_on(dispatcher: AsyncDispatcher, deadline: zx::MonotonicInstant) -> Self {
110 let state = None;
111 let dispatcher = DetectDispatcher::new(dispatcher);
112 Self { dispatcher, state, deadline }
113 }
114}
115
116impl Future for AfterDeadline {
117 type Output = Result<(), Status>;
118
119 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
120 let dispatcher = self.dispatcher.get_or_detect()?;
122
123 if let Some(state) = &self.state {
125 let status = state.status.load(Ordering::Relaxed);
126 if status != Status::SHOULD_WAIT.into_raw() {
127 return Poll::Ready(Status::ok(status));
128 } else {
129 state.waker.register(cx.waker());
130 return Poll::Pending;
131 }
132 }
133
134 let deadline = self.deadline;
135 let now = dispatcher.now();
136 if deadline < zx::MonotonicInstant::from_nanos(now) {
137 return Poll::Ready(Ok(()));
138 }
139
140 let async_dispatcher = dispatcher.as_ptr();
142
143 let task = async_task {
144 handler: Some(AfterDeadlineState::call),
145 deadline: deadline.into_nanos(),
146 ..Default::default()
147 };
148 let state = AfterDeadlineState {
149 async_dispatcher,
150 waker: AtomicWaker::new(),
151 status: AtomicI32::new(Status::SHOULD_WAIT.into_raw()),
152 };
153 let state = SharedState::new(task, state);
154 state.waker.register(cx.waker());
155
156 let state_ptr = SharedState::make_raw_ptr(state.clone());
157
158 let res = Status::ok(unsafe { async_post_task(async_dispatcher.as_ptr(), state_ptr) });
162 match res {
163 Ok(_) => {
164 self.state = Some(state);
165 Poll::Pending
166 }
167 Err(err) => {
168 unsafe { SharedState::release_raw_ptr(state_ptr) };
171 Poll::Ready(Err(err))
172 }
173 }
174 }
175}
176
177impl Drop for AfterDeadline {
178 fn drop(&mut self) {
179 let Some(state) = self.state.take() else {
180 return;
182 };
183 let Some(dispatcher) = self.dispatcher.get() else {
184 return;
187 };
188 if state.status.load(Ordering::Relaxed) != Status::SHOULD_WAIT.into_raw() {
189 return;
191 }
192 let async_dispatcher = dispatcher.as_ptr();
193 if async_dispatcher != state.async_dispatcher {
194 panic!(
195 "Dropping a pending `AfterDeadline` future from a different dispatcher than the one it was awaited on."
196 );
197 }
198 let state_ptr = SharedState::as_raw_ptr(&state);
199 let status = unsafe { async_cancel_task(async_dispatcher.as_ptr(), state_ptr) };
203 if Status::from_raw(status) == Status::OK {
204 unsafe { SharedState::release_raw_ptr(state_ptr) };
207 }
208 }
209}
210
211#[cfg(test)]
214mod tests {
215 use std::sync::mpsc;
216 use std::thread::sleep;
217 use std::time::Duration;
218
219 use super::*;
220
221 use futures::{FutureExt, poll};
222 use std::task::Waker;
223
224 use fdf_env::test::spawn_in_driver;
225 use libasync_dispatcher::CurrentDispatcher;
226
227 fn now() -> zx::MonotonicInstant {
228 zx::MonotonicInstant::from_nanos(CurrentDispatcher.get_async_dispatcher().now())
229 }
230
231 #[test]
232 fn after_the_past() {
233 spawn_in_driver("testing task", async {
234 let fut = CurrentDispatcher.after_deadline(zx::MonotonicInstant::INFINITE_PAST);
235 assert_eq!(poll!(fut), Poll::Ready(Ok(())));
236 });
237 }
238
239 #[test]
240 fn after_now() {
241 spawn_in_driver("testing task", async {
242 let fut = CurrentDispatcher.after_deadline(now());
243 assert_eq!(poll!(fut), Poll::Ready(Ok(())));
244 });
245 }
246
247 #[test]
248 fn after_future() {
249 spawn_in_driver("testing task", async {
250 let deadline = now() + zx::MonotonicDuration::from_seconds(3);
251 let mut fut = CurrentDispatcher.after_deadline(deadline);
252 assert_eq!(poll!(&mut fut), Poll::Pending);
253 assert!(fut.await.is_ok());
254 assert!(now() >= deadline);
255 });
256 }
257
258 #[test]
259 fn drop_after_poll() {
260 spawn_in_driver("testing task", async {
261 let deadline = now() + zx::MonotonicDuration::from_minutes(3);
262 let mut fut = CurrentDispatcher.after_deadline(deadline);
263 assert_eq!(poll!(&mut fut), Poll::Pending);
264 });
265 }
266
267 #[test]
268 fn dispatcher_shutdown_cancel() {
269 let (fut_tx, fut_rx) = mpsc::channel();
270 spawn_in_driver("testing task", async move {
271 let deadline = now() + zx::MonotonicDuration::from_minutes(3);
272 let mut fut = CurrentDispatcher.after_deadline(deadline);
273 assert_eq!(poll!(&mut fut), Poll::Pending);
274 fut_tx.send(fut).unwrap();
275 });
276 let mut fut = fut_rx.recv().unwrap();
277 loop {
278 let Poll::Ready(res) = fut.poll_unpin(&mut Context::from_waker(Waker::noop())) else {
279 sleep(Duration::from_millis(10));
280 continue;
281 };
282 assert_eq!(res, Err(Status::CANCELED));
283 break;
284 }
285 }
286}