From 3bef49427155fbd2dfbce13aa9999cb3102701b3 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Tue, 26 Aug 2025 16:45:25 +0200 Subject: [PATCH] fix: Add mutex to nix_util::settings to prevent concurrent access segfault Fixes #106 The Nix settings system uses global mutable state without internal synchronization. When multiple threads call settings::set concurrently (as happens in parallel test execution), this causes a segfault in the C++ std::set implementation. Changes: - Add mutex to serialize access through the Rust API - Add documentation explaining thread safety limitations - Add Once guard in nix-flake tests to minimize concurrent access The mutex provides protection between Rust callers, though it cannot completely prevent C++ Nix code from modifying settings concurrently. (cherry picked from commit 203917657b60c4e1dcbaf442bec64c37c634abc4) --- rust/nix-flake/src/lib.rs | 9 ++++++++- rust/nix-util/src/settings.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) 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 }