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}