diff --git a/rust/nix-flake/src/lib.rs b/rust/nix-flake/src/lib.rs index b7ea7ba..e4c45e5 100644 --- a/rust/nix-flake/src/lib.rs +++ b/rust/nix-flake/src/lib.rs @@ -259,9 +259,16 @@ mod tests { use nix_store::store::Store; use super::*; + use std::sync::Once; + + static INIT: Once = Once::new(); fn init() { - nix_util::settings::set("experimental-features", "flakes").unwrap(); + // Only set experimental-features once to minimize the window where + // concurrent Nix operations might read the setting while it's being modified + INIT.call_once(|| { + nix_util::settings::set("experimental-features", "flakes").unwrap(); + }); } #[test] diff --git a/rust/nix-util/src/settings.rs b/rust/nix-util/src/settings.rs index fc29810..93d305d 100644 --- a/rust/nix-util/src/settings.rs +++ b/rust/nix-util/src/settings.rs @@ -1,22 +1,54 @@ use anyhow::Result; use nix_c_raw as raw; +use std::sync::Mutex; use crate::{ check_call, context, result_string_init, string_return::{callback_get_result_string, callback_get_result_string_data}, }; +// Global mutex to protect concurrent access to Nix settings +// See the documentation on `set()` for important thread safety information. +static SETTINGS_MUTEX: Mutex<()> = Mutex::new(()); + +/// Set a Nix setting. +/// +/// # Thread Safety +/// +/// This function uses a mutex to serialize access through the Rust API. +/// However, the underlying Nix settings system uses global mutable state +/// without internal synchronization. +/// +/// The mutex provides protection between Rust callers but cannot prevent: +/// - C++ Nix code from modifying settings concurrently +/// - Other Nix operations from reading settings during modification +/// +/// For multi-threaded applications, ensure that no other Nix operations +/// are running while changing settings. Settings are best modified during +/// single-threaded initialization. pub fn set(key: &str, value: &str) -> Result<()> { + // Lock the mutex to ensure thread-safe access to global settings + let guard = SETTINGS_MUTEX.lock().unwrap(); + let mut ctx = context::Context::new(); let key = std::ffi::CString::new(key)?; let value = std::ffi::CString::new(value)?; unsafe { check_call!(raw::setting_set(&mut ctx, key.as_ptr(), value.as_ptr()))?; } + drop(guard); Ok(()) } +/// Get a Nix setting. +/// +/// # Thread Safety +/// +/// See the documentation on [`set()`] for important thread safety information. pub fn get(key: &str) -> Result { + // Lock the mutex to ensure thread-safe access to global settings + let guard = SETTINGS_MUTEX.lock().unwrap(); + let mut ctx = context::Context::new(); let key = std::ffi::CString::new(key)?; let mut r: Result = result_string_init!(); @@ -28,6 +60,7 @@ pub fn get(key: &str) -> Result { callback_get_result_string_data(&mut r) ))?; } + drop(guard); r }