fix: Deduplicate stores to work around nix#11979
Fixes tests hanging. Before this commit:
nix build .#packages.x86_64-linux.nixops4-eval-release
See https://github.com/NixOS/nix/issues/11979
(cherry picked from commit 03af71f92488f2ee683565318f24afd3e3c001df)
This commit is contained in:
parent
1c0e2cd72f
commit
12d3d62108
2 changed files with 52 additions and 4 deletions
|
|
@ -594,7 +594,8 @@ mod tests {
|
||||||
fn weak_ref_gone() {
|
fn weak_ref_gone() {
|
||||||
gc_registering_current_thread(|| {
|
gc_registering_current_thread(|| {
|
||||||
let weak = {
|
let weak = {
|
||||||
let store = Store::open("auto", HashMap::new()).unwrap();
|
// Use a slightly different URL which is unique in the test suite, to bypass the global store cache
|
||||||
|
let store = Store::open("auto?foo=bar", HashMap::new()).unwrap();
|
||||||
let es = EvalState::new(store, []).unwrap();
|
let es = EvalState::new(store, []).unwrap();
|
||||||
es.weak_ref()
|
es.weak_ref()
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Error, Result};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use nix_c_raw as raw;
|
use nix_c_raw as raw;
|
||||||
use nix_util::context::Context;
|
use nix_util::context::Context;
|
||||||
use nix_util::string_return::{callback_get_result_string, callback_get_result_string_data};
|
use nix_util::string_return::{callback_get_result_string, callback_get_result_string_data};
|
||||||
use nix_util::{check_call, result_string_init};
|
use nix_util::{check_call, result_string_init};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::ffi::{c_char, CString};
|
use std::ffi::{c_char, CString};
|
||||||
use std::ptr::null_mut;
|
use std::ptr::null_mut;
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Mutex, Weak};
|
||||||
|
|
||||||
/* TODO make Nix itself thread safe */
|
/* TODO make Nix itself thread safe */
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
|
@ -33,6 +34,8 @@ impl Drop for StoreRef {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unsafe impl Send for StoreRef {}
|
unsafe impl Send for StoreRef {}
|
||||||
|
/// Unlike pointers in general, operations on raw::Store are thread safe and it is therefore safe to share them between threads.
|
||||||
|
unsafe impl Sync for StoreRef {}
|
||||||
|
|
||||||
/// A [Weak] reference to a store.
|
/// A [Weak] reference to a store.
|
||||||
pub struct StoreWeak {
|
pub struct StoreWeak {
|
||||||
|
|
@ -50,6 +53,13 @@ impl StoreWeak {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Protects against https://github.com/NixOS/nix/issues/11979 (unless different parameters are passed, in which case it's up to luck, but you do get your own parameters as you asked for).
|
||||||
|
type StoreCacheMap = HashMap<(String, Vec<(String, String)>), StoreWeak>;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref STORE_CACHE: Arc<Mutex<StoreCacheMap>> = Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Store {
|
pub struct Store {
|
||||||
inner: Arc<StoreRef>,
|
inner: Arc<StoreRef>,
|
||||||
/* An error context to reuse. This way we don't have to allocate them for each store operation. */
|
/* An error context to reuse. This way we don't have to allocate them for each store operation. */
|
||||||
|
|
@ -59,6 +69,41 @@ impl Store {
|
||||||
pub fn open<'a, 'b>(
|
pub fn open<'a, 'b>(
|
||||||
url: &str,
|
url: &str,
|
||||||
params: impl IntoIterator<Item = (&'a str, &'b str)>,
|
params: impl IntoIterator<Item = (&'a str, &'b str)>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let params = params
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| (k.to_owned(), v.to_owned()))
|
||||||
|
.collect::<Vec<(String, String)>>();
|
||||||
|
let params2 = params.clone();
|
||||||
|
let mut store_cache = STORE_CACHE
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| Error::msg("Failed to lock store cache. This should never happen."))?;
|
||||||
|
match store_cache.entry((url.to_string(), params)) {
|
||||||
|
std::collections::hash_map::Entry::Occupied(mut e) => {
|
||||||
|
if let Some(store) = e.get().upgrade() {
|
||||||
|
Ok(store)
|
||||||
|
} else {
|
||||||
|
let store = Self::open_uncached(
|
||||||
|
url,
|
||||||
|
params2.iter().map(|(k, v)| (k.as_str(), v.as_str())),
|
||||||
|
)?;
|
||||||
|
e.insert(store.weak_ref());
|
||||||
|
Ok(store)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::collections::hash_map::Entry::Vacant(e) => {
|
||||||
|
let store = Self::open_uncached(
|
||||||
|
url,
|
||||||
|
params2.iter().map(|(k, v)| (k.as_str(), v.as_str())),
|
||||||
|
)?;
|
||||||
|
e.insert(store.weak_ref());
|
||||||
|
Ok(store)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn open_uncached<'a, 'b>(
|
||||||
|
url: &str,
|
||||||
|
params: impl IntoIterator<Item = (&'a str, &'b str)>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let x = INIT.as_ref();
|
let x = INIT.as_ref();
|
||||||
match x {
|
match x {
|
||||||
|
|
@ -191,7 +236,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn weak_ref_gone() {
|
fn weak_ref_gone() {
|
||||||
let weak = {
|
let weak = {
|
||||||
let store = Store::open("auto", HashMap::new()).unwrap();
|
// Concurrent tests calling Store::open will keep the weak reference to auto alive,
|
||||||
|
// so for this test we need to bypass the global cache.
|
||||||
|
let store = Store::open_uncached("auto", HashMap::new()).unwrap();
|
||||||
store.weak_ref()
|
store.weak_ref()
|
||||||
};
|
};
|
||||||
assert!(weak.upgrade().is_none());
|
assert!(weak.upgrade().is_none());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue