use super::*;
use std::sync::atomic::AtomicUsize;
use trusty_common::memory_core::palace::{Palace, PalaceId};
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn gate_never_exceeds_its_limit_under_contention() {
let limit = 3;
let gate = StartupOpenGate::with_limit(limit);
let observed_peak = Arc::new(AtomicUsize::new(0));
let live = Arc::new(AtomicUsize::new(0));
let mut set = tokio::task::JoinSet::new();
for _ in 0..16 {
let gate = gate.clone();
let observed_peak = Arc::clone(&observed_peak);
let live = Arc::clone(&live);
set.spawn(async move {
let permit = gate.acquire().await;
let now = live.fetch_add(1, Ordering::SeqCst) + 1;
observed_peak.fetch_max(now, Ordering::SeqCst);
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
live.fetch_sub(1, Ordering::SeqCst);
drop(permit);
});
}
while set.join_next().await.is_some() {}
let peak = observed_peak.load(Ordering::SeqCst);
assert!(
peak <= limit,
"#7106: the gate admitted {peak} concurrent holders against a limit of {limit}"
);
assert_eq!(
gate.peak_concurrent(),
peak,
"the gate's own high-water mark must agree with what the holders saw"
);
assert_eq!(
peak, limit,
"the gate must actually use its budget, not serialise"
);
assert_eq!(live.load(Ordering::SeqCst), 0, "every permit must release");
}
#[test]
fn parse_open_limit_warns_and_keeps_the_default_on_garbage() {
for bad in ["banana", "-2", "0", "4 palaces"] {
let (limit, warning) = parse_open_limit(Some(bad));
assert_eq!(
limit, DEFAULT_STARTUP_OPEN_LIMIT,
"{bad:?} must fall back to the bounded default"
);
let warning =
warning.unwrap_or_else(|| panic!("{bad:?} must produce a warning, not a silence"));
assert!(warning.contains(STARTUP_OPEN_LIMIT_ENV), "{warning}");
assert!(warning.contains(bad), "{warning}");
}
assert_eq!(parse_open_limit(Some("2")), (2, None));
assert_eq!(parse_open_limit(None), (DEFAULT_STARTUP_OPEN_LIMIT, None));
}
fn seed_palace(registry: &PalaceRegistry, root: &std::path::Path, id: &str) -> PalaceId {
let palace = Palace {
id: PalaceId::new(id.to_string()),
name: id.to_string(),
description: None,
created_at: chrono::Utc::now(),
data_dir: root.join(id),
};
let handle = registry
.create_palace(root, palace)
.expect("create test palace");
let palace_id = handle.id.clone();
drop(handle);
palace_id
}
#[test]
fn release_after_sweep_keeps_a_recently_used_palace() {
let tmp = tempfile::tempdir().expect("tempdir");
let registry = PalaceRegistry::new();
let id = seed_palace(®istry, tmp.path(), "recent");
let data_dir = tmp.path().join(&id.0);
crate::palace_last_used::write(&data_dir, crate::palace_last_used::now_unix())
.expect("stamp last_used");
let released = release_after_sweep(
®istry,
&id,
&data_dir,
false,
std::time::Duration::from_secs(DEFAULT_KEEP_RECENT_SECS),
);
assert!(
!released,
"#7087: a palace used inside the keep-recent window must stay resident"
);
assert!(
registry.peek(&id).is_some(),
"the handle must still be cached"
);
}
#[test]
fn release_after_sweep_keeps_an_already_resident_palace() {
let tmp = tempfile::tempdir().expect("tempdir");
let registry = PalaceRegistry::new();
let id = seed_palace(®istry, tmp.path(), "already-warm");
let data_dir = tmp.path().join(&id.0);
let keep = std::time::Duration::from_secs(DEFAULT_KEEP_RECENT_SECS);
assert!(
!release_after_sweep(®istry, &id, &data_dir, true, keep),
"a palace resident before the sweep must not be released by it"
);
assert!(registry.peek(&id).is_some());
assert!(
release_after_sweep(®istry, &id, &data_dir, false, keep),
"#7106: an unreferenced palace the sweep itself opened must be handed back"
);
assert!(
registry.peek(&id).is_none(),
"the handle must be gone from the cache; redb remains the source of truth"
);
}
#[test]
fn release_after_sweep_refuses_while_a_reference_is_held() {
let tmp = tempfile::tempdir().expect("tempdir");
let registry = PalaceRegistry::new();
let id = seed_palace(®istry, tmp.path(), "in-flight");
let data_dir = tmp.path().join(&id.0);
let keep = std::time::Duration::from_secs(DEFAULT_KEEP_RECENT_SECS);
let in_flight = registry.peek(&id).expect("palace is resident");
assert!(
!release_after_sweep(®istry, &id, &data_dir, false, keep),
"a referenced handle must never be released"
);
drop(in_flight);
assert!(release_after_sweep(®istry, &id, &data_dir, false, keep));
}