poll.rs (5564B)
1 //! This contains the main abstraction, which is a thin layer over `epoll`. 2 //! There are two main abstractions over epoll. One is a structure called 3 //! `Poll` and the other is called `Registry` 4 5 use crate::ffi; 6 /// Its convenient to use `io::Result` type since most errors will stem from one 7 /// of our calls into the operating system, and an operating system error can 8 /// be mapped to an `io::Error` type 9 use std::{ 10 io::{self, Result}, 11 net::TcpStream, 12 os::fd::AsRawFd, 13 }; 14 15 type Events = Vec<ffi::Event>; 16 17 /// Poll is a struct that represents the event queue itself. 18 pub struct Poll { 19 registry: Registry, 20 } 21 22 impl Poll { 23 /// Creates a new event queue 24 pub fn new() -> Result<Self> { 25 let res = unsafe { ffi::epoll_create(1) }; 26 if res < 0 { 27 return Err(io::Error::last_os_error()); 28 } 29 30 Ok(Self { 31 registry: Registry { raw_fd: res }, 32 }) 33 } 34 35 /// Returns a reference to the registry that can be used to register 36 /// interest to be notified about new events 37 pub fn registry(&self) -> &Registry { 38 &self.registry 39 } 40 41 /// Blocks the thread it's called on until an event is ready or it times 42 /// out, whichever occurs first 43 pub fn poll(&mut self, events: &mut Events, timeout: Option<i32>) -> Result<()> { 44 // The first thing we do is to get the raw file descriptor for 45 // the event queue and store it in the fd variable. 46 let fd = self.registry.raw_fd; 47 48 // Next is our timeout. If it’s Some, we unwrap that value, 49 // and if it’s None, we set it to –1, which is the value 50 // that tells the operating system that we want to block 51 // until an event occurs even though that might never happen. 52 let timeout = timeout.unwrap_or(-1); 53 54 // At the top of the file, we defined Events as a type alias 55 // for Vec<ffi::Event>, so the next thing we do is to get 56 // the capacity of that Vec. It’s important that we don’t 57 // rely on Vec::len since that reports how many items we have 58 // in the Vec. Vec::capacity reports the space we’ve allocated 59 // and that’s what we’re after. 60 let max_events = events.capacity() as i32; 61 62 // Next up is the call to ffi::epoll_wait. This call will return 63 // successfully if it has a value of 0 or larger, telling us 64 // how many events have occurred. 65 let res = unsafe { ffi::epoll_wait(fd, events.as_mut_ptr(), max_events, timeout) }; 66 67 // Note: We would get a value of 0 if a timeout elapses before 68 // an event has happened. 69 if res < 0 { 70 return Err(io::Error::last_os_error()); 71 }; 72 73 // The last thing we do is to make an unsafe call to 74 // events.set_len(res as usize). This function is unsafe since 75 // we could potentially set the length so that we would access 76 // memory that’s not been initialized yet in safe Rust. 77 // We know from the guarantee the operating system gives us 78 // that the number of events it returns is pointing to valid 79 // data in our Vec, so this is safe in our case. 80 unsafe { 81 events.set_len(res as usize); 82 }; 83 Ok(()) 84 } 85 } 86 87 /// While `Poll` represents the event queue, `Registry` is a handle that allows 88 /// us to register interest in new events 89 pub struct Registry { 90 raw_fd: i32, 91 } 92 93 impl Registry { 94 /// `Registry` will only have one method: `register` 95 /// https://docs.rs/mio/0.8.8/mio/struct.Registry.html 96 /// The `interests` argument will indicate what kind of events we want our 97 /// event queue to keep track of. 98 /// 99 /// The register function takes a &TcpStream as a source, 100 /// a token of type usize, and a bitmask named interests, 101 /// which is of type i32. 102 pub fn register(&self, source: &TcpStream, token: usize, interests: i32) -> Result<()> { 103 // The first thing we do is to create an ffi::Event object. 104 // The events field is simply set to the bitmask we received 105 // and named interests, and epoll_data is set to the value 106 // we passed in the token argument. 107 let mut event = ffi::Event { 108 events: interests as u32, 109 epoll_data: token, 110 }; 111 112 // The operation we want to perform on the epoll queue is 113 // adding interest in events on a new file descriptor. 114 // Therefore, we set the op argument to the ffi::EPOLL_CTL_ADD 115 // constant value. 116 let op = ffi::EPOLL_CTL_ADD; 117 118 // Next up is the call to ffi::epoll_ctl. We pass in the file 119 // descriptor to the epoll instance first, then we pass in 120 // the op argument to indicate what kind of operation we want 121 // to perform. The last two arguments are the file descriptor 122 // we want the queue to track and the Event object we created 123 // to indicate what kind of events we’re interested in getting 124 // notifications for. 125 let res = unsafe { ffi::epoll_ctl(self.raw_fd, op, source.as_raw_fd(), &mut event) }; 126 127 if res < 0 { 128 return Err(io::Error::last_os_error()); 129 } 130 Ok(()) 131 } 132 } 133 134 impl Drop for Registry { 135 fn drop(&mut self) { 136 // The Drop implementation simply calls ffi::close on the epoll 137 // file descriptor. 138 let res = unsafe { ffi::close(self.raw_fd) }; 139 if res < 0 { 140 let err = io::Error::last_os_error(); 141 eprintln!("ERROR: {err:?}"); 142 } 143 } 144 }