epoll_rs/
lib.rs

1//! A rusty wrapper for Linux's epoll interface that is easy to use and hard
2//! to misuse.
3//!
4//! Create a new epoll instance with [`Epoll::new`]. Add any struct
5//! that implements the [`OwnedRawFd`] trait with [`Epoll::add`].
6//! epoll::add returns a [`Token`] that takes ownership of the added file.
7//! ```no_run
8//! use epoll_rs::{Epoll, Opts};
9//! # fn main() -> std::io::Result<()> {
10//! let mut epoll = Epoll::new()?;
11//! # let file = std::fs::File::open("")?;
12//! let token = epoll.add(file, Opts::IN)?;
13//! # Ok(())
14//! # }
15//! ```
16//!
17//! Tokens returned from one epoll instance cannot be used with another instance.
18//! Doing so will cause a panic in debug mode and undefined behavior in release mode.
19//! ```no_run
20//! use epoll_rs::{Epoll, Opts};
21//! # fn main() -> std::io::Result<()> {
22//! let mut epoll1 = Epoll::new()?;
23//! let mut epoll2 = Epoll::new()?;
24//! # let file = std::fs::File::open("")?;
25//! let token1 = epoll1.add(file, Opts::IN)?;
26//! let res = epoll2.remove(token1); // <- undefined behavior in release mode
27//! # Ok(())
28//! # }
29//! ```
30
31use bitflags::bitflags;
32use std::fmt::{self, Debug};
33use std::hash::{Hash, Hasher};
34use std::os::unix::{self, io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}};
35use std::{convert::TryInto, io, net, time::Duration};
36#[cfg(not(debug_assertions))]
37use std::marker::PhantomData;
38
39
40/// Opaque type used to refer to single files registered with an epoll instance
41///
42/// In debug mode it has extra fields to ensure you're only using it with the
43/// epoll instance it came from, but in release mode these fields are stripped
44/// out.
45#[derive(Debug)] //TODO: consider other derives (eq? hash?)
46pub struct Token<'a, F: OwnedRawFd> {
47    file: F,
48    #[cfg(not(debug_assertions))]
49    phantom: PhantomData<&'a Epoll>,
50    #[cfg(debug_assertions)]
51    epoll: &'a Epoll,
52    #[cfg(debug_assertions)]
53    epoll_fd: RawFd,
54}
55
56impl<'a, F: OwnedRawFd> Token<'a, F> {
57    #[cfg(debug_assertions)]
58    fn new(file: F, epoll: &'a Epoll) -> Self {
59        Token {
60            epoll_fd: epoll.epoll_fd,
61            file,
62            epoll,
63        }
64    }
65    #[cfg(not(debug_assertions))]
66    fn new(file: F) -> Self {
67        Token {
68            file,
69            phantom: PhantomData,
70        }
71    }
72
73    /// Consumes this token and returns the contained file
74    ///
75    /// This does not remove the file from any epoll instances it has been
76    /// added to.
77    pub fn into_file(self) -> F {
78        self.file
79    }
80
81    /// Gives an immutable reference to the contained file
82    pub fn file(&self) -> &F {
83        &self.file
84    }
85
86    /// Equivalent to calling `self.file().as_raw_fd()`, only shorter
87    ///
88    /// Don't close the returned RawFd or create a `File` from it
89    pub fn fd(&self) -> RawFd {
90        self.file().as_raw_fd()
91    }
92
93    /// Gives a mutable reference to the contained file
94    pub fn file_mut(&mut self) -> &mut F {
95        &mut self.file
96    }
97}
98
99bitflags! {
100    /// Options used in [adding](Epoll::add) a file or
101    /// [modifying](Epoll::modify) a previously added file
102    ///
103    /// Bitwise or (`|`) these together to combine multiple options
104    pub struct Opts: libc::c_uint {
105        /// Available for reads
106        const IN = libc::EPOLLIN as libc::c_uint;
107        /// Available for writes
108        const OUT = libc::EPOLLOUT as libc::c_uint;
109        /// Socket connection closed
110        const RDHUP = libc::EPOLLRDHUP as libc::c_uint;
111        /// Exceptional condition (see man 3 poll)
112        const PRI = libc::EPOLLPRI as libc::c_uint;
113        const ERR = libc::EPOLLERR as libc::c_uint;
114        /// Hangup. If you register for another event type, this is automatically enabled
115        const HUP = libc::EPOLLHUP as libc::c_uint;
116        /// Use edge-triggered notifications
117        const ET = libc::EPOLLET as libc::c_uint;
118        const ONESHOT = libc::EPOLLONESHOT as libc::c_uint;
119        const WAKEUP = libc::EPOLLWAKEUP as libc::c_uint;
120        /// Deliver on only one epoll fd (see man epoll)
121        const EXCLUSIVE = libc::EPOLLEXCLUSIVE as libc::c_uint;
122    }
123}
124
125/// if condition is true, return errno
126macro_rules! then_errno {
127    ($e:expr) => {
128        if $e {
129            return Err(io::Error::last_os_error());
130        }
131    };
132}
133
134//this should be in libc, but isn't
135#[derive(Copy, Clone)]
136#[repr(C)]
137union EpollData {
138    ptr: * mut libc::c_void,
139    fd: libc::c_int,
140    u32: u32,
141    u64: u64,
142}
143
144impl EpollData {
145    const fn new(fd: RawFd) -> Self {
146        EpollData{fd}
147    }
148
149    //TODO: make const when https://github.com/rust-lang/rust/issues/51909
150    // is resolved
151    fn get(self) -> RawFd {
152        // Safety: this library only reads from and writes to epoll_data.fd
153        // the other fields are included only for layout compatibility
154        unsafe{self.fd}
155    }
156}
157
158// Debug like an i32
159impl Debug for EpollData {
160    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161        let fd = self.get();
162        fd.fmt(f)
163    }
164}
165
166impl PartialEq for EpollData {
167    fn eq(&self, other: &Self) -> bool {
168        self.get() == other.get()
169    }
170}
171
172impl Eq for EpollData {}
173
174impl Hash for EpollData {
175    fn hash<H: Hasher>(&self, state: &mut H) {
176        self.get().hash(state);
177    }
178}
179
180/// An event, such as that a file is available for reading.
181/// Transmute compatible with `libc::epoll_event`
182#[cfg_attr(
183    any(
184        all(
185            target_arch = "x86",
186            not(target_env = "musl"),
187            not(target_os = "android")),
188        target_arch = "x86_64"),
189repr(packed))]
190#[repr(C)]
191#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
192pub struct EpollEvent {
193    pub events: Opts,
194    fd: EpollData,
195}
196
197impl EpollEvent {
198    pub const fn zeroed() -> Self {
199        Self::new(Opts::empty(), 0)
200    }
201
202    const fn new(opts: Opts, fd: RawFd) -> Self {
203        EpollEvent{events: opts, fd:EpollData::new(fd)}
204    }
205
206    /// The RawFd that this event is associated with.
207    ///
208    /// For example if a Vec of sockets were added to an epoll instance,
209    /// this is the fd of the socket that is ready.
210    /// Do not create an [`OwnedRawFd`] (including File) out of this, as closing it
211    /// will close the file that was added to Epoll.
212    pub fn fd(&self) -> RawFd {
213        // Safety: every bit pattern is a valid libc::c_int, so this is always safe
214        // Furthermore, this library mantains as an invariant that self.fd.fd refers
215        // to an open file
216        
217        #[cfg(debug_assert)]
218        {
219            let fd = self.fd.get();
220            // valid fds are always non-negative
221            debug_assert!( fd >= 0 );
222            // Safety: every bit pattern is a valid u64
223            let u64 = unsafe{ self.fd.u64 };
224            // make sure padding bits are zeroed
225            const PADDING_SIZE:usize = size_of::<EpollData>() - size_of::<libc::c_int>();
226            debug_assert_eq!(&u64.to_be_bytes()[0..PADDING_SIZE], &[0; PADDING_SIZE]);
227        }
228        self.fd.get()
229    }
230}
231
232/// Rust abstraction atop linux's epoll interface.
233/// Wrapper type around an epoll file descriptor. Performs proper cleanup on drop.
234///
235/// ```rust, no_run
236/// use std::{time::Duration, fs::File};
237/// use std::os::unix::io::AsRawFd;
238/// use epoll_rs::{Epoll, Opts, EpollEvent};
239///
240/// # fn main() -> std::io::Result<()> {
241/// let mut epoll = Epoll::new()?;
242/// let file = File::open("/")?;
243/// let token = epoll.add(file, Opts::IN)?;
244/// // add other files...
245/// let mut buf = [EpollEvent::zeroed(); 10];
246/// let events = epoll.wait_timeout(&mut buf, Duration::from_millis(50))?;
247/// for event in events {
248///     if token.fd() == event.fd() {
249///         println!("File ready for reading");
250///     } else {
251///         println!("File not ready for reading");
252///     }
253/// }
254/// epoll.remove(token); // this cleanup is performed when epoll goes out of scope
255/// # Ok(())
256/// # }
257/// ```
258#[derive(Debug)]
259pub struct Epoll {
260    epoll_fd: RawFd
261}
262
263impl AsRawFd for Epoll {
264    fn as_raw_fd(&self) -> unix::io::RawFd {
265        self.epoll_fd
266    }
267}
268
269impl IntoRawFd for Epoll {
270    fn into_raw_fd(self) -> RawFd {
271        self.epoll_fd
272    }
273}
274
275impl FromRawFd for Epoll {
276    /// Safety: super unsafe. Use only if fd came from this library.
277    /// Otherwise, you need to make sure all fds added to this epoll have
278    /// `epoll_data` that contains their own fd, that all added fds are open
279    /// and that fd is open and refers to an epoll file description.
280    unsafe fn from_raw_fd(fd: RawFd) -> Self {
281        Epoll{epoll_fd: fd}
282    }
283}
284
285impl Drop for Epoll {
286    fn drop(&mut self) {
287        // Safety: this library mantains as an invariant that self.epoll_fd
288        // refers to a valid, open file, but libc::close is safe to call on
289        // invalid/closed file descriptors too (it returns -1 and sets errno)
290        unsafe {libc::close(self.epoll_fd)};
291    }
292}
293
294/// A trait for all structs that wrap a unix file descriptor.
295///
296/// This trait is specifically not implemented for RawFd itself, since that
297/// would safely allow the use of fds that don't refer to an open file.
298/// TODO: replace with `Into<OwnedFd>` when !#[feature(io_safety)] lands
299/// <https://github.com/rust-lang/rust/issues/87074>
300pub trait OwnedRawFd: AsRawFd + IntoRawFd + FromRawFd {}
301
302impl OwnedRawFd for std::fs::File {}
303impl OwnedRawFd for net::TcpListener {}
304impl OwnedRawFd for net::TcpStream {}
305impl OwnedRawFd for net::UdpSocket {}
306impl OwnedRawFd for unix::net::UnixDatagram {}
307impl OwnedRawFd for unix::net::UnixListener {}
308impl OwnedRawFd for unix::net::UnixStream {}
309// impl OwnedRawFd for io::Stderr {}
310// impl OwnedRawFd for io::Stdin {}
311// impl OwnedRawFd for io::Stdout {}
312// impl OwnedRawFd for std::process::ChildStderr {}
313// impl OwnedRawFd for std::process::ChildStdin {}
314// impl OwnedRawFd for std::process::ChildStdout {}
315// impl OwnedRawFd for io::StderrLock<'_> {}
316// impl OwnedRawFd for io::StdinLock<'_> {}
317// impl OwnedRawFd for io::StdoutLock<'_> {}
318impl OwnedRawFd for Epoll {}
319
320impl Epoll {
321    pub fn new() -> io::Result<Self> {
322        // Safety: Always safe. We're passing flags and getting an fd back
323        let fd = unsafe { libc::epoll_create1(libc::EPOLL_CLOEXEC) };
324        then_errno!(fd == -1);
325        Ok(Epoll {epoll_fd: fd})
326    }
327
328    // panic if token comes from a different epoll instance
329    #[cfg(debug_assertions)]
330    fn check_token<F: OwnedRawFd>(&self, token: &Token<'_, F>) {
331        assert_eq!(self as *const _, token.epoll as *const _);
332    }
333
334    /// Add a file-like struct to the epoll instance
335    ///
336    /// The returned token can be ignored if you don't need to distinguish
337    /// which file is ready, but dropping the token closes the added file
338    pub fn add<'a, 'b:'a, F: OwnedRawFd>(
339        &'b self,
340        file: F,
341        opts: Opts,
342    ) -> io::Result<Token<'a, F>> {
343        // Safety: lifetime bounds on function declaration keep this safe
344        unsafe { self.add_raw_fd(file.into_raw_fd(), opts) }
345    }
346
347    /// Remove a previously added file-like struct from this epoll instance
348    ///
349    /// No new events will be deilvered referring to this token even if the 
350    /// event occurs before removal. Consumes the token, returning the contained file,
351    /// since the file it is associated with is no longer in this epoll instance
352    pub fn remove<'a, 'b:'a, F: OwnedRawFd>(
353        &'b self,
354        token: Token<'a, F>,
355    ) -> io::Result<F> {
356        #[cfg(debug_assertions)]
357        self.check_token(&token);
358        let empty_event = &mut EpollEvent::zeroed();
359        // Safety: empty event (required for early linux kernels) must point to
360        // a valid epoll_event struct. This is guaranteed by EpollEvent having
361        // the same memory layout as struct epoll_event.
362        let res = unsafe {
363            libc::epoll_ctl(
364                self.epoll_fd,
365                libc::EPOLL_CTL_DEL,
366                token.file.as_raw_fd(),
367                empty_event as *mut EpollEvent as *mut _,
368            )
369        };
370        then_errno!(res == -1);
371        Ok(token.into_file())
372    }
373
374    /// Change the [`Opts`] of a previously added file-like struct
375    pub fn modify<'a, 'b: 'a, F: OwnedRawFd>(
376        &'b self,
377        token: &'a Token<'a, F>,
378        opts: Opts,
379    ) -> io::Result<()> {
380        #[cfg(debug_assertions)]
381        self.check_token(token);
382        let mut event = EpollEvent::new(opts, token.file.as_raw_fd());
383        // Safety: event must point to a valid epoll_event struct
384        let res = unsafe {
385            libc::epoll_ctl(
386                self.epoll_fd,
387                libc::EPOLL_CTL_MOD,
388                token.file.as_raw_fd(),
389                &mut event as *mut EpollEvent as *mut _,
390            )
391        };
392        then_errno!(res == -1);
393        Ok(())
394    }
395
396    // TODO: use uninitalized memory. waiting on stabilization of
397    // maybe_uninit_slice, https://github.com/rust-lang/rust/issues/63569
398    /// If passed a zero length buf, this function will return Err
399    fn wait_maybe_timeout<'a, 'b>(
400        &'a self, buf: &'b mut[EpollEvent],
401        timeout: Option<Duration>,
402    ) -> io::Result<&'b mut[EpollEvent]> {
403        let timeout_ms = match timeout {
404            Some(t) => t.as_millis().try_into().unwrap_or(i32::MAX),
405            None => -1
406        };
407        let max_events = buf.len().clamp(0, libc::c_int::MAX.try_into().unwrap());
408        // Safety: buf_size must be non-zero and <= to the length of self.buf
409        // self.buf.as_mut_ptr must point to memory sized and aligned for epoll_events
410        let res = unsafe {
411            libc::epoll_wait(
412                self.epoll_fd,
413                buf.as_mut_ptr() as *mut libc::epoll_event,
414                max_events as libc::c_int,
415                timeout_ms,
416            )
417        };
418        then_errno!(res == -1);
419        let len = res.try_into().unwrap();
420        Ok(&mut buf[0..len])
421    }
422
423    /// Wait indefinetly until at least one event and at most `buf.len()` events occur.
424    ///
425    /// If passed a zero length buf, this function will return Err
426    pub fn wait<'a, 'b>(&'a self, buf: &'b mut[EpollEvent]) -> io::Result<&'b mut [EpollEvent]> {
427        self.wait_maybe_timeout(buf, None)
428    }
429
430    /// Wait until at least one event and at most `buf.len()` events occur or timeout expires.
431    ///
432    /// If passed a zero length buf, this function will return Err
433    pub fn wait_timeout<'a, 'b>(
434        &'a self, 
435        buf: &'b mut[EpollEvent],
436        timeout: Duration,
437    ) -> io::Result<&'b mut [EpollEvent]> {
438        self.wait_maybe_timeout(buf, Some(timeout))
439    }
440
441    /// Wait indefinetly for one event.
442    pub fn wait_one(&self) -> io::Result<EpollEvent> {
443        let mut buf = [EpollEvent::zeroed(); 1];
444        let res = self.wait(&mut buf);
445        res.map(|slice| slice[0])
446    }
447
448    /// Wait for one event or until timeout expires.
449    ///
450    /// Return value of Ok(None) indicates timeout expired
451    pub fn wait_one_timeout(&self, timeout: Duration) -> io::Result<Option<EpollEvent>> {
452        let mut buf = [EpollEvent::zeroed(); 1];
453        let res = self.wait_timeout(&mut buf, timeout);
454        res.map(|slice| slice.get(0).copied())
455    }
456
457    /// Manually close this epoll instance, handling any potential errors
458    ///
459    /// Same as drop, only this lets the user deal with errors in closing.
460    /// The invariants of this library should mean that close never fails, but
461    /// those invariants can be broken with unsafe code.
462    pub fn close(&mut self) -> io::Result<()> {
463        // Safety: this library mantains as an invariant that self.epoll_fd
464        // refers to a valid, open file, but libc::close is safe to call on
465        // invalid/closed file descriptors too (it returns -1 and sets errno)
466        let res = unsafe {libc::close(self.epoll_fd)};
467        then_errno!(res == -1);
468        Ok(())
469    }
470
471    /// Adds a RawFd to an epoll instance directly
472    ///
473    /// This is pretty unsafe, prefer [add](Self::add)
474    ///
475    /// ## Safety
476    /// `fd` must refer to a currently open, unowned, file descriptor of type F.
477    /// If `fd` refers to a file that is dropped or the fd is closed before this Epoll
478    /// instance is, later use of the returned `Token` may modify a different file,
479    /// since file descriptors (`RawFd`s) are reused.
480    /// The following is notably [io unsound](https://rust-lang.github.io/rfcs/3128-io-safety.html)
481    /// ```rust
482    /// use epoll_rs::{Epoll, Opts, Token};
483    /// use std::{fs::File, io, os::unix::io::{FromRawFd, AsRawFd}};
484    /// # fn main() -> io::Result<()> {
485    /// let mut epoll = Epoll::new()?;
486    /// {
487    ///     let stdin = unsafe{File::from_raw_fd(1)};
488    ///     let token: Token<File> = unsafe {
489    ///         epoll.add_raw_fd(stdin.as_raw_fd(), Opts::IN)?
490    ///     };
491    /// } // stdin dropped here, fd 1 `libc::close`d, invariants violated
492    /// # Ok(())
493    /// # }
494    /// ```
495    /// instead use into_raw_fd to get an unowned RawFd
496    /// ```rust
497    /// use epoll_rs::{Epoll, Opts, Token};
498    /// use std::{fs::File, io, os::unix::io::{FromRawFd, AsRawFd, IntoRawFd}};
499    /// # fn main() -> io::Result<()> {
500    /// let mut epoll = Epoll::new()?;
501    /// {
502    ///     let stdin = unsafe{File::from_raw_fd(1)};
503    ///     let token: Token<File> = unsafe {
504    ///         epoll.add_raw_fd(stdin.into_raw_fd(), Opts::IN)?
505    ///     };
506    /// } // stdin was consumed by into_raw_fd(), so it's drop code won't be run
507    /// # Ok(())
508    /// # }
509    /// ```
510    pub unsafe fn add_raw_fd<'a, 'b:'a, F: OwnedRawFd>(
511        &'b self,
512        fd: RawFd,
513        opts: Opts,
514    ) -> io::Result<Token<'a, F>> {
515        #[cfg(debug_assertions)]
516        let token = Token::new(F::from_raw_fd(fd), self);
517        #[cfg(not(debug_assertions))]
518        let token = Token::new(F::from_raw_fd(fd));
519        let mut event = EpollEvent::new(opts, token.file.as_raw_fd());
520        let res = libc::epoll_ctl(
521            self.epoll_fd,
522            libc::EPOLL_CTL_ADD,
523            fd,
524            &mut event as *mut _ as *mut libc::epoll_event
525        );
526        then_errno!(res == -1);
527
528        Ok(token)
529    }
530}
531
532// Use a doctest because those are allowed to fail at compile time
533/// ```compile_fail
534/// # use epoll_rs::*;
535/// # use std::*;
536/// # use time::*;
537/// # use fs::*;
538/// # fn main() -> std::io::Result<()> {
539/// let file = File::open("").unwrap();
540/// let token = {
541///     let mut epoll = Epoll::new().unwrap();
542///     let token = epoll.add(file, Opts::OUT).unwrap();
543///     epoll.wait_one_timeout(Duration::from_millis(10)).unwrap();
544///     token
545/// }; // epoll doesn't live long enough
546/// # Ok(())
547/// # }
548/// ```
549//#[cfg(test)]
550#[doc(hidden)]
551#[allow(unused)] // this test is actually a doctest
552fn test_token_lifetime() {}
553
554#[cfg(test)]
555mod test {
556    use rand::Rng;
557
558    use crate::{Epoll, EpollEvent, Opts, Token};
559    use std::collections::HashMap;
560    use std::convert::TryInto;
561    use std::os::unix::{io::{AsRawFd, FromRawFd}, prelude::RawFd};
562    use std::{
563        fs::File,
564        io::{self, Read, Write},
565        thread,
566        time::{Duration, Instant},
567        mem::{size_of, align_of},
568    };
569
570    // Checks that two different types have the same memory representation bit
571    // for bit. This is an important guarantee because the linux kernel works
572    // with C definitions, and this library reimplements those definitions for
573    // convience.
574    fn assert_bitwise_eq<T: Sized, U: Sized>(t:T, u:U) {
575        assert_eq!(size_of::<T>(), size_of::<U>(), "can't be bitwise equal if different sizes");
576        let left_ptr = &t as *const T as *const u8;
577        let right_ptr = &u as *const U as *const u8;
578        for byte_idx in 0_isize..(size_of::<T>().try_into().unwrap()) {
579            // Safety: we know size of T == size of U and we read bytes only within this range
580            let left_byte = unsafe { *left_ptr.offset(byte_idx)};
581            let right_byte = unsafe { *right_ptr.offset(byte_idx)};
582            assert_eq!(left_byte, right_byte, "Byte number {} is different", byte_idx);
583        }
584    }
585
586    #[test]
587    fn test_epoll_event_equivalence() {
588        assert_eq!(size_of::<libc::epoll_event>(), size_of::<EpollEvent>());
589        assert_eq!(align_of::<libc::epoll_event>(), align_of::<EpollEvent>());
590
591        let libc_event= libc::epoll_event{events: libc::EPOLLOUT as u32, u64: i32::MAX as u64};
592        let event = EpollEvent::new(Opts::OUT, i32::MAX);
593        assert_bitwise_eq(event, libc_event);
594    }
595
596    // Opens a unix pipe and wraps in in Rust `File`s
597    //                            read  write
598    fn open_pipe() -> io::Result<(File, File)> {
599        let (read, write) = {
600            let mut pipes = [0 as RawFd; 2];
601            // Safety: pipes must be sized and aligned to fit two c_ints/RawFds
602            let res = unsafe { libc::pipe2(pipes.as_mut_ptr(), 0) };
603            if res != 0 {
604                return Err(io::Error::last_os_error());
605            }
606            (pipes[0], pipes[1])
607        };
608        // Safety: these files will be the sole owner of the file descriptors
609        // since the fds are dropped when this function returns
610        let read = unsafe { File::from_raw_fd(read) };
611        let write = unsafe { File::from_raw_fd(write) };
612        Ok((read, write))
613    }
614
615    // Tests that an epoll instance can outlive the token it generates,
616    // and that dropping a token nullified pending events, even if the event
617    // takes place before the token is dropped
618    #[test]
619    fn test_epoll_outlives_token() {
620        let epoll = Epoll::new().unwrap();
621        let (read, mut write) = open_pipe().unwrap();
622        write.write(&mut[0]).unwrap();
623        {
624            // Token immediately discarded
625            let _ = epoll.add(read, Opts::IN).unwrap();
626        }
627        let event = epoll.wait_one_timeout(Duration::from_millis(10));
628        assert_eq!(event.unwrap(), None);
629    }
630
631    #[test]
632    fn test_epoll_wait_read() {
633        const MESSAGE: &[u8; 6] = b"abc123";
634        fn wait_then_read(file: File) -> Instant {
635            let epoll = Epoll::new().unwrap();
636            let mut tok = epoll.add(file, Opts::IN).unwrap();
637            let event = epoll.wait_one().unwrap();
638            assert_eq!(
639                event,
640                EpollEvent::new(Opts::IN, tok.fd())
641            );
642            let read_instant = Instant::now();
643            let mut buf = [0_u8; 100];
644            tok.file_mut().read(&mut buf).unwrap();
645            assert_eq!(MESSAGE, &buf[0..MESSAGE.len()]);
646            read_instant
647        }
648
649        let (read, mut write) = open_pipe().unwrap();
650        let th = thread::spawn(move || wait_then_read(read));
651        thread::sleep(Duration::from_millis(120));
652        write.write(MESSAGE).unwrap();
653        let instant = th.join().unwrap();
654        let elapsed = instant.elapsed();
655        assert!(elapsed < Duration::from_millis(1), "elapsed: {:?}", elapsed);
656    }
657
658    #[test]
659    fn test_timeout() {
660        let (read, _write) = open_pipe().unwrap();
661        let epoll = Epoll::new().unwrap();
662        epoll.add(read, Opts::IN).unwrap();
663        for &wait_ms in [0_u64, 30, 100].iter() {
664            let start_wait = Instant::now();
665            let event = epoll.wait_one_timeout(Duration::from_millis(wait_ms)).unwrap();
666            assert_eq!(event, None);
667            let elapsed = start_wait.elapsed();
668            assert!(
669                elapsed > Duration::from_millis(wait_ms),
670                "elapsed: {:?}",
671                elapsed
672            );
673            assert!(
674                elapsed < Duration::from_millis(wait_ms + 1),
675                "elapsed: {:?}",
676                elapsed
677            );
678        }
679    }
680
681    #[test]
682    fn test_hup() {
683        let (read, write) = open_pipe().unwrap();
684        let read_fd = read.as_raw_fd();
685        let epoll = Epoll::new().unwrap();
686        // no need to epoll.add(Opts::HUP) - it is added by default
687        let _token = epoll.add(read, Opts::empty()).unwrap();
688        drop(write);
689        let event = epoll.wait_one_timeout(Duration::from_millis(10)).unwrap().unwrap();
690        assert_eq!(
691            event,
692            EpollEvent::new(Opts::HUP, read_fd)
693        )
694    }
695
696    #[test]
697    fn test_wait_many() {
698        // open a bunch of pipes
699        const NUM_PIPES:usize = 20;
700        const MESSAGE: &[u8;12] = b"test message";
701        let (reads, mut writes):(Vec<File>, Vec<File>) = 
702            (0..NUM_PIPES)
703            .map(|_| open_pipe().unwrap())
704            .unzip();
705        let epoll = Epoll::new().unwrap();
706        // Add read ends of pipes to an epoll instance
707        let mut tokens: HashMap<RawFd, (usize, Token<File>)> = reads
708            .into_iter()
709            .map(|read| epoll.add(read, Opts::IN).unwrap())
710            .enumerate()
711            .map(|(idx, tok)| (tok.fd(), (idx,tok)))
712            .collect();
713
714        // Write to a random pipe in `writes`
715        let secret_rand = {
716            let mut rng = rand::thread_rng();
717            let rand = rng.gen_range(0..NUM_PIPES);
718            eprintln!("Writing to pipe {}", rand);
719            assert_eq!(epoll.wait_one_timeout(Duration::from_millis(0)).unwrap(), None);
720            writes[rand].write(MESSAGE).unwrap();
721            rand
722        };
723
724        // Epoll.wait to find out which pipe was written to
725        let event = epoll.wait_one_timeout(Duration::from_millis(10)).unwrap().unwrap();
726        let (idx, token) = tokens.get_mut(&event.fd()).unwrap();
727
728        let mut buf = [0; MESSAGE.len()];
729        token.file_mut().read(&mut buf).unwrap();
730        assert_eq!(&buf, MESSAGE);
731        assert_eq!(*idx, secret_rand);
732    }
733
734    // removing a file nullifies pending events
735    #[test]
736    fn test_remove_ordering() {
737        let (read, mut write) = open_pipe().unwrap();
738        let epoll = Epoll::new().unwrap();
739        let token = epoll.add(read, Opts::IN).unwrap();
740        write.write(b"message in a bottle").unwrap();
741        epoll.remove(token).unwrap();
742        let event = epoll.wait_one_timeout(Duration::from_millis(10)).unwrap();
743        assert_eq!(event, None);
744    }
745
746    // test that different types can be added to the same epoll instance
747    #[test]
748    fn test_add_different_types() {
749        let (read, _write) = open_pipe().unwrap();
750        let localhost = std::net::Ipv4Addr::new(127, 0, 0, 1);
751        let socket = std::net::UdpSocket::bind((localhost, 23456)).unwrap();
752        let epoll = Epoll::new().unwrap();
753        let _pipe_token = epoll.add(read, Opts::IN).unwrap();
754        let _sock_token = epoll.add(socket, Opts::IN).unwrap();
755    }
756}