Skip to main content

majordomus_cli/http/
deadline.rs

1//! The read deadline: how long a connection may hold this server without a request read in
2//! full on it (I2156).
3//!
4//! `tiny_http` reads every request head on a thread of its own pool and never times a read
5//! out, and the body is read by the thread answering the request. A client that opens a
6//! connection and sends one byte a second therefore holds a thread forever; enough of them
7//! hold every request handler, and past [`super::server::MAX_HANDLERS`] the probe a client
8//! sends before taking the lease over waits behind them. `tiny_http` never hands its socket
9//! out, so the deadline is kept from beside it: a sweeper lists this process's descriptors,
10//! keeps the ones connected to the served address, and shuts down every connection that has
11//! been waiting for a request, or reading one, for longer than the deadline. A connection
12//! whose request is being answered is never closed: handler time is bounded by moving it off
13//! the workers, not by cutting a tool call short.
14//!
15//! The deadline also bounds a wait it does not cause. `tiny_http` keeps a pool thread on each
16//! connection for as long as it lives, and a connection that arrives in a burst can be queued
17//! in that pool until one of its threads is free; behind slow clients, that was never. Now it
18//! is at most the deadline, when a slow client's thread is released.
19//!
20//! Unix only: elsewhere the descriptors cannot be listed, nothing is closed, and the
21//! counters stay at zero.
22//!
23//! The lifecycle, from a process's side: nothing is reported until a server starts, and
24//! from then on the counters only grow.
25//!
26//! ```no_run
27//! use majordomus_cli::http::deadline;
28//! use majordomus_cli::http::{server, Router};
29//! # fn router() -> Router { unimplemented!() }
30//! assert_eq!(deadline::counts(), None, "no server, nothing to report");
31//! let running = server::bind("127.0.0.1", 0).unwrap().start(router());
32//! // the deadline the server holds connections to is the declared one
33//! let counts = deadline::counts().expect("a serving process reports its counts");
34//! assert_eq!(
35//!     counts.read_deadline_seconds,
36//!     majordomus_cli::lease::timings().read_deadline.as_secs()
37//! );
38//! running.stop();
39//! ```
40
41use std::collections::{HashMap, HashSet};
42use std::net::SocketAddr;
43use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
44use std::sync::Mutex;
45use std::time::{Duration, Instant};
46
47/// Connections this process closed because they missed the read deadline.
48static CLOSED: AtomicU64 = AtomicU64::new(0);
49
50/// Requests this process refused because [`super::server::MAX_HANDLERS`] were being answered.
51static REFUSED: AtomicU64 = AtomicU64::new(0);
52
53/// The deadline in force, in seconds; zero until a server has started in this process.
54static DEADLINE_SECONDS: AtomicU64 = AtomicU64::new(0);
55
56/// What this process's HTTP server has done to clients that held a connection without using
57/// it: the deadline it applies and what that deadline cost, since the process started.
58/// Absent while this process serves no HTTP.
59///
60/// ```
61/// use majordomus_cli::http::deadline::{counts, ConnectionCounts};
62/// // a process that has started no server reports nothing, rather than zeroes that would
63/// // read as "no slow client was seen"
64/// assert_eq!(counts(), None);
65/// let c = ConnectionCounts { read_deadline_seconds: 30, closed_by_deadline: 4, refused_busy: 0 };
66/// let text = serde_json::to_string(&c).unwrap();
67/// assert_eq!(serde_json::from_str::<ConnectionCounts>(&text).unwrap(), c);
68/// ```
69#[derive(
70    Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
71)]
72pub struct ConnectionCounts {
73    /// `server.read_deadline_seconds:` as this process applies it.
74    pub read_deadline_seconds: u64,
75    /// Connections closed because no request was read in full on them within the deadline.
76    pub closed_by_deadline: u64,
77    /// Requests answered 503 because every request handler was busy.
78    pub refused_busy: u64,
79}
80
81/// The counters of this process's HTTP server, or nothing when it serves none.
82///
83/// ```
84/// use majordomus_cli::http::deadline::counts;
85/// // asked twice, the same answer: reading the counters does not move them
86/// assert_eq!(counts(), counts());
87/// ```
88pub fn counts() -> Option<ConnectionCounts> {
89    let seconds = DEADLINE_SECONDS.load(Ordering::SeqCst);
90    (seconds > 0).then(|| ConnectionCounts {
91        read_deadline_seconds: seconds,
92        closed_by_deadline: CLOSED.load(Ordering::SeqCst),
93        refused_busy: REFUSED.load(Ordering::SeqCst),
94    })
95}
96
97/// Count one request refused past the handler bound.
98pub(crate) fn refused() {
99    REFUSED.fetch_add(1, Ordering::SeqCst);
100}
101
102/// Where one connection stands.
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104enum Phase {
105    /// Open with no request in progress since this instant: being read by `tiny_http`, or
106    /// kept alive between requests.
107    Waiting(Instant),
108    /// Its request head arrived and its body is being read since this instant.
109    Reading(Instant),
110    /// Its request is being answered: never closed.
111    Busy,
112    /// Upgraded to a live channel, which waits by design: never closed.
113    Live,
114    /// Shut down by the sweeper; the request it was reading is abandoned.
115    Closed,
116}
117
118/// The connections of one served socket, and the deadline they are held to.
119pub(crate) struct Watch {
120    local: SocketAddr,
121    deadline: Duration,
122    peers: Mutex<HashMap<SocketAddr, Phase>>,
123}
124
125impl Watch {
126    /// Watch the connections accepted on `local`, closing those that miss `deadline`.
127    pub(crate) fn new(local: SocketAddr, deadline: Duration) -> Self {
128        DEADLINE_SECONDS.store(deadline.as_secs().max(1), Ordering::SeqCst);
129        Self {
130            local,
131            deadline,
132            peers: Mutex::new(HashMap::new()),
133        }
134    }
135
136    fn peers(&self) -> std::sync::MutexGuard<'_, HashMap<SocketAddr, Phase>> {
137        self.peers.lock().unwrap_or_else(|e| e.into_inner())
138    }
139
140    /// A request head arrived from `peer`: its body is being read from now on.
141    pub(crate) fn reading(&self, peer: Option<&SocketAddr>) {
142        if let Some(peer) = peer {
143            // a connection the sweeper closed stays closed: its body can only come short
144            let mut peers = self.peers();
145            let phase = peers.entry(*peer).or_insert(Phase::Busy);
146            if *phase != Phase::Closed {
147                *phase = Phase::Reading(Instant::now());
148            }
149        }
150    }
151
152    /// The request of `peer` was read in full: it is answered from now on, and never closed
153    /// under its handler. False when the sweeper closed the connection while the body was
154    /// being read, so that a truncated body is never answered.
155    pub(crate) fn busy(&self, peer: Option<&SocketAddr>) -> bool {
156        let Some(peer) = peer else { return true };
157        let mut peers = self.peers();
158        match peers.get_mut(peer) {
159            Some(Phase::Closed) => false,
160            Some(phase) => {
161                *phase = Phase::Busy;
162                true
163            }
164            None => {
165                peers.insert(*peer, Phase::Busy);
166                true
167            }
168        }
169    }
170
171    /// `peer` was upgraded to a live channel: it is never held to the deadline again.
172    pub(crate) fn live(&self, peer: Option<&SocketAddr>) {
173        if let Some(peer) = peer {
174            self.peers().insert(*peer, Phase::Live);
175        }
176    }
177
178    /// Whether the client of `peer`'s live channel has hung up, asked of the connection's
179    /// socket rather than learned from a failed write: the channel writes only on an event or
180    /// a heartbeat, and until it learned, the idle timer counted a closed channel as open
181    /// (I2131). The answer is a closure the channel's own thread asks on each quiet interval.
182    ///
183    /// Called before the socket is handed to the channel, while its client is certainly
184    /// there, because that is when the socket can be found; the channel holds the socket
185    /// open for as long as it asks. An unknown peer, or a platform where the socket cannot
186    /// be found, never hangs up: the channel then learns at its next write, as before.
187    pub(crate) fn hung_up(&self, peer: Option<&SocketAddr>) -> impl Fn() -> bool + Send + 'static {
188        let descriptor = peer.and_then(|peer| sockets::descriptor(self.local, *peer));
189        move || descriptor.is_some_and(sockets::hung_up)
190    }
191
192    /// The request of `peer` was answered: the connection waits for the next from now on.
193    pub(crate) fn answered(&self, peer: Option<&SocketAddr>) {
194        if let Some(peer) = peer {
195            if let Some(phase) = self.peers().get_mut(peer) {
196                if *phase == Phase::Busy {
197                    *phase = Phase::Waiting(Instant::now());
198                }
199            }
200        }
201    }
202
203    /// Stop reading from `peer` before a request of it is answered without its body: a 503
204    /// past the handler bound, a 413 over the body bound. `tiny_http` reads whatever body is
205    /// left when the request is dropped, so a refusal of a slow body would otherwise hold the
206    /// thread that refused it for as long as the client trickles — a worker, for the 503.
207    /// The response is still written; the connection ends with it.
208    pub(crate) fn stop_reading(&self, peer: Option<&SocketAddr>) {
209        if let Some(peer) = peer {
210            sockets::shut_reading(self.local, *peer);
211        }
212    }
213
214    /// How often the sweeper looks: a quarter of the deadline, within 100 ms and 1 s.
215    pub(crate) fn interval(&self) -> Duration {
216        (self.deadline / 4).clamp(Duration::from_millis(100), Duration::from_secs(1))
217    }
218
219    /// Run the sweeper until `stopping` is set.
220    pub(crate) fn run(&self, stopping: &AtomicBool) {
221        let step = Duration::from_millis(50);
222        let mut next = Instant::now() + self.interval();
223        while !stopping.load(Ordering::SeqCst) {
224            std::thread::sleep(step);
225            if Instant::now() >= next {
226                self.sweep(Instant::now());
227                next = Instant::now() + self.interval();
228            }
229        }
230    }
231
232    /// Close every connection of the served socket that has waited for, or been reading, a
233    /// request for longer than the deadline, and forget the connections that are gone.
234    fn sweep(&self, now: Instant) {
235        self.sweep_with(now, sockets::connected_to(self.local), sockets::shut_down);
236    }
237
238    /// [`Watch::sweep`] over the connections `open`, closing with `close`: the system's own in
239    /// earnest, a test's in a test.
240    fn sweep_with(
241        &self,
242        now: Instant,
243        open: Vec<(i32, SocketAddr)>,
244        close: impl Fn(i32, SocketAddr, SocketAddr) -> bool,
245    ) {
246        let mut peers = self.peers();
247        let mut seen = HashSet::new();
248        for (descriptor, peer) in open {
249            // `tiny_http` holds a reading and a writing descriptor for one connection; one
250            // shutdown closes both
251            if !seen.insert(peer) {
252                continue;
253            }
254            let phase = peers.entry(peer).or_insert(Phase::Waiting(now));
255            let since = match *phase {
256                Phase::Waiting(since) | Phase::Reading(since) => since,
257                Phase::Busy | Phase::Live | Phase::Closed => continue,
258            };
259            if now.saturating_duration_since(since) < self.deadline {
260                continue;
261            }
262            if close(descriptor, self.local, peer) {
263                *phase = Phase::Closed;
264                CLOSED.fetch_add(1, Ordering::SeqCst);
265                tracing::info!(
266                    peer = %peer,
267                    deadline_seconds = self.deadline.as_secs(),
268                    "closed a connection that read no request within the deadline"
269                );
270            }
271        }
272        peers.retain(|peer, _| seen.contains(peer));
273    }
274}
275
276#[cfg(unix)]
277mod sockets {
278    use std::mem::ManuallyDrop;
279    use std::net::{Shutdown, SocketAddr, TcpStream};
280    use std::os::fd::{FromRawFd, RawFd};
281
282    /// This process's open descriptors, from the directory the system lists them in.
283    fn descriptors() -> Vec<RawFd> {
284        ["/dev/fd", "/proc/self/fd"]
285            .iter()
286            .find_map(|dir| std::fs::read_dir(dir).ok())
287            .map(|entries| {
288                entries
289                    .filter_map(Result::ok)
290                    .filter_map(|e| descriptor_of(&e.file_name()))
291                    .collect()
292            })
293            .unwrap_or_default()
294    }
295
296    /// The descriptor a directory entry of the system's listing names: only a number does.
297    pub(super) fn descriptor_of(name: &std::ffi::OsStr) -> Option<RawFd> {
298        name.to_str()?.parse().ok()
299    }
300
301    /// The addresses of `descriptor` when it is a connected TCP socket.
302    fn addresses(descriptor: RawFd) -> Option<(SocketAddr, SocketAddr)> {
303        // SAFETY: the descriptor is only asked for its addresses and never closed:
304        // ManuallyDrop keeps this `TcpStream` from owning it. One that is not a socket, or
305        // that was closed since it was listed, answers with an error.
306        let stream = ManuallyDrop::new(unsafe { TcpStream::from_raw_fd(descriptor) });
307        Some((stream.local_addr().ok()?, stream.peer_addr().ok()?))
308    }
309
310    fn served(local: SocketAddr, mine: SocketAddr) -> bool {
311        mine.port() == local.port() && (local.ip().is_unspecified() || mine.ip() == local.ip())
312    }
313
314    /// Every descriptor connected to a peer through the served address `local`.
315    pub(super) fn connected_to(local: SocketAddr) -> Vec<(RawFd, SocketAddr)> {
316        descriptors()
317            .into_iter()
318            .filter_map(|d| {
319                let (mine, peer) = addresses(d)?;
320                served(local, mine).then_some((d, peer))
321            })
322            .collect()
323    }
324
325    /// Stop reading the connection from `peer`, leaving its writing half open.
326    pub(super) fn shut_reading(local: SocketAddr, peer: SocketAddr) {
327        for (descriptor, theirs) in connected_to(local) {
328            if theirs == peer {
329                // SAFETY: as in `addresses`
330                let stream = ManuallyDrop::new(unsafe { TcpStream::from_raw_fd(descriptor) });
331                let _ = stream.shutdown(Shutdown::Read);
332                return;
333            }
334        }
335    }
336
337    /// The descriptor of the connection from `peer` to the served address `local`, while it
338    /// is connected. Found once, while the client is there: a socket its client has reset
339    /// no longer names its peer (macOS answers `getpeername` with an error), so a later
340    /// search by address finds nothing exactly when there is something to find.
341    pub(super) fn descriptor(local: SocketAddr, peer: SocketAddr) -> Option<RawFd> {
342        connected_to(local)
343            .into_iter()
344            .find_map(|(descriptor, theirs)| (theirs == peer).then_some(descriptor))
345    }
346
347    /// Has the client at the other end of `descriptor` hung up? Whatever it sent is read and
348    /// dropped — the live channel never reads, and ignores what a client sends
349    /// (`super::super::ws`) — until the socket says it has nothing more: an end of stream or
350    /// a reset is a client that has gone, an empty socket is one that is still there. Never
351    /// blocks: every read is `MSG_DONTWAIT`, on a descriptor whose own flags are left alone,
352    /// because the channel's thread writes through the same socket.
353    ///
354    /// The caller holds the socket `descriptor` belongs to open for as long as it asks, so
355    /// the number cannot have been closed and reused in between.
356    pub(super) fn hung_up(descriptor: RawFd) -> bool {
357        let mut buffer = [0u8; 4096];
358        // a bound, so that a client streaming bytes at the server cannot hold this thread
359        for _ in 0..64 {
360            // SAFETY: a read into a buffer this function owns, of at most its length, on a
361            // descriptor that is only read and never closed here
362            let read = unsafe {
363                libc::recv(
364                    descriptor,
365                    buffer.as_mut_ptr().cast(),
366                    buffer.len(),
367                    libc::MSG_DONTWAIT,
368                )
369            };
370            match read {
371                0 => return true,
372                n if n > 0 => continue,
373                _ => {
374                    let error = std::io::Error::last_os_error();
375                    return !matches!(
376                        error.kind(),
377                        std::io::ErrorKind::WouldBlock | std::io::ErrorKind::Interrupted
378                    );
379                }
380            }
381        }
382        false
383    }
384
385    /// Shut the connection down, after asking again that the descriptor is still the one
386    /// it was: a descriptor closed and reused since the listing is left alone.
387    pub(super) fn shut_down(descriptor: RawFd, local: SocketAddr, peer: SocketAddr) -> bool {
388        match addresses(descriptor) {
389            Some((mine, now)) if served(local, mine) && now == peer => {
390                // SAFETY: as in `addresses`; shutdown ends the connection without closing
391                // the descriptor its owner will close
392                let stream = ManuallyDrop::new(unsafe { TcpStream::from_raw_fd(descriptor) });
393                stream.shutdown(Shutdown::Both).is_ok()
394            }
395            _ => false,
396        }
397    }
398}
399
400#[cfg(not(unix))]
401mod sockets {
402    use std::net::SocketAddr;
403
404    pub(super) fn connected_to(_: SocketAddr) -> Vec<(i32, SocketAddr)> {
405        Vec::new()
406    }
407
408    pub(super) fn shut_down(_: i32, _: SocketAddr, _: SocketAddr) -> bool {
409        false
410    }
411
412    pub(super) fn shut_reading(_: SocketAddr, _: SocketAddr) {}
413
414    pub(super) fn descriptor(_: SocketAddr, _: SocketAddr) -> Option<i32> {
415        None
416    }
417
418    pub(super) fn hung_up(_: i32) -> bool {
419        false
420    }
421}
422
423#[cfg(test)]
424mod tests {
425    use super::*;
426
427    fn watch() -> Watch {
428        Watch {
429            local: "127.0.0.1:1".parse().unwrap(),
430            deadline: Duration::from_secs(2),
431            peers: Mutex::new(HashMap::new()),
432        }
433    }
434
435    #[test]
436    fn a_body_cut_short_by_the_sweeper_is_never_answered() {
437        let w = watch();
438        let peer: SocketAddr = "127.0.0.1:5000".parse().unwrap();
439        w.reading(Some(&peer));
440        w.peers().insert(peer, Phase::Closed);
441        assert!(!w.busy(Some(&peer)));
442        // nor does a head that arrived after the close reopen it
443        w.reading(Some(&peer));
444        assert_eq!(w.peers()[&peer], Phase::Closed);
445    }
446
447    #[test]
448    fn an_answered_request_starts_the_wait_for_the_next() {
449        let w = watch();
450        let peer: SocketAddr = "127.0.0.1:5001".parse().unwrap();
451        w.reading(Some(&peer));
452        assert!(w.busy(Some(&peer)));
453        assert_eq!(w.peers()[&peer], Phase::Busy);
454        w.answered(Some(&peer));
455        assert!(matches!(w.peers()[&peer], Phase::Waiting(_)));
456    }
457
458    #[test]
459    fn a_live_channel_outlives_the_answer_that_opened_it() {
460        let w = watch();
461        let peer: SocketAddr = "127.0.0.1:5002".parse().unwrap();
462        w.reading(Some(&peer));
463        w.live(Some(&peer));
464        w.answered(Some(&peer));
465        assert_eq!(w.peers()[&peer], Phase::Live);
466    }
467
468    #[test]
469    fn the_sweeper_looks_often_enough_to_keep_the_deadline() {
470        let mut w = watch();
471        assert_eq!(w.interval(), Duration::from_millis(500));
472        w.deadline = Duration::from_secs(60);
473        assert_eq!(w.interval(), Duration::from_secs(1));
474    }
475
476    #[test]
477    fn a_request_whose_peer_is_unknown_is_answered_and_never_tracked() {
478        let w = watch();
479        // `tiny_http` names no remote address for some connections: nothing is held to the
480        // deadline for them, and their request is never refused as cut short
481        w.reading(None);
482        assert!(w.busy(None));
483        w.live(None);
484        w.answered(None);
485        w.stop_reading(None);
486        assert!(w.peers().is_empty());
487    }
488
489    #[test]
490    fn a_request_read_before_its_head_was_seen_is_busy_from_then_on() {
491        let w = watch();
492        let peer: SocketAddr = "127.0.0.1:5003".parse().unwrap();
493        // the sweeper never listed it and no head was noted: it is answered all the same
494        assert!(w.busy(Some(&peer)));
495        assert_eq!(w.peers()[&peer], Phase::Busy);
496        // an answer for a connection that is not tracked starts nothing
497        let other: SocketAddr = "127.0.0.1:5004".parse().unwrap();
498        w.answered(Some(&other));
499        assert!(!w.peers().contains_key(&other));
500    }
501
502    #[test]
503    fn a_watch_poisoned_by_a_panic_still_tracks_its_connections() {
504        let w = std::sync::Arc::new(watch());
505        let held = std::sync::Arc::clone(&w);
506        let _ = std::thread::spawn(move || {
507            let _guard = held.peers.lock().unwrap();
508            panic!("a handler panicked while it held the watch");
509        })
510        .join();
511        assert!(w.peers.is_poisoned());
512        let peer: SocketAddr = "127.0.0.1:5005".parse().unwrap();
513        w.reading(Some(&peer));
514        assert!(matches!(w.peers()[&peer], Phase::Reading(_)));
515        assert!(w.busy(Some(&peer)));
516    }
517
518    /// Everything a subscriber writes, for a test to read back.
519    #[derive(Clone, Default)]
520    struct Captured(std::sync::Arc<Mutex<Vec<u8>>>);
521
522    impl std::io::Write for Captured {
523        fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
524            self.0
525                .lock()
526                .unwrap_or_else(|e| e.into_inner())
527                .extend_from_slice(bytes);
528            Ok(bytes.len())
529        }
530
531        fn flush(&mut self) -> std::io::Result<()> {
532            Ok(())
533        }
534    }
535
536    impl Captured {
537        fn text(&self) -> String {
538            String::from_utf8_lossy(&self.0.lock().unwrap_or_else(|e| e.into_inner())).into_owned()
539        }
540    }
541
542    #[cfg(unix)]
543    #[test]
544    fn a_connection_idle_past_the_deadline_is_closed_counted_and_logged() {
545        use std::io::Read;
546        use std::net::{TcpListener, TcpStream};
547
548        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
549        let local = listener.local_addr().unwrap();
550        let w = Watch::new(local, Duration::from_secs(2));
551        let mut client = TcpStream::connect(local).unwrap();
552        let (accepted, peer) = listener.accept().unwrap();
553        // `tiny_http` holds two descriptors on one connection; one shutdown closes both
554        let twin = accepted.try_clone().unwrap();
555
556        let start = Instant::now();
557        w.sweep(start);
558        assert_eq!(
559            w.peers()[&peer],
560            Phase::Waiting(start),
561            "a connection first seen by the sweeper waits from then on"
562        );
563        w.sweep(start + Duration::from_secs(1));
564        assert_eq!(
565            w.peers()[&peer],
566            Phase::Waiting(start),
567            "within the deadline"
568        );
569
570        let before = CLOSED.load(Ordering::SeqCst);
571        let captured = Captured::default();
572        let writer = captured.clone();
573        let subscriber = tracing_subscriber::fmt()
574            .with_max_level(tracing::Level::INFO)
575            .with_writer(move || writer.clone())
576            .finish();
577        tracing::subscriber::with_default(subscriber, || {
578            w.sweep(start + Duration::from_secs(3));
579        });
580        assert_eq!(w.peers()[&peer], Phase::Closed, "past the deadline");
581        assert!(
582            CLOSED.load(Ordering::SeqCst) > before,
583            "the close is counted"
584        );
585        let log = captured.text();
586        assert!(
587            log.contains("closed a connection that read no request within the deadline"),
588            "{log}"
589        );
590        assert!(log.contains("deadline_seconds=2"), "{log}");
591        assert!(log.contains(&peer.to_string()), "the peer is named: {log}");
592
593        // the client sees its connection end
594        client
595            .set_read_timeout(Some(Duration::from_secs(30)))
596            .unwrap();
597        let mut buffer = [0u8; 16];
598        assert_eq!(client.read(&mut buffer).unwrap_or(0), 0);
599
600        // a connection that is gone is forgotten
601        drop(client);
602        drop(accepted);
603        drop(twin);
604        w.sweep(start + Duration::from_secs(4));
605        assert!(!w.peers().contains_key(&peer));
606    }
607
608    #[test]
609    fn a_connection_the_system_will_not_close_stays_as_it_was_and_a_gone_one_is_forgotten() {
610        let w = watch();
611        let peer: SocketAddr = "127.0.0.1:40000".parse().unwrap();
612        let start = Instant::now();
613        let asked = std::cell::Cell::new(0);
614        let refuse = |descriptor: i32, _: SocketAddr, at: SocketAddr| {
615            assert_eq!((descriptor, at), (7, peer));
616            asked.set(asked.get() + 1);
617            false
618        };
619        // two descriptors of one connection are one connection
620        w.sweep_with(start, vec![(7, peer), (8, peer)], refuse);
621        assert_eq!(asked.get(), 0, "within the deadline nothing is closed");
622        w.sweep_with(start + Duration::from_secs(3), vec![(7, peer)], refuse);
623        assert_eq!(asked.get(), 1, "past the deadline the system is asked once");
624        assert_eq!(
625            w.peers()[&peer],
626            Phase::Waiting(start),
627            "a connection the system did not close is not called closed"
628        );
629        w.sweep_with(start + Duration::from_secs(4), vec![], |_, _, _| {
630            unreachable!("nothing is open")
631        });
632        assert!(
633            w.peers().is_empty(),
634            "a connection that is gone is forgotten"
635        );
636    }
637
638    #[cfg(unix)]
639    #[test]
640    fn only_a_number_names_a_descriptor() {
641        use std::ffi::OsStr;
642        use std::os::unix::ffi::OsStrExt;
643        assert_eq!(sockets::descriptor_of(OsStr::new("12")), Some(12));
644        assert_eq!(sockets::descriptor_of(OsStr::new("cwd")), None);
645        assert_eq!(sockets::descriptor_of(OsStr::from_bytes(b"\xff")), None);
646    }
647
648    #[cfg(unix)]
649    #[test]
650    fn a_descriptor_that_is_not_the_listed_connection_is_left_alone() {
651        use std::io::{Read, Write};
652        use std::net::{TcpListener, TcpStream};
653        use std::os::fd::AsRawFd;
654
655        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
656        let local = listener.local_addr().unwrap();
657        let mut client = TcpStream::connect(local).unwrap();
658        let (mut accepted, peer) = listener.accept().unwrap();
659
660        // a descriptor reused by a socket that is not connected (here, the listener)
661        assert!(!sockets::shut_down(listener.as_raw_fd(), local, peer));
662        // the descriptor now connected to another peer than the one that was listed
663        let other: SocketAddr = "127.0.0.1:9".parse().unwrap();
664        assert!(!sockets::shut_down(accepted.as_raw_fd(), local, other));
665        // a descriptor served on another address
666        let elsewhere: SocketAddr = "127.0.0.1:1".parse().unwrap();
667        assert!(!sockets::shut_down(accepted.as_raw_fd(), elsewhere, peer));
668
669        // the connection left alone still carries data both ways
670        accepted.write_all(b"x").unwrap();
671        client
672            .set_read_timeout(Some(Duration::from_secs(30)))
673            .unwrap();
674        let mut byte = [0u8; 1];
675        client.read_exact(&mut byte).unwrap();
676        assert_eq!(&byte, b"x");
677
678        // and the descriptor that is still the listed connection is shut down
679        assert!(sockets::shut_down(accepted.as_raw_fd(), local, peer));
680        assert_eq!(client.read(&mut byte).unwrap_or(0), 0);
681    }
682}