switchgear_testing/
ports.rs

1use fs4::fs_std::FileExt;
2use indexmap::IndexSet;
3use std::fs::OpenOptions;
4use std::io::{Read, Seek, SeekFrom, Write};
5use std::net::TcpListener;
6use std::path::Path;
7use std::thread::sleep;
8use std::time::Duration;
9
10const GLOBAL_LOCK_FILE: &str = "ports.txt";
11const GLOBAL_LOCK_FILE_SIZE: usize = 256;
12
13pub struct PortAllocator {}
14
15impl PortAllocator {
16    pub fn find_available_port(ports_path: &Path) -> anyhow::Result<u16> {
17        let ports_path = ports_path.join(GLOBAL_LOCK_FILE);
18        let mut ports_file = OpenOptions::new()
19            .read(true)
20            .write(true)
21            .create(true)
22            .truncate(false)
23            .open(ports_path)?;
24
25        ports_file.lock_exclusive()?;
26
27        // Read assigned ports from file
28        let mut contents = String::new();
29        ports_file.read_to_string(&mut contents)?;
30
31        let mut assigned_ports: IndexSet<u16> = contents
32            .lines()
33            .filter_map(|line| line.trim().parse().ok())
34            .collect();
35
36        loop {
37            let listener = TcpListener::bind("127.0.0.1:0")?;
38            let port = listener.local_addr()?.port();
39
40            {
41                if !assigned_ports.contains(&port) {
42                    assigned_ports.insert(port);
43                    if assigned_ports.len() > GLOBAL_LOCK_FILE_SIZE {
44                        assigned_ports.shift_remove_index(0);
45                    }
46                    // Write updated ports list to file
47                    ports_file.seek(SeekFrom::Start(0))?;
48                    ports_file.set_len(0)?; // Truncate file
49                    let contents = assigned_ports
50                        .iter()
51                        .map(|p| p.to_string())
52                        .collect::<Vec<_>>()
53                        .join("\n");
54                    ports_file.write_all(contents.as_bytes())?;
55                    ports_file.sync_all()?;
56
57                    return Ok(port);
58                }
59            }
60            sleep(Duration::from_millis(10));
61        }
62    }
63}