Skip to main content

xianyun_executor/
portprobe.rs

1//! Reports whether a TCP port on the local host already has a listener.
2//!
3//! Ported from `internal/portprobe/portprobe.go`. Not yet implemented.
4
5use std::net::TcpStream;
6use std::time::Duration;
7
8const PROBE_TIMEOUT: Duration = Duration::from_millis(200);
9
10/// Ported from `InUse`.
11///
12/// Dials `address` over TCP with a 200ms timeout; returns `true` if
13/// something is listening, `false` on any connection error (including
14/// timeout, refused, or an unparsable address).
15pub fn in_use(address: &str) -> bool {
16    let Ok(mut addrs) = address.to_socket_addrs() else {
17        return false;
18    };
19    let Some(addr) = addrs.next() else {
20        return false;
21    };
22    TcpStream::connect_timeout(&addr, PROBE_TIMEOUT).is_ok()
23}
24
25use std::net::ToSocketAddrs;
26
27#[cfg(test)]
28mod tests {
29    use super::*;
30    use std::net::TcpListener;
31
32    #[test]
33    fn in_use_reflects_listener_lifecycle() {
34        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
35        let address = listener.local_addr().unwrap().to_string();
36
37        assert!(
38            in_use(&address),
39            "port with a listener should read as in use"
40        );
41
42        drop(listener);
43
44        // The OS may keep the just-closed port briefly connectable (e.g.
45        // a lingering accept backlog / TIME_WAIT on macOS), so poll for it
46        // to read free rather than asserting on the first probe. If it
47        // never frees within the window, that's a real failure.
48        let mut free = false;
49        for _ in 0..50 {
50            if !in_use(&address) {
51                free = true;
52                break;
53            }
54            std::thread::sleep(std::time::Duration::from_millis(20));
55        }
56        assert!(free, "port should read as free once the listener closes");
57    }
58}