1#![allow(clippy::needless_lifetimes)]
23//! A library for cooperative port allocation between multiple processes.
4//!
5//! Fedimint tests in many places need to allocate ranges of unused ports for
6//! Federations and other software under tests, without being able to `bind`
7//! them beforehand.
8//!
9//! We used to mitigate that using a global per-process atomic counter, as
10//! as simple port allocation mechanism. But this does not prevent conflicts
11//! between different processes.
12//!
13//! Normally this would prevent us from running multiple tests at the same time,
14//! which also makes it impossible to use `cargo nextest`.
15//!
16//! This library keeps track of allocated ports (with an expiration timeout) in
17//! a shared file, protected by an advisory fs lock, and uses `bind` to make
18//! sure a given port is actually free
1920mod data;
21mod envs;
22mod util;
2324use std::path::PathBuf;
2526use anyhow::bail;
2728use crate::data::DataDir;
29use crate::envs::FM_PORTALLOC_DATA_DIR_ENV;
3031pub fn port_alloc(range_size: u16) -> anyhow::Result<u16> {
32if range_size == 0 {
33bail!("Can't allocate range of 0 ports");
34 }
3536let mut data_dir = DataDir::new(data_dir()?)?;
3738 data_dir.with_lock(|data_dir| {
39let mut data = data_dir.load_data()?;
40let base_port = data.get_free_port_range(range_size);
41 data_dir.store_data(&data)?;
42Ok(base_port)
43 })
44}
4546fn data_dir() -> anyhow::Result<PathBuf> {
47if let Some(env) = std::env::var_os(FM_PORTALLOC_DATA_DIR_ENV) {
48Ok(PathBuf::from(env))
49 } else if let Some(dir) = dirs::cache_dir() {
50Ok(dir.join("fm-portalloc"))
51 } else {
52bail!("Could not determine port alloc data dir. Try setting FM_PORTALLOC_DATA_DIR");
53 }
54}