Skip to main content

sozu_command_lib/
ready.rs

1use std::fmt;
2
3/// Binary representation of a file descriptor readiness (obtained through epoll)
4#[derive(Copy, PartialEq, Eq, Clone, PartialOrd, Ord)]
5pub struct Ready(pub u16);
6
7impl Ready {
8    pub const EMPTY: Ready = Ready(0);
9    pub const READABLE: Ready = Ready(0b00001);
10    pub const WRITABLE: Ready = Ready(0b00010);
11    pub const ERROR: Ready = Ready(0b00100);
12    /// Hang UP (see EPOLLHUP in epoll_ctl man page). Raised for either
13    /// closed side, so a peer's half-close (`EPOLLRDHUP`) carries it too.
14    pub const HUP: Ready = Ready(0b01000);
15    /// The peer can receive nothing more: mio's `Event::is_write_closed`
16    /// (`EPOLLHUP`, or `EPOLLERR`). Unlike HUP, a half-close (`EPOLLRDHUP`
17    /// alone) never raises it. A reset whose error a `read` or `write`
18    /// already consumed is reported as `EPOLLHUP` without `EPOLLERR`, so this
19    /// bit, not ERROR, is what tells such a hang-up from a half-close.
20    pub const WRITE_CLOSED: Ready = Ready(0b10000);
21    pub const ALL: Ready = Ready(0b00011);
22
23    #[inline]
24    pub fn is_empty(&self) -> bool {
25        self.0 == 0
26    }
27
28    #[inline]
29    pub fn is_readable(&self) -> bool {
30        self.contains(Ready::READABLE)
31    }
32
33    #[inline]
34    pub fn is_writable(&self) -> bool {
35        self.contains(Ready::WRITABLE)
36    }
37
38    pub fn is_error(&self) -> bool {
39        self.contains(Ready::ERROR)
40    }
41
42    pub fn is_hup(&self) -> bool {
43        self.contains(Ready::HUP)
44    }
45
46    pub fn is_write_closed(&self) -> bool {
47        self.contains(Ready::WRITE_CLOSED)
48    }
49
50    #[inline]
51    pub fn insert<T: Into<Self>>(&mut self, other: T) {
52        let other = other.into();
53        self.0 |= other.0;
54    }
55
56    #[inline]
57    pub fn remove<T: Into<Self>>(&mut self, other: T) {
58        let other = other.into();
59        self.0 &= !other.0;
60    }
61
62    #[inline]
63    pub fn contains<T: Into<Self>>(&self, other: T) -> bool {
64        let other = other.into();
65        (*self & other) == other
66    }
67}
68
69//pub(crate) const RWINTEREST: mio::Interest = mio::Interest::READABLE | mio::Interest::WRITABLE;
70
71use std::ops;
72impl<T: Into<Ready>> ops::BitOr<T> for Ready {
73    type Output = Ready;
74
75    #[inline]
76    fn bitor(self, other: T) -> Ready {
77        Ready(self.0 | other.into().0)
78    }
79}
80
81impl<T: Into<Ready>> ops::BitOrAssign<T> for Ready {
82    #[inline]
83    fn bitor_assign(&mut self, other: T) {
84        self.0 |= other.into().0;
85    }
86}
87
88impl<T: Into<Ready>> ops::BitAnd<T> for Ready {
89    type Output = Ready;
90
91    #[inline]
92    fn bitand(self, other: T) -> Ready {
93        Ready(self.0 & other.into().0)
94    }
95}
96
97impl fmt::Debug for Ready {
98    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
99        let mut one = false;
100        let flags = [
101            (Ready::READABLE, "Readable"),
102            (Ready::WRITABLE, "Writable"),
103            (Ready::ERROR, "Error"),
104            (Ready::HUP, "Hup"),
105            (Ready::WRITE_CLOSED, "WriteClosed"),
106        ];
107
108        for &(flag, msg) in &flags {
109            if self.contains(flag) {
110                if one {
111                    write!(fmt, " | ")?
112                }
113                write!(fmt, "{msg}")?;
114
115                one = true
116            }
117        }
118
119        if !one {
120            fmt.write_str("(empty)")?;
121        }
122
123        Ok(())
124    }
125}
126
127impl std::convert::From<mio::Interest> for Ready {
128    fn from(i: mio::Interest) -> Self {
129        let mut r = Ready::EMPTY;
130        if i.is_readable() {
131            r.insert(Ready::READABLE);
132        }
133        if i.is_writable() {
134            r.insert(Ready::WRITABLE);
135        }
136
137        r
138    }
139}
140
141impl std::convert::From<&mio::event::Event> for Ready {
142    fn from(e: &mio::event::Event) -> Self {
143        let mut r = Ready::EMPTY;
144        if e.is_readable() {
145            r.insert(Ready::READABLE);
146        }
147        if e.is_writable() {
148            r.insert(Ready::WRITABLE);
149        }
150        if e.is_error() {
151            r.insert(Ready::ERROR);
152        }
153        if e.is_read_closed() || e.is_write_closed() {
154            r.insert(Ready::HUP);
155        }
156        if e.is_write_closed() {
157            r.insert(Ready::WRITE_CLOSED);
158        }
159
160        r
161    }
162}
163
164// Real-epoll probes: the readiness words they check are Linux's.
165#[cfg(all(test, target_os = "linux"))]
166mod tests {
167    use std::{
168        io::{ErrorKind, Write},
169        net::{Shutdown, TcpListener, TcpStream},
170        os::fd::AsRawFd,
171        time::Duration,
172    };
173
174    use mio::{Events, Interest, Poll, Token};
175
176    use super::Ready;
177
178    /// A connected pair: the client end, and the server end registered
179    /// edge-triggered with a fresh `Poll`, its first edge already consumed.
180    fn registered_pair() -> (TcpStream, mio::net::TcpStream, Poll, Events) {
181        let listener = TcpListener::bind("127.0.0.1:0").expect("a loopback listener binds");
182        let client =
183            TcpStream::connect(listener.local_addr().expect("the listener has an address"))
184                .expect("loopback connect completes");
185        let (server, _) = listener.accept().expect("the connection is accepted");
186        server
187            .set_nonblocking(true)
188            .expect("mio needs a nonblocking stream");
189        let mut server = mio::net::TcpStream::from_std(server);
190        let poll = Poll::new().expect("a poll opens");
191        poll.registry()
192            .register(
193                &mut server,
194                Token(1),
195                Interest::READABLE | Interest::WRITABLE,
196            )
197            .expect("the stream registers");
198        let mut events = Events::with_capacity(8);
199        let mut poll = poll;
200        poll.poll(&mut events, Some(Duration::from_millis(100)))
201            .expect("the first poll succeeds");
202        (client, server, poll, events)
203    }
204
205    /// Reset the connection: `SO_LINGER` zero, then close.
206    fn reset(client: TcpStream) {
207        let linger = libc::linger {
208            l_onoff: 1,
209            l_linger: 0,
210        };
211        // SAFETY: `linger` is a valid `libc::linger` and the descriptor is
212        // owned by `client`, alive for the call.
213        let rc = unsafe {
214            libc::setsockopt(
215                client.as_raw_fd(),
216                libc::SOL_SOCKET,
217                libc::SO_LINGER,
218                &linger as *const _ as *const libc::c_void,
219                std::mem::size_of::<libc::linger>() as libc::socklen_t,
220            )
221        };
222        assert_eq!(rc, 0, "SO_LINGER zero must set");
223        drop(client);
224    }
225
226    fn next_ready(poll: &mut Poll, events: &mut Events) -> Ready {
227        poll.poll(events, Some(Duration::from_secs(2)))
228            .expect("poll succeeds");
229        let event = events
230            .iter()
231            .find(|event| event.token() == Token(1))
232            .expect("the server end gets an event");
233        Ready::from(event)
234    }
235
236    /// A half-close raises HUP, never WRITE_CLOSED: the peer still reads.
237    #[test]
238    fn a_half_close_is_hup_but_not_write_closed() {
239        let (client, _server, mut poll, mut events) = registered_pair();
240        client
241            .shutdown(Shutdown::Write)
242            .expect("the client half-closes");
243        let ready = next_ready(&mut poll, &mut events);
244        assert!(ready.is_hup(), "a half-close raises HUP: {ready:?}");
245        assert!(
246            !ready.is_write_closed(),
247            "a half-close is not a hang-up: {ready:?}"
248        );
249        assert!(!ready.is_error(), "a half-close is not an error: {ready:?}");
250    }
251
252    /// A reset nothing has read yet is ERROR and WRITE_CLOSED.
253    #[test]
254    fn a_reset_is_error_and_write_closed() {
255        let (client, _server, mut poll, mut events) = registered_pair();
256        reset(client);
257        let ready = next_ready(&mut poll, &mut events);
258        assert!(ready.is_error(), "an unread reset is an error: {ready:?}");
259        assert!(ready.is_write_closed(), "a reset is a hang-up: {ready:?}");
260    }
261
262    /// A reset whose error a `write` consumed first is reported as `EPOLLHUP`
263    /// without `EPOLLERR`: HUP and WRITE_CLOSED, but not ERROR. ERROR alone
264    /// therefore cannot tell this hang-up from a half-close; WRITE_CLOSED can.
265    ///
266    /// TO SEE THIS RED: drop the WRITE_CLOSED insertion from
267    /// `Ready::from(&mio::event::Event)`.
268    #[test]
269    fn a_reset_consumed_by_a_write_is_write_closed_without_error() {
270        let (client, mut server, mut poll, mut events) = registered_pair();
271        reset(client);
272        let mut consumed = None;
273        for _ in 0..200 {
274            match server.write_all(b"x") {
275                Ok(_) => std::thread::sleep(Duration::from_millis(5)),
276                Err(e) => {
277                    consumed = Some(e.kind());
278                    break;
279                }
280            }
281        }
282        assert!(
283            matches!(
284                consumed,
285                Some(ErrorKind::ConnectionReset) | Some(ErrorKind::BrokenPipe)
286            ),
287            "premise: a write consumes the reset, got {consumed:?}"
288        );
289        let ready = next_ready(&mut poll, &mut events);
290        assert!(ready.is_hup(), "the hang-up raises HUP: {ready:?}");
291        assert!(
292            ready.is_write_closed(),
293            "the hang-up must be told from a half-close: {ready:?}"
294        );
295        assert!(
296            !ready.is_error(),
297            "premise: the write consumed the error, so EPOLLERR is gone: {ready:?}"
298        );
299    }
300}