WEB: skip registry sweeps before the earliest possible expiry

This commit is contained in:
Alexey
2026-10-06 22:00:13 +03:00
parent 9d5b896bb6
commit deafa4a6cf
14 changed files with 1382 additions and 80 deletions
+302
View File
@@ -0,0 +1,302 @@
use super::super::*;
use proptest::prelude::*;
// Keep synthetic ownership fixtures separate from the frozen exhaustive algorithm.
include!("fixtures.rs");
#[path = "benchmarks.rs"]
mod benchmarks;
#[path = "production_tests.rs"]
mod production_tests;
#[path = "reference.rs"]
mod reference;
fn assert_equivalent(actual: &ManagerState, expected: &ManagerState) {
let bootstraps = |state: &ManagerState| {
state
.bootstraps
.iter()
.map(|(key, b)| {
(
*key,
(b.expires_at, b.issued_at, b.issuance_ip, b.used, b.recovery),
)
})
.collect::<BTreeMap<_, _>>()
};
let closed = |state: &ManagerState| {
state
.closed_tokens
.iter()
.map(|(key, c)| (*key, (c.expires_at, c.host.clone(), c.carrier)))
.collect::<BTreeMap<_, _>>()
};
let sessions = |state: &ManagerState| {
state
.closed_sessions
.iter()
.map(|(key, c)| {
(
*key,
(c.expires_at, c.closed_at, c.attempt, c.carrier, c.reason),
)
})
.collect::<BTreeMap<_, _>>()
};
assert_eq!(bootstraps(actual), bootstraps(expected));
assert_eq!(closed(actual), closed(expected));
assert_eq!(sessions(actual), sessions(expected));
assert_eq!(actual.closed_session_order, expected.closed_session_order);
assert_eq!(actual.bootstraps_per_ip, expected.bootstraps_per_ip);
for b in actual.bootstraps.values() {
assert!(
actual
.bootstrap_expiry
.is_some_and(|expiry| expiry <= b.expires_at)
);
assert!(!b.user_registration.is_cancelled());
}
for c in actual.closed_tokens.values() {
assert!(
actual
.closed_token_expiry
.is_some_and(|expiry| expiry <= c.expires_at)
);
}
}
#[test]
fn exact_expiry_boundary_and_recovery_accounting_match_exhaustive_sweep() {
let fixture = Fixture::new();
let now = Instant::now();
let deadline = now + Duration::from_secs(1);
let mut actual = ManagerState::default();
let mut expected = ManagerState::default();
for state in [&mut actual, &mut expected] {
for flags in 0..4 {
insert_bootstrap(state, [flags; 32], fixture.bootstrap(now, deadline, flags));
}
insert_closed_token(state, [8; 32], deadline);
insert_bootstrap(
state,
[6; 32],
fixture.bootstrap(now, deadline + Duration::from_secs(2), 0),
);
insert_closed_token(state, [9; 32], deadline + Duration::from_secs(2));
}
for (time, count) in [
(deadline - Duration::from_nanos(1), 0),
(deadline, 0),
(deadline + Duration::from_nanos(1), 1),
// A caller may have captured an older time before waiting for the manager lock.
(deadline - Duration::from_nanos(1), 0),
(deadline + Duration::from_secs(1), 0),
(deadline + Duration::from_secs(3), 0),
] {
assert_eq!(remove_expired_locked(&mut actual, time), count);
assert_eq!(reference::exhaustive(&mut expected, time), count);
assert_equivalent(&actual, &expected);
}
assert!(actual.bootstraps_per_ip.is_empty());
assert_eq!(actual.bootstrap_expiry, None);
assert_eq!(actual.closed_token_expiry, None);
}
#[test]
fn independent_hints_do_not_skip_logical_session_tombstones() {
let fixture = Fixture::new();
let now = Instant::now();
for bootstrap_due in [false, true] {
let mut state = ManagerState::default();
let early = now - Duration::from_secs(1);
let late = now + Duration::from_secs(1);
insert_bootstrap(
&mut state,
[1; 32],
fixture.bootstrap(early, if bootstrap_due { early } else { late }, 0),
);
insert_closed_token(
&mut state,
[2; 32],
if bootstrap_due { late } else { early },
);
state.closed_sessions.insert(
3,
ClosedSession {
expires_at: early,
attempt: 1,
carrier: WebCarrier::Https,
reason: crate::web::session::SessionCloseReason::ApiClose,
closed_at: early,
},
);
state.closed_session_order.push_back(3);
remove_expired_locked(&mut state, now);
assert_eq!(state.bootstraps.is_empty(), bootstrap_due);
assert_eq!(state.closed_tokens.is_empty(), !bootstrap_due);
assert!(state.closed_sessions.is_empty());
assert!(state.closed_session_order.is_empty());
}
let mut state = ManagerState::default();
remember_closed_session_locked(
&mut state,
1,
1,
WebCarrier::Https,
crate::web::session::SessionCloseReason::ApiClose,
Duration::ZERO,
1,
);
let time = state.closed_sessions[&1].expires_at + Duration::from_nanos(1);
remove_expired_locked(&mut state, time);
assert!(state.closed_sessions.is_empty());
}
#[test]
fn closed_token_overwrite_and_capacity_eviction_keep_a_conservative_hint() {
let mut state = ManagerState::default();
for (lifetime, capacity) in [(60, 2), (1, 2), (120, 2), (1, 0), (60, 1)] {
remember_closed_token_locked(
&mut state,
[1; 32],
"proxy.example.com",
WebCarrier::Https,
Duration::from_secs(lifetime),
capacity,
);
assert!(state.closed_tokens.len() <= capacity);
for c in state.closed_tokens.values() {
assert!(state.closed_token_expiry.unwrap() <= c.expires_at);
}
}
let deadline = state.closed_tokens[&[1; 32]].expires_at;
remove_expired_locked(&mut state, deadline);
assert_eq!(state.closed_tokens.len(), 1);
remove_expired_locked(&mut state, deadline + Duration::from_nanos(1));
assert!(state.closed_tokens.is_empty());
for (key, lifetime) in [(1, 1), (2, 60), (3, 120)] {
remember_closed_token_locked(
&mut state,
[key; 32],
"proxy.example.com",
WebCarrier::Https,
Duration::from_secs(lifetime),
2,
);
}
let stale_hint = state.closed_token_expiry.unwrap();
assert!(!state.closed_tokens.contains_key(&[1; 32]));
assert!(state.closed_tokens.contains_key(&[2; 32]));
assert!(state.closed_tokens.contains_key(&[3; 32]));
let survivor_min = state
.closed_tokens
.values()
.map(|c| c.expires_at)
.min()
.unwrap();
assert!(stale_hint < survivor_min);
remove_expired_locked(&mut state, stale_hint + Duration::from_nanos(1));
assert_eq!(state.closed_tokens.len(), 2);
assert_eq!(state.closed_token_expiry, Some(survivor_min));
}
#[test]
fn concurrent_due_sweeps_match_exhaustive_accounting() {
let fixture = Fixture::new();
let now = Instant::now();
let states = parking_lot::Mutex::new((ManagerState::default(), ManagerState::default()));
let barrier = std::sync::Barrier::new(16);
std::thread::scope(|scope| {
for worker in 0..16 {
let fixture = &fixture;
let states = &states;
let barrier = &barrier;
scope.spawn(move || {
barrier.wait();
for iteration in 0..64 {
let flags = (iteration % 4) as u8;
let expired = now - Duration::from_nanos(1);
let first = fixture.bootstrap(expired, expired, flags);
let second = fixture.bootstrap(expired, expired, flags);
let mut states = states.lock();
let (actual, expected) = &mut *states;
insert_bootstrap(actual, [worker; 32], first);
insert_bootstrap(expected, [worker; 32], second);
insert_closed_token(actual, [worker; 32], expired);
insert_closed_token(expected, [worker; 32], expired);
assert_eq!(
remove_expired_locked(actual, now),
reference::exhaustive(expected, now),
);
assert_equivalent(actual, expected);
assert!(actual.bootstraps_per_ip.is_empty());
assert!(actual.closed_tokens.is_empty());
}
});
}
});
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(128))]
#[test]
fn mixed_registry_operations_match_exhaustive_sweep(
actions in prop::collection::vec((0u8..9, any::<u8>(), 0u16..100), 1..300)
) {
let fixture = Fixture::new();
let mut actual = ManagerState::default();
let mut expected = ManagerState::default();
let start = Instant::now();
let mut now = start;
for (step, (operation, key, ttl)) in actions.into_iter().enumerate() {
let hash = [key; 32];
let expiry = now + Duration::from_millis(u64::from(ttl));
for state in [&mut actual, &mut expected] {
match operation {
0 => insert_bootstrap(state, hash, fixture.bootstrap(
start + Duration::from_nanos(step as u64), expiry, key % 4,
)),
1 => remove_bootstrap_locked(state, hash),
2 => {
if let Some(b) = state.bootstraps.get_mut(&hash) {
if !b.used {
decrement_map(&mut state.bootstraps_per_ip, &b.issuance_ip);
b.used = true;
}
b.expires_at += Duration::from_millis(u64::from(ttl));
}
}
3 => { drop(evict_oldest_unused_bootstrap(state)); }
4 => insert_closed_token(state, hash, expiry),
5 => { state.closed_tokens.remove(&hash); }
6 => {
state.bootstraps.clear();
state.bootstraps_per_ip.clear();
state.bootstrap_expiry = None;
}
7 => {
let id = u64::from(key);
if state.closed_sessions.insert(id, ClosedSession {
expires_at: expiry,
attempt: 1,
carrier: WebCarrier::Https,
reason: crate::web::session::SessionCloseReason::ApiClose,
closed_at: now,
}).is_none() {
state.closed_session_order.push_back(id);
}
}
_ => {}
}
}
if operation == 8 {
now += Duration::from_millis(u64::from(ttl));
}
assert_eq!(
remove_expired_locked(&mut actual, now),
reference::exhaustive(&mut expected, now),
);
assert_equivalent(&actual, &expected);
}
}
}