fedimint_portalloc/
lib.rs

1#![allow(clippy::needless_lifetimes)]
2
3//! 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
19
20mod data;
21mod envs;
22mod util;
23
24use std::path::PathBuf;
25
26use anyhow::bail;
27
28use crate::data::DataDir;
29use crate::envs::FM_PORTALLOC_DATA_DIR_ENV;
30
31pub fn port_alloc(range_size: u16) -> anyhow::Result<u16> {
32    if range_size == 0 {
33        bail!("Can't allocate range of 0 ports");
34    }
35
36    let mut data_dir = DataDir::new(data_dir()?)?;
37
38    data_dir.with_lock(|data_dir| {
39        let mut data = data_dir.load_data()?;
40        let base_port = data.get_free_port_range(range_size);
41        data_dir.store_data(&data)?;
42        Ok(base_port)
43    })
44}
45
46fn data_dir() -> anyhow::Result<PathBuf> {
47    if let Some(env) = std::env::var_os(FM_PORTALLOC_DATA_DIR_ENV) {
48        Ok(PathBuf::from(env))
49    } else if let Some(dir) = dirs::cache_dir() {
50        Ok(dir.join("fm-portalloc"))
51    } else {
52        bail!("Could not determine port alloc data dir. Try setting FM_PORTALLOC_DATA_DIR");
53    }
54}