From 176023156eb8885b62b5fd0c53971080e61db498 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 14 Nov 2025 14:42:36 +0000 Subject: [PATCH] test_infra: Make guest ID generation multiprocess safe When using nextest for running tests each test is run in its own process so the old solution of using a static variable for the guest ID (used to determine the network segment) no longer works. Instead use a text file on the filesystem protected with an exclusive lock. The test process will read from it and then write back the next ID that can be used. It wraps around at the limit of u8 and skips ID 0. This function intentionally panics rather than propagate errors as it should only be called for testing purposes and there the panic handler will give a useful backtrace and cleanup. Signed-off-by: Rob Bradford --- test_infra/src/lib.rs | 45 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 24a818c19..12d9c5940 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -7,14 +7,14 @@ use std::ffi::OsStr; use std::fmt::Display; -use std::io::{Read, Write}; +use std::fs::OpenOptions; +use std::io::{Read, Seek, SeekFrom, Write}; use std::net::{TcpListener, TcpStream}; use std::os::unix::fs::PermissionsExt; use std::os::unix::io::{AsRawFd, FromRawFd}; use std::path::Path; use std::process::{Child, Command, ExitStatus, Output, Stdio}; use std::str::FromStr; -use std::sync::{LazyLock, Mutex}; use std::time::Duration; use std::{env, fmt, fs, io, thread}; @@ -849,14 +849,45 @@ pub fn kill_child(child: &mut Child) { pub const PIPE_SIZE: i32 = 32 << 20; -static NEXT_VM_ID: LazyLock> = LazyLock::new(|| Mutex::new(1)); - pub struct Guest { pub tmp_dir: TempDir, pub disk_config: Box, pub network: GuestNetworkConfig, } +// Return the next id that can be used for this guest. This is stored in a +// file in the filesystem and is protected by a filesystem lock allowing +// multiple test processes to safely access it with the process blocking +// until the lock is released. +fn next_guest_id() -> u8 { + let mut id_file_path = dirs::home_dir().unwrap(); + id_file_path.push("workloads"); + id_file_path.push("id.counter"); + + let mut id_file = OpenOptions::new() + .create(true) + .truncate(false) + .write(true) + .read(true) + .open(id_file_path) + .unwrap(); + + id_file.lock().unwrap(); + + // Use a string in the file for (human) readability + let mut buf = String::default(); + id_file.read_to_string(&mut buf).unwrap(); + let id = buf.trim().parse::().unwrap_or(1); + let next_id = u8::max(1, id.overflowing_add(1).0); + id_file.set_len(0).unwrap(); + id_file.seek(SeekFrom::Start(0)).unwrap(); + id_file.write_all(next_id.to_string().as_bytes()).unwrap(); + + id_file.unlock().unwrap(); + + id +} + // Safe to implement as we know we have no interior mutability impl std::panic::RefUnwindSafe for Guest {} @@ -890,11 +921,7 @@ impl Guest { } pub fn new(disk_config: Box) -> Self { - let mut guard = NEXT_VM_ID.lock().unwrap(); - let id = *guard; - *guard = id + 1; - - Self::new_from_ip_range(disk_config, "192.168", id) + Self::new_from_ip_range(disk_config, "192.168", next_guest_id()) } pub fn default_net_string(&self) -> String {