1 use std::fs::File;
2 use std::io::{self, Read, Write};
3 #[cfg(not(target_os = "hermit"))]
4 use std::os::fd::{AsRawFd, FromRawFd, RawFd};
5 // TODO: once <https://github.com/rust-lang/rust/issues/126198> is fixed this
6 // can use `std::os::fd` and be merged with the above.
7 #[cfg(target_os = "hermit")]
8 use std::os::hermit::io::{AsRawFd, FromRawFd, RawFd};
9 
10 use crate::sys::Selector;
11 use crate::{Interest, Token};
12 
13 /// Waker backed by `eventfd`.
14 ///
15 /// `eventfd` is effectively an 64 bit counter. All writes must be of 8
16 /// bytes (64 bits) and are converted (native endian) into an 64 bit
17 /// unsigned integer and added to the count. Reads must also be 8 bytes and
18 /// reset the count to 0, returning the count.
19 #[derive(Debug)]
20 pub(crate) struct Waker {
21     fd: File,
22 }
23 
24 impl Waker {
25     #[allow(dead_code)] // Not used by the `poll(2)` implementation.
new(selector: &Selector, token: Token) -> io::Result<Waker>26     pub(crate) fn new(selector: &Selector, token: Token) -> io::Result<Waker> {
27         let waker = Waker::new_unregistered()?;
28         selector.register(waker.fd.as_raw_fd(), token, Interest::READABLE)?;
29         Ok(waker)
30     }
31 
new_unregistered() -> io::Result<Waker>32     pub(crate) fn new_unregistered() -> io::Result<Waker> {
33         #[cfg(not(target_os = "espidf"))]
34         let flags = libc::EFD_CLOEXEC | libc::EFD_NONBLOCK;
35         // ESP-IDF is EFD_NONBLOCK by default and errors if you try to pass this flag.
36         #[cfg(target_os = "espidf")]
37         let flags = 0;
38         let fd = syscall!(eventfd(0, flags))?;
39         let file = unsafe { File::from_raw_fd(fd) };
40         Ok(Waker { fd: file })
41     }
42 
43     #[allow(clippy::unused_io_amount)] // Don't care about partial writes.
wake(&self) -> io::Result<()>44     pub(crate) fn wake(&self) -> io::Result<()> {
45         // The epoll emulation on some illumos systems currently requires
46         // the eventfd to be read before an edge-triggered read event is
47         // generated.
48         // See https://www.illumos.org/issues/16700.
49         #[cfg(target_os = "illumos")]
50         self.reset()?;
51 
52         let buf: [u8; 8] = 1u64.to_ne_bytes();
53         match (&self.fd).write(&buf) {
54             Ok(_) => Ok(()),
55             Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => {
56                 // Writing only blocks if the counter is going to overflow.
57                 // So we'll reset the counter to 0 and wake it again.
58                 self.reset()?;
59                 self.wake()
60             }
61             Err(err) => Err(err),
62         }
63     }
64 
65     #[allow(dead_code)] // Only used by the `poll(2)` implementation.
ack_and_reset(&self)66     pub(crate) fn ack_and_reset(&self) {
67         let _ = self.reset();
68     }
69 
70     /// Reset the eventfd object, only need to call this if `wake` fails.
71     #[allow(clippy::unused_io_amount)] // Don't care about partial reads.
reset(&self) -> io::Result<()>72     fn reset(&self) -> io::Result<()> {
73         let mut buf: [u8; 8] = 0u64.to_ne_bytes();
74         match (&self.fd).read(&mut buf) {
75             Ok(_) => Ok(()),
76             // If the `Waker` hasn't been awoken yet this will return a
77             // `WouldBlock` error which we can safely ignore.
78             Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => Ok(()),
79             Err(err) => Err(err),
80         }
81     }
82 }
83 
84 impl AsRawFd for Waker {
as_raw_fd(&self) -> RawFd85     fn as_raw_fd(&self) -> RawFd {
86         self.fd.as_raw_fd()
87     }
88 }
89