switchgear_testing/
ports.rs1use 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 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 ports_file.seek(SeekFrom::Start(0))?;
48 ports_file.set_len(0)?; 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}