async_timer/oneshot/
apple.rs

1//! Dispatch Source based Timer
2
3use core::{ptr, task, time};
4use core::pin::Pin;
5use core::future::Future;
6
7use super::state::TimerState;
8use crate::alloc::boxed::Box;
9
10use libc::{c_long, c_ulong, c_void, uintptr_t};
11
12#[allow(non_camel_case_types)]
13mod ffi {
14    use super::*;
15
16    pub type dispatch_object_t = *const c_void;
17    pub type dispatch_queue_t = *const c_void;
18    pub type dispatch_source_t = *const c_void;
19    pub type dispatch_source_type_t = *const c_void;
20    pub type dispatch_time_t = u64;
21
22    pub const DISPATCH_TIME_FOREVER: dispatch_time_t = !0;
23    //pub const DISPATCH_WALLTIME_NOW: dispatch_time_t = !1;
24    pub const QOS_CLASS_DEFAULT: c_long = 0x15;
25
26    extern "C" {
27        pub static _dispatch_source_type_timer: c_long;
28
29        pub fn dispatch_get_global_queue(identifier: c_long, flags: c_ulong) -> dispatch_queue_t;
30        pub fn dispatch_source_create(type_: dispatch_source_type_t, handle: uintptr_t, mask: c_ulong, queue: dispatch_queue_t) -> dispatch_source_t;
31        pub fn dispatch_source_set_timer(source: dispatch_source_t, start: dispatch_time_t, interval: u64, leeway: u64);
32        pub fn dispatch_source_set_event_handler_f(source: dispatch_source_t, handler: unsafe extern "C" fn(*mut c_void));
33        pub fn dispatch_set_context(object: dispatch_object_t, context: *mut c_void);
34        pub fn dispatch_resume(object: dispatch_object_t);
35        pub fn dispatch_suspend(object: dispatch_object_t);
36        pub fn dispatch_release(object: dispatch_object_t);
37        pub fn dispatch_source_cancel(object: dispatch_object_t);
38        pub fn dispatch_walltime(when: *const c_void, delta: i64) -> dispatch_time_t;
39    }
40}
41
42//TODO: Investigate why sometimes it is called multiple times
43unsafe extern "C" fn timer_handler(context: *mut c_void) {
44    let state = context as *mut TimerState;
45
46    (*state).wake();
47}
48
49struct TimerHandle {
50    inner: ffi::dispatch_source_t,
51    //Suspension count. Incremented suspend, and decremented on each resume
52    s_count: u8,
53}
54
55impl Drop for TimerHandle {
56    fn drop(&mut self) {
57        unsafe {
58            ffi::dispatch_source_cancel(self.inner);
59
60            //It is error to release while source is suspended
61            //So we decrement it
62            self.resume();
63
64            ffi::dispatch_release(self.inner);
65        }
66    }
67}
68
69impl TimerHandle {
70    fn new(state: *mut TimerState) -> Self {
71        let inner = unsafe {
72            let queue = ffi::dispatch_get_global_queue(ffi::QOS_CLASS_DEFAULT, 0);
73            ffi::dispatch_source_create(&ffi::_dispatch_source_type_timer as *const _ as ffi::dispatch_source_type_t, 0, 0, queue)
74        };
75
76        os_assert!(!inner.is_null());
77
78        unsafe {
79            ffi::dispatch_source_set_event_handler_f(inner, timer_handler);
80            ffi::dispatch_set_context(inner, state as *mut _);
81        }
82
83        Self {
84            inner,
85            //Starts as suspended
86            s_count: 1,
87        }
88    }
89
90    fn suspend(&mut self) {
91        if self.s_count == 0 {
92            unsafe {
93                ffi::dispatch_suspend(self.inner);
94            }
95
96            self.s_count += 1;
97        }
98    }
99
100    fn resume(&mut self) {
101        while self.s_count > 0 {
102            unsafe {
103                ffi::dispatch_resume(self.inner)
104            }
105
106            self.s_count -= 1;
107        }
108    }
109
110    fn set_delay(&mut self, timeout: time::Duration) {
111        self.suspend();
112
113        unsafe {
114            let start = ffi::dispatch_walltime(ptr::null(), timeout.as_nanos() as i64);
115            ffi::dispatch_source_set_timer(self.inner, start, ffi::DISPATCH_TIME_FOREVER, 0);
116        }
117
118        self.resume();
119    }
120}
121
122
123enum State {
124    Init(time::Duration),
125    Running(TimerHandle, Box<TimerState>),
126}
127
128///Timer based on Apple APIs
129pub struct AppleTimer {
130    state: State,
131}
132
133impl super::Oneshot for AppleTimer {
134    fn new(timeout: time::Duration) -> Self {
135        debug_assert!(!(timeout.as_secs() == 0 && timeout.subsec_nanos() == 0), "Zero timeout makes no sense");
136
137        Self {
138            state: State::Init(timeout),
139        }
140    }
141
142    fn is_ticking(&self) -> bool {
143        match &self.state {
144            State::Init(_) => false,
145            State::Running(_, ref state) => !state.is_done(),
146        }
147    }
148
149    fn is_expired(&self) -> bool {
150        match &self.state {
151            State::Init(_) => false,
152            State::Running(_, ref state) => state.is_done(),
153        }
154    }
155
156    fn cancel(&mut self) {
157        match &mut self.state {
158            State::Init(_) => (),
159            State::Running(ref mut fd, _) => {
160                fd.suspend();
161            }
162        }
163    }
164
165    fn restart(&mut self, new_value: time::Duration, waker: &task::Waker) {
166        debug_assert!(!(new_value.as_secs() == 0 && new_value.subsec_nanos() == 0), "Zero timeout makes no sense");
167
168        match &mut self.state {
169            State::Init(ref mut timeout) => {
170                *timeout = new_value;
171            },
172            State::Running(ref mut fd, ref state) => {
173                state.register(waker);
174                fd.set_delay(new_value);
175            },
176        }
177    }
178}
179
180impl Future for AppleTimer {
181    type Output = ();
182
183    fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context) -> task::Poll<Self::Output> {
184        self.state = match &self.state {
185            State::Init(ref timeout) => {
186                let state = Box::into_raw(Box::new(TimerState::new()));
187                let mut fd = TimerHandle::new(state);
188
189                let state = unsafe { Box::from_raw(state) };
190                state.register(ctx.waker());
191
192                fd.set_delay(*timeout);
193
194                State::Running(fd, state)
195            },
196            State::Running(_, ref state) => match state.is_done() {
197                false => return task::Poll::Pending,
198                true => return task::Poll::Ready(()),
199            }
200        };
201
202        task::Poll::Pending
203    }
204}
205
206unsafe impl Send for AppleTimer {}
207unsafe impl Sync for AppleTimer {}