fedimint_portalloc/
lib.rs

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