#![cfg(all(feature = "persistent-artrie", target_os = "linux"))]
use libdictenstein::artrie_trait::{ARTrie, EvictableARTrie};
use libdictenstein::persistent_artrie::eviction::EvictionConfig;
use libdictenstein::persistent_artrie_char::SharedCharARTrie;
use libdictenstein::MutableMappedDictionary;
use proptest::prelude::*;
use std::time::{Duration, Instant};
use tempfile::tempdir;
fn serialize_thread_tests() -> std::sync::MutexGuard<'static, ()> {
static THREAD_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
THREAD_TEST_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn trie_thread_count() -> usize {
let mut count = 0;
if let Ok(entries) = std::fs::read_dir("/proc/self/task") {
for entry in entries.flatten() {
if let Ok(comm) = std::fs::read_to_string(entry.path().join("comm")) {
let name = comm.trim();
if name == "wal-sync" || name.starts_with("artrie-") {
count += 1;
}
}
}
}
count
}
fn wait_until_threads_at_most(target: usize, timeout: Duration) -> usize {
let start = Instant::now();
loop {
let now = trie_thread_count();
if now <= target || start.elapsed() > timeout {
return now;
}
std::thread::sleep(Duration::from_millis(20));
}
}
#[derive(Debug, Clone)]
enum Lifecycle {
Plain,
Eviction,
EvictionThenDisable,
}
fn lifecycle_strategy() -> impl Strategy<Value = Lifecycle> {
prop_oneof![
Just(Lifecycle::Plain),
Just(Lifecycle::Eviction),
Just(Lifecycle::EvictionThenDisable),
]
}
proptest! {
#![proptest_config(ProptestConfig { cases: 24, .. ProptestConfig::default() })]
#[test]
fn daemon_threads_return_to_baseline(
ops in prop::collection::vec(lifecycle_strategy(), 1..12)
) {
let _serial = serialize_thread_tests();
let baseline = trie_thread_count();
for op in &ops {
let dir = tempdir().expect("tempdir");
let path = dir.path().join("t.artrie");
let shared: SharedCharARTrie<i32> = ARTrie::create(&path).expect("create");
let _ = MutableMappedDictionary::insert_with_value(&shared, "alpha", 1);
match op {
Lifecycle::Plain => {}
Lifecycle::Eviction => {
let _ = shared.enable_eviction(EvictionConfig::default());
}
Lifecycle::EvictionThenDisable => {
let _ = shared.enable_eviction(EvictionConfig::default());
let _ = shared.disable_eviction();
}
}
drop(shared);
}
let after = wait_until_threads_at_most(baseline, Duration::from_secs(10));
prop_assert!(
after <= baseline,
"daemon threads leaked across {} lifecycles: baseline={}, after={}, ops={:?}",
ops.len(), baseline, after, ops
);
}
}