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}