timerfd_mio/
lib.rs

1#![warn(missing_debug_implementations)]
2#[cfg(not(target_os = "linux"))]
3compile_error!("This crate is only compatible with Linux.");
4
5use rustix::fd::OwnedFd;
6use rustix::time::{Itimerspec, TimerfdClockId, Timespec};
7use std::os::unix::prelude::*;
8use rustix::time::{TimerfdFlags, TimerfdTimerFlags};
9use std::time::Duration;
10use mio::event::Source;
11use mio::unix::SourceFd;
12use mio::{Interest, Registry, Token};
13use std::io;
14
15const TS_NULL: Timespec = Timespec {
16    tv_sec: 0,
17    tv_nsec: 0,
18};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum TimerFdFlag {
22    Default,
23    Abstime,
24    TimerCancelOnSet,
25}
26
27#[derive(Debug)]
28pub struct TimerFd(OwnedFd);
29impl TimerFd {
30
31    pub fn new_custom(
32        clock: TimerfdClockId,
33        nonblocking: bool,
34        cloexec: bool,
35    ) -> std::io::Result<TimerFd> {
36        let mut flags = TimerfdFlags::empty();
37        if nonblocking {
38            flags |= TimerfdFlags::NONBLOCK;
39        }
40        if cloexec {
41            flags |= TimerfdFlags::CLOEXEC;
42        }
43
44        let fd = rustix::time::timerfd_create(clock, flags)?;
45        Ok(TimerFd(fd))
46    }
47
48    pub fn new() -> std::io::Result<TimerFd> {
49        TimerFd::new_custom(TimerfdClockId::Monotonic, true, true)
50    }
51
52    #[inline]
53    fn duration_to_timespec(duration: Duration) -> Timespec {
54        Timespec { tv_sec: duration.as_secs() as i64, tv_nsec: duration.subsec_nanos() as i64 }
55    }
56
57    #[inline]
58    fn timespec_to_duration(timespec: Timespec) -> Duration {
59        Duration::new(timespec.tv_sec as u64, timespec.tv_nsec as u32)
60    }
61
62    fn convert_timerfd_flags(flags: TimerFdFlag) -> TimerfdTimerFlags {
63        match flags {
64            TimerFdFlag::Default => TimerfdTimerFlags::empty(),
65            TimerFdFlag::Abstime => TimerfdTimerFlags::ABSTIME,
66            TimerFdFlag::TimerCancelOnSet => {
67                TimerfdTimerFlags::ABSTIME | TimerfdTimerFlags::CANCEL_ON_SET
68            }
69        }
70    }
71
72    pub fn set_timeout_interval_and_flags(&mut self, value: Duration, interval: Duration, sflags: TimerFdFlag) -> std::io::Result<()>  {
73        let flags = Self::convert_timerfd_flags(sflags);
74
75        let timer_spec: Itimerspec = Itimerspec {
76            it_value: Self::duration_to_timespec(value),
77            it_interval: Self::duration_to_timespec(interval),
78        };
79        rustix::time::timerfd_settime(&self.0, flags, &timer_spec)?;
80        Ok(())
81    }
82
83    pub fn set_timeout_interval(&mut self, value: Duration, interval: Duration) -> std::io::Result<()> {
84        Self::set_timeout_interval_and_flags(self, value, interval, TimerFdFlag::Default)
85    }
86
87    pub fn set_timeout_oneshot_and_flags(&mut self, value: Duration, flags: TimerFdFlag) -> std::io::Result<()> {
88        let flags = Self::convert_timerfd_flags(flags);
89        let timer_spec: Itimerspec = Itimerspec {
90            it_value: Self::duration_to_timespec(value),
91            it_interval: TS_NULL,
92        };
93        rustix::time::timerfd_settime(&self.0, flags, &timer_spec)?;
94        Ok(())
95    }
96
97    pub fn set_timeout_oneshot(&mut self, value: Duration) -> std::io::Result<()> {
98        Self::set_timeout_oneshot_and_flags(self, value, TimerFdFlag::Default)
99    }
100
101    pub fn disarm(&mut self) {
102        let timer_spec: Itimerspec = Itimerspec {
103            it_value: TS_NULL,
104            it_interval: TS_NULL,
105        };
106        rustix::time::timerfd_settime(&self.0, TimerfdTimerFlags::empty(), &timer_spec).unwrap();
107    }
108
109    pub fn get_remaining_time(&self) -> io::Result<Duration> {
110        Ok(Self::timespec_to_duration(rustix::time::timerfd_gettime(&self.0)?.it_value))
111    }
112
113    pub fn get_interval(&self) -> io::Result<Duration> {
114        Ok(Self::timespec_to_duration(rustix::time::timerfd_gettime(&self.0)?.it_interval))
115    }
116
117    pub fn read(&self) -> io::Result<u64> {
118        let mut buffer = [0_u8; 8];
119        loop {
120            match rustix::io::read(&self.0, &mut buffer) {
121
122                // Read number of timer expirations
123                Ok(8) => { 
124                    let value = u64::from_ne_bytes(buffer);
125                    assert_ne!(value, 0);
126                    return Ok(value);
127                }
128
129                // non-blocking mode : return no timer expirations
130                Err(rustix::io::Errno::WOULDBLOCK) => return Ok(0), 
131
132                // Interrupted by a signal, read again
133                Err(rustix::io::Errno::INTR) => (), 
134
135                // Error handling
136                Err(e) => {
137                    return Err(io::Error::new(
138                        io::ErrorKind::Other,
139                        format!("Unexpected rustix::io::read error: {}", e),
140                    ));
141                }
142                _ => unreachable!(),
143            }
144        }
145    }
146
147    pub fn read_and_check_overrun(&self) -> io::Result<bool> {
148        let res = self.read()?;
149        match res {
150            0 => Ok(false), // No timer expirations
151            1 => Ok(true),  // One timer expiration
152            x =>
153            // More than one timer expiration
154            {
155                Err(io::Error::new(
156                    io::ErrorKind::Other,
157                    format!("Timer overrun: missed {} timer expirations", x - 1),
158                ))
159            }
160        }
161    }
162}
163
164impl AsRawFd for TimerFd {
165    fn as_raw_fd(&self) -> RawFd {
166        self.0.as_raw_fd()
167    }
168}
169
170impl AsFd for TimerFd {
171    fn as_fd(&self) -> rustix::fd::BorrowedFd<'_> {
172        self.0.as_fd()
173    }
174}
175
176impl Source for TimerFd {
177    fn register(
178        &mut self,
179        registry: &Registry,
180        token: Token,
181        interests: Interest,
182    ) -> io::Result<()> {
183        SourceFd(&self.as_raw_fd()).register(registry, token, interests)
184    }
185
186    fn reregister(
187        &mut self,
188        registry: &Registry,
189        token: Token,
190        interests: Interest,
191    ) -> io::Result<()> {
192        SourceFd(&self.as_raw_fd()).reregister(registry, token, interests)
193    }
194
195    fn deregister(&mut self, registry: &Registry) -> io::Result<()> {
196        SourceFd(&self.as_raw_fd()).deregister(registry)
197    }
198}
199
200#[cfg(test)]
201mod test {
202    use super::*;
203    use mio::{Events, Poll};
204    use std::time::{Duration, Instant};
205
206    fn is_fd_open(fd: RawFd) -> bool {
207        let borrowed_fd = unsafe { BorrowedFd::borrow_raw(fd) };
208        rustix::io::fcntl_getfd(borrowed_fd).is_ok()
209    }
210
211    #[test]
212    fn test_new_timerfd() {
213        let timer_fd = TimerFd::new().expect("Failed to create TimerFd");
214        assert!(timer_fd.as_raw_fd() > 0);
215    }
216
217    #[test]
218    fn test_duration_to_timespec_and_back() {
219        let duration = Duration::new(5, 500_000_000); // 5.5 seconds
220        let timespec = TimerFd::duration_to_timespec(duration);
221        assert_eq!(timespec.tv_sec, 5);
222        assert_eq!(timespec.tv_nsec, 500_000_000);
223
224        let converted_duration = TimerFd::timespec_to_duration(timespec);
225        assert_eq!(duration, converted_duration);
226    }
227
228    #[test]
229    fn test_set_timeout_oneshot() {
230        let mut timer_fd = TimerFd::new().unwrap();
231        let timeout = Duration::from_secs(1);
232        timer_fd.set_timeout_oneshot(timeout).unwrap();
233
234        let remaining_time = timer_fd.get_remaining_time().unwrap();
235        assert!(remaining_time <= timeout);
236    }
237
238    #[test]
239    fn test_set_timeout_interval() {
240        let mut timer_fd = TimerFd::new().unwrap();
241        let timeout = Duration::from_secs(1);
242        let interval = Duration::from_secs(2);
243
244        timer_fd.set_timeout_interval(timeout, interval).unwrap();
245
246        let remaining_time = timer_fd.get_remaining_time().unwrap();
247        let configured_interval = timer_fd.get_interval().unwrap();
248
249        assert!(remaining_time <= timeout);
250        assert_eq!(configured_interval, interval);
251    }
252
253    #[test]
254    fn test_disarm_timer() {
255        let mut timer_fd = TimerFd::new().unwrap();
256        let timeout = Duration::from_secs(1);
257        timer_fd.set_timeout_oneshot(timeout).unwrap();
258
259        timer_fd.disarm();
260
261        let remaining_time = timer_fd.get_remaining_time().unwrap();
262        assert_eq!(remaining_time, Duration::ZERO);
263    }
264
265    #[test]
266    fn test_read_expiration_count() {
267        let mut timer_fd = TimerFd::new().unwrap();
268        let timeout = Duration::from_millis(100);
269        timer_fd.set_timeout_oneshot(timeout).unwrap();
270
271        // Wait for the timer to expire
272        std::thread::sleep(timeout + Duration::from_millis(50));
273
274        let expirations = timer_fd.read().unwrap();
275        assert_eq!(expirations, 1);
276    }
277
278    #[test]
279    fn test_read_and_check_overrun() {
280        let mut timer_fd = TimerFd::new().unwrap();
281        let timeout = Duration::from_millis(100);
282
283        // Set a repeating timer
284        timer_fd.set_timeout_interval(timeout, timeout).unwrap();
285
286        // Wait long enough for multiple expirations
287        std::thread::sleep(timeout * 5);
288
289        // Verify that an overrun is detected
290        match timer_fd.read_and_check_overrun() {
291            Err(err) => assert!(err.to_string().contains("Timer overrun")),
292            _ => panic!("Expected timer expirations"),
293        }
294    }
295
296    #[test]
297    fn test_register_with_mio() {
298        use mio::{Events, Poll};
299
300        let mut timer_fd = TimerFd::new().unwrap();
301        let mut poll = Poll::new().unwrap();
302        let mut events = Events::with_capacity(128);
303
304        poll.registry()
305            .register(&mut timer_fd, Token(0), Interest::READABLE)
306            .unwrap();
307
308        let timeout = Duration::from_millis(100);
309        timer_fd.set_timeout_oneshot(timeout).unwrap();
310
311        poll.poll(&mut events, Some(timeout * 2)).unwrap();
312
313        let event = events.iter().next().unwrap();
314        assert_eq!(event.token(), Token(0));
315        assert!(event.is_readable());
316    }
317
318    #[test]
319    fn oneshot_timeout() {
320        const TOKEN: Token = Token(0);
321        const TIMEOUT: Duration = Duration::from_millis(100);
322
323        let mut poll = Poll::new().unwrap();
324        let mut events = Events::with_capacity(1024);
325        let mut timer = TimerFd::new().unwrap();
326
327        timer.set_timeout_oneshot(TIMEOUT).unwrap();
328        poll.registry().register(&mut timer, TOKEN, Interest::READABLE).unwrap();
329
330        // timer should not elapse before the timeout
331        poll.poll(&mut events, Some(TIMEOUT / 2)).unwrap();
332        assert!(events.is_empty());
333        assert!(timer.read().unwrap() == 0);
334
335        // timer should elapse after its timeout has passed
336        poll.poll(&mut events, Some(TIMEOUT)).unwrap();
337        assert!(!events.is_empty());
338        assert!(timer.read().unwrap() == 1);
339
340        // timer should not elapse again without a rearm
341        poll.poll(&mut events, Some(TIMEOUT)).unwrap();
342        assert!(events.is_empty());
343        assert!(timer.read().unwrap() == 0);
344
345        // timer should elapse after the rearmed timeout
346        timer.set_timeout_oneshot(TIMEOUT).unwrap();
347        poll.poll(&mut events, Some(TIMEOUT * 2)).unwrap();
348        assert!(!events.is_empty());
349        assert!(timer.read().unwrap() == 1);
350    }
351
352    #[test]
353    fn test_timerfd_closes_on_drop() {
354        let rawfd: i32;
355
356        // Test with scope
357        {
358            let timer_fd = TimerFd::new().expect("Failed to create TimerFd");
359            rawfd = timer_fd.as_raw_fd();
360            assert!(is_fd_open(rawfd));
361
362        }
363        assert!(!is_fd_open(rawfd));
364
365        // Test with drop()
366        let timer_fd = TimerFd::new().expect("Failed to create TimerFd");
367        let rawfd = timer_fd.as_raw_fd();
368        assert!(is_fd_open(rawfd));
369        drop(timer_fd);
370        assert!(!is_fd_open(rawfd));
371    }
372
373    #[test]
374    fn test_multiple_timers() {
375        let mut timer1 = TimerFd::new().expect("Failed to create TimerFd 1");
376        let mut timer2 = TimerFd::new().expect("Failed to create TimerFd 2");
377        let mut timer3 = TimerFd::new().expect("Failed to create TimerFd 3");
378
379        let duration1 = Duration::from_millis(100);
380        let duration2 = Duration::from_millis(200);
381        let duration3 = Duration::from_millis(300);
382
383        timer1.set_timeout_oneshot(duration1).expect("Failed to set timer 1");
384        timer2.set_timeout_oneshot(duration2).expect("Failed to set timer 2");
385        timer3.set_timeout_oneshot(duration3).expect("Failed to set timer 3");
386
387        let start = Instant::now();
388
389        while timer1.read().unwrap() == 0 {
390            assert!(start.elapsed() < duration1 + Duration::from_millis(50));
391        }
392        println!("Timer 1 expired!");
393
394        while timer2.read().unwrap() == 0 {
395            assert!(start.elapsed() < duration2 + Duration::from_millis(50));
396        }
397        println!("Timer 2 expired!");
398
399        while timer3.read().unwrap() == 0 {
400            assert!(start.elapsed() < duration3 + Duration::from_millis(50));
401        }
402        println!("Timer 3 expired!");
403    }
404}