os_timer/timer/
posix.rs

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