os_timer/timer/
win32.rs

1use core::{time, ptr, mem};
2use core::cell::Cell;
3use core::sync::atomic::{AtomicPtr, Ordering};
4use super::BoxFnPtr;
5
6extern crate alloc;
7use alloc::boxed::Box;
8
9mod ffi {
10    pub use core::ffi::c_void;
11
12    type DWORD = u32;
13    type BOOL = i32;
14
15    #[repr(C)]
16    pub struct FileTime {
17        pub low_date_time: DWORD,
18        pub high_date_time: DWORD,
19    }
20
21    pub type Callback = unsafe extern "system" fn(cb_inst: *mut c_void, ctx: *mut c_void, timer: *mut c_void);
22
23    extern "system" {
24        pub fn CloseThreadpoolTimer(ptr: *mut c_void);
25        pub fn CreateThreadpoolTimer(cb: Callback, user_data: *mut c_void, env: *mut c_void) -> *mut c_void;
26        pub fn SetThreadpoolTimerEx(timer: *mut c_void, pftDueTime: *mut FileTime, msPeriod: DWORD, msWindowLength: DWORD) -> BOOL;
27        pub fn IsThreadpoolTimerSet(timer: *mut c_void) -> BOOL;
28        pub fn WaitForThreadpoolTimerCallbacks(timer: *mut c_void, fCancelPendingCallbacks: BOOL);
29    }
30}
31
32unsafe extern "system" fn timer_callback(_: *mut ffi::c_void, data: *mut ffi::c_void, _: *mut ffi::c_void) {
33    if !data.is_null() {
34        let cb: fn() -> () = mem::transmute(data);
35
36        (cb)();
37    }
38}
39
40unsafe extern "system" fn timer_callback_unsafe(_: *mut ffi::c_void, data: *mut ffi::c_void, _: *mut ffi::c_void) {
41    if !data.is_null() {
42        let cb: unsafe fn() -> () = mem::transmute(data);
43
44        (cb)();
45    }
46}
47
48unsafe extern "system" fn timer_callback_generic<T: FnMut() -> ()>(_: *mut ffi::c_void, data: *mut ffi::c_void, _: *mut ffi::c_void) {
49    if !data.is_null() {
50        let cb = &mut *(data as *mut T);
51
52        (cb)();
53    }
54}
55
56enum CallbackVariant {
57    Trivial(*mut ffi::c_void),
58    Boxed(Box<dyn FnMut()>),
59}
60
61///Timer's callback abstraction
62pub struct Callback {
63    variant: CallbackVariant,
64    ffi_cb: ffi::Callback,
65}
66
67impl Callback {
68    ///Creates raw callback for platform timer.
69    ///
70    ///Signature depends on platform.
71    pub unsafe fn raw(ffi_cb: ffi::Callback, data: *mut ffi::c_void) -> Self {
72        Self {
73            variant: CallbackVariant::Trivial(data),
74            ffi_cb,
75        }
76    }
77
78    ///Creates callback using plain rust function
79    pub fn plain(cb: fn()) -> Self {
80        Self {
81            variant: CallbackVariant::Trivial(cb as _),
82            ffi_cb: timer_callback,
83        }
84    }
85
86    ///Creates callback using plain unsafe function
87    pub fn unsafe_plain(cb: unsafe fn()) -> Self {
88        Self {
89            variant: CallbackVariant::Trivial(cb as _),
90            ffi_cb: timer_callback_unsafe,
91        }
92    }
93
94    ///Creates callback using closure, storing it on heap.
95    pub fn closure<F: 'static + FnMut()>(cb: F) -> Self {
96        Self {
97            variant: CallbackVariant::Boxed(Box::new(cb)),
98            ffi_cb: timer_callback_generic::<F>,
99        }
100    }
101}
102
103///Windows thread pool timer
104pub struct Timer {
105    inner: AtomicPtr<ffi::c_void>,
106    data: Cell<BoxFnPtr>,
107}
108
109impl Timer {
110    #[inline]
111    ///Creates new uninitialized instance.
112    ///
113    ///In order to use it one must call `init`.
114    pub const unsafe fn uninit() -> Self {
115        Self {
116            inner: AtomicPtr::new(ptr::null_mut()),
117            data: Cell::new(BoxFnPtr::null()),
118        }
119    }
120
121    #[inline(always)]
122    fn get_inner(&self) -> *mut ffi::c_void {
123        let inner = self.inner.load(Ordering::Acquire);
124        debug_assert!(!inner.is_null(), "Timer has not been initialized");
125        inner
126    }
127
128    #[inline(always)]
129    ///Returns whether timer is initialized
130    pub fn is_init(&self) -> bool {
131        !self.inner.load(Ordering::Acquire).is_null()
132    }
133
134    #[must_use]
135    ///Performs timer initialization
136    ///
137    ///`cb` is variant of callback to invoke when timer expires
138    ///
139    ///Returns whether timer has been initialized successfully or not.
140    ///
141    ///If timer is already initialized does nothing, returning false.
142    pub fn init(&self, cb: Callback) -> bool {
143        if self.is_init() {
144            return false;
145        }
146
147        let ffi_cb = cb.ffi_cb;
148        let (data, ffi_data) = match cb.variant {
149            CallbackVariant::Trivial(data) => (BoxFnPtr::null(), data),
150            CallbackVariant::Boxed(cb) => unsafe {
151                let raw = Box::into_raw(cb);
152                (BoxFnPtr(mem::transmute(raw)), raw as *mut ffi::c_void)
153            },
154        };
155
156        let handle = unsafe {
157            ffi::CreateThreadpoolTimer(ffi_cb, ffi_data, ptr::null_mut())
158        };
159
160        match self.inner.compare_exchange(ptr::null_mut(), handle, Ordering::SeqCst, Ordering::Acquire) {
161            Ok(_) => match handle.is_null() {
162                true => false,
163                false => {
164                    //safe because we can never reach here once `handle.is_null() != true`
165                    self.data.set(data);
166                    true
167                },
168            },
169            Err(_) => {
170                unsafe {
171                    ffi::CloseThreadpoolTimer(handle);
172                }
173                false
174            }
175        }
176    }
177
178    ///Creates new timer, invoking provided `cb` when timer expires.
179    ///
180    ///On failure, returns `None`
181    pub fn new(cb: Callback) -> Option<Self> {
182        let ffi_cb = cb.ffi_cb;
183        let (data, ffi_data) = match cb.variant {
184            CallbackVariant::Trivial(data) => (BoxFnPtr::null(), data),
185            CallbackVariant::Boxed(cb) => unsafe {
186                let raw = Box::into_raw(cb);
187                (BoxFnPtr(mem::transmute(raw)), raw as *mut ffi::c_void)
188            },
189        };
190
191        let handle = unsafe {
192            ffi::CreateThreadpoolTimer(ffi_cb, ffi_data, ptr::null_mut())
193        };
194
195        if handle.is_null() {
196            return None;
197        }
198
199        Some(Self {
200            inner: AtomicPtr::new(handle),
201            data: Cell::new(data),
202        })
203    }
204
205    ///Schedules timer to alarm periodically with `interval` with initial alarm of `timeout`.
206    ///
207    ///Note that if timer has been scheduled before, but hasn't expire yet, behaviour is undefined (Callback may or may not be called).
208    ///To prevent that user must `cancel` timer first.
209    ///
210    ///# Note
211    ///
212    ///- `interval` is truncated by `u32::max_value()`
213    ///
214    ///Returns `true` if successfully set, otherwise on error returns `false`
215    pub fn schedule_interval(&self, timeout: time::Duration, interval: time::Duration) -> bool {
216        let mut ticks = i64::from(timeout.subsec_nanos() / 100);
217        ticks += (timeout.as_secs() * 10_000_000) as i64;
218        let ticks = -ticks;
219
220        let interval = interval.as_millis() as u32;
221
222        unsafe {
223            let mut time: ffi::FileTime = mem::transmute(ticks);
224            ffi::SetThreadpoolTimerEx(self.get_inner(), &mut time, interval, 0);
225        }
226
227        true
228    }
229
230    #[inline]
231    ///Returns `true` if timer has been scheduled and still pending.
232    ///
233    ///On Win/Mac it only returns whether timer has been scheduled, as there is no way to check
234    ///whether timer is ongoing
235    pub fn is_scheduled(&self) -> bool {
236        let handle = self.get_inner();
237        unsafe {
238            ffi::IsThreadpoolTimerSet(handle) != 0
239        }
240    }
241
242    #[inline]
243    ///Cancels ongoing timer, if it was scheduled.
244    pub fn cancel(&self) {
245        let handle = self.get_inner();
246        unsafe {
247            ffi::SetThreadpoolTimerEx(handle, ptr::null_mut(), 0, 0);
248            ffi::WaitForThreadpoolTimerCallbacks(handle, 1);
249        }
250    }
251}
252
253impl Drop for Timer {
254    fn drop(&mut self) {
255        let handle = self.inner.load(Ordering::Relaxed);
256        if !handle.is_null() {
257            self.cancel();
258            unsafe {
259                ffi::CloseThreadpoolTimer(handle);
260            }
261        }
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268
269    #[test]
270    fn init_plain_fn() {
271        let mut timer = unsafe {
272            Timer::uninit()
273        };
274
275        fn cb() {
276        }
277
278        let closure = || {
279        };
280
281        assert!(timer.init(Callback::plain(cb)));
282        let ptr = timer.inner.load(Ordering::Relaxed);
283        assert!(!ptr.is_null());
284        assert!(timer.data.get_mut().is_null());
285
286        assert!(!timer.init(Callback::closure(closure)));
287        assert!(!ptr.is_null());
288        assert_eq!(ptr, timer.inner.load(Ordering::Relaxed));
289        assert!(timer.data.get_mut().is_null());
290    }
291
292    #[test]
293    fn init_closure() {
294        let mut timer = unsafe {
295            Timer::uninit()
296        };
297
298        fn cb() {
299        }
300
301        let closure = || {
302        };
303
304        assert!(timer.init(Callback::closure(closure)));
305        let ptr = timer.inner.load(Ordering::Relaxed);
306        assert!(!ptr.is_null());
307        assert!(!timer.data.get_mut().is_null());
308
309        assert!(!timer.init(Callback::plain(cb)));
310        assert!(!ptr.is_null());
311        assert_eq!(ptr, timer.inner.load(Ordering::Relaxed));
312        assert!(!timer.data.get_mut().is_null());
313    }
314}