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)
This commit is contained in:
parent
200dcf0891
commit
3bef494271
2 changed files with 41 additions and 1 deletions
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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<String> {
|
||||
// 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<String> = result_string_init!();
|
||||
|
|
@ -28,6 +60,7 @@ pub fn get(key: &str) -> Result<String> {
|
|||
callback_get_result_string_data(&mut r)
|
||||
))?;
|
||||
}
|
||||
drop(guard);
|
||||
r
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue