use crate::types::Budget;
use std::future::Future;
use std::pin::Pin;
pub enum Finalizer {
Sync(Box<dyn FnOnce() + Send>),
Async(Pin<Box<dyn Future<Output = ()> + Send>>),
}
impl std::fmt::Debug for Finalizer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Sync(_) => f.debug_tuple("Sync").field(&"<closure>").finish(),
Self::Async(_) => f.debug_tuple("Async").field(&"<future>").finish(),
}
}
}
pub const FINALIZER_POLL_BUDGET: u32 = 100;
pub const FINALIZER_TIME_BUDGET_NANOS: u64 = 5_000_000_000;
#[must_use]
#[inline]
pub fn finalizer_budget() -> Budget {
let budget = Budget::new().with_poll_quota(FINALIZER_POLL_BUDGET);
debug_assert!(
budget.poll_quota > 0,
"br-asupersync-mg70eb: finalizer budget must have positive poll quota \
(poll_quota={})",
budget.poll_quota
);
debug_assert!(
FINALIZER_TIME_BUDGET_NANOS > 0,
"br-asupersync-mg70eb: finalizer time budget must be positive \
(time_budget_nanos={})",
FINALIZER_TIME_BUDGET_NANOS
);
debug_assert!(
FINALIZER_TIME_BUDGET_NANOS <= 300_000_000_000, "br-asupersync-mg70eb: finalizer time budget seems excessive, may indicate configuration error \
(time_budget_nanos={}, max_reasonable=300_000_000_000)",
FINALIZER_TIME_BUDGET_NANOS
);
budget
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FinalizerEscalation {
Soft,
#[default]
BoundedLog,
BoundedPanic,
}
impl FinalizerEscalation {
#[inline]
#[must_use]
pub const fn allows_continuation(self) -> bool {
matches!(self, Self::BoundedLog)
}
#[inline]
#[must_use]
pub const fn is_soft(self) -> bool {
matches!(self, Self::Soft)
}
#[inline]
#[must_use = "validate_policy_configuration returns configuration diagnostics"]
pub fn validate_policy_configuration(self) -> Result<(), &'static str> {
match self {
Self::Soft => {
debug_assert!(
true, "br-asupersync-mg70eb: Soft escalation policy can cause indefinite waits \
- ensure finalizers respect cancellation signals"
);
Ok(())
}
Self::BoundedLog => {
Ok(())
}
Self::BoundedPanic => {
debug_assert!(
true, "br-asupersync-mg70eb: BoundedPanic escalation policy will panic on budget exhaustion \
- ensure finalizer budgets are adequate for expected workload"
);
Ok(())
}
}
}
}
#[derive(Debug, Default)]
pub struct FinalizerStack {
finalizers: Vec<Finalizer>,
escalation: FinalizerEscalation,
}
impl FinalizerStack {
#[must_use]
#[inline]
pub fn new() -> Self {
Self::default()
}
#[must_use]
#[inline]
pub fn with_escalation(escalation: FinalizerEscalation) -> Self {
let _ = escalation.validate_policy_configuration();
Self {
finalizers: Vec::new(),
escalation,
}
}
#[must_use]
#[inline]
pub const fn escalation(&self) -> FinalizerEscalation {
self.escalation
}
#[inline]
pub fn push(&mut self, finalizer: Finalizer) {
debug_assert!(
self.finalizers.len() < 10000,
"br-asupersync-mg70eb: excessive finalizer count suggests potential leak \
(current_count={}, max_reasonable=10000)",
self.finalizers.len()
);
self.finalizers.push(finalizer);
#[cfg(debug_assertions)]
{
debug_assert!(
!self.finalizers.is_empty(),
"FinalizerStack::push() maintains non-empty invariant after successful push"
);
debug_assert_eq!(
self.finalizers.len(),
self.len(),
"br-asupersync-mg70eb: finalizer stack length inconsistency after push"
);
}
}
#[inline]
pub fn pop(&mut self) -> Option<Finalizer> {
let _len_before = self.finalizers.len();
let result = self.finalizers.pop();
#[cfg(debug_assertions)]
if result.is_some() {
debug_assert!(
true, "FinalizerStack::pop() maintains LIFO contract per SEM-INV-002"
);
debug_assert_eq!(
self.finalizers.len(),
_len_before.saturating_sub(1),
"br-asupersync-mg70eb: finalizer stack length inconsistency after pop \
(before={}, after={}, expected={})",
_len_before,
self.finalizers.len(),
_len_before.saturating_sub(1)
);
debug_assert!(
_len_before > 0,
"br-asupersync-mg70eb: finalizer stack underflow - popped from empty stack"
);
} else {
debug_assert_eq!(
_len_before, 0,
"br-asupersync-mg70eb: finalizer stack returned None but was not empty \
(_len_before={})",
_len_before
);
}
result
}
#[must_use]
#[inline]
pub fn len(&self) -> usize {
self.finalizers.len()
}
#[must_use]
#[inline]
pub fn is_empty(&self) -> bool {
self.finalizers.is_empty()
}
pub fn push_sync<F>(&mut self, f: F)
where
F: FnOnce() + Send + 'static,
{
self.push(Finalizer::Sync(Box::new(f)));
}
pub fn push_async<F>(&mut self, future: F)
where
F: Future<Output = ()> + Send + 'static,
{
self.push(Finalizer::Async(Box::pin(future)));
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::pedantic,
clippy::nursery,
clippy::expect_fun_call,
clippy::map_unwrap_or,
clippy::cast_possible_wrap,
clippy::future_not_send
)]
use super::*;
use parking_lot::Mutex;
fn finalizer_policy_table() -> String {
[
FinalizerEscalation::Soft,
FinalizerEscalation::BoundedLog,
FinalizerEscalation::BoundedPanic,
]
.into_iter()
.map(|policy| {
format!(
"{policy:?}|soft={}|continue={}|polls={}|time_ns={}",
policy.is_soft(),
policy.allows_continuation(),
FINALIZER_POLL_BUDGET,
FINALIZER_TIME_BUDGET_NANOS
)
})
.collect::<Vec<_>>()
.join("\n")
}
fn init_test(name: &str) {
crate::test_utils::init_test_logging();
crate::test_phase!(name);
}
#[test]
fn finalizer_stack_lifo_order() {
init_test("finalizer_stack_lifo_order");
let mut stack = FinalizerStack::new();
let order = std::sync::Arc::new(Mutex::new(Vec::new()));
let o1 = order.clone();
let o2 = order.clone();
let o3 = order.clone();
stack.push_sync(move || o1.lock().push(1));
stack.push_sync(move || o2.lock().push(2));
stack.push_sync(move || o3.lock().push(3));
while let Some(finalizer) = stack.pop() {
if let Finalizer::Sync(f) = finalizer {
f();
}
}
let order = order.lock().clone();
crate::assert_with_log!(order == vec![3, 2, 1], "order", vec![3, 2, 1], order);
crate::test_complete!("finalizer_stack_lifo_order");
}
#[test]
fn finalizer_stack_empty() {
init_test("finalizer_stack_empty");
let mut stack = FinalizerStack::new();
let empty = stack.is_empty();
crate::assert_with_log!(empty, "empty", true, empty);
let len = stack.len();
crate::assert_with_log!(len == 0, "len", 0, len);
let pop = stack.pop();
crate::assert_with_log!(pop.is_none(), "pop none", true, pop.is_none());
crate::test_complete!("finalizer_stack_empty");
}
#[test]
fn finalizer_escalation_policies() {
init_test("finalizer_escalation_policies");
let soft = FinalizerEscalation::Soft.is_soft();
crate::assert_with_log!(soft, "soft is soft", true, soft);
let log_soft = FinalizerEscalation::BoundedLog.is_soft();
crate::assert_with_log!(!log_soft, "log not soft", false, log_soft);
let panic_soft = FinalizerEscalation::BoundedPanic.is_soft();
crate::assert_with_log!(!panic_soft, "panic not soft", false, panic_soft);
let log_cont = FinalizerEscalation::BoundedLog.allows_continuation();
crate::assert_with_log!(log_cont, "log allows", true, log_cont);
let soft_cont = FinalizerEscalation::Soft.allows_continuation();
crate::assert_with_log!(!soft_cont, "soft no continue", false, soft_cont);
let panic_cont = FinalizerEscalation::BoundedPanic.allows_continuation();
crate::assert_with_log!(!panic_cont, "panic no continue", false, panic_cont);
crate::test_complete!("finalizer_escalation_policies");
}
#[test]
fn finalizer_budget_has_expected_values() {
init_test("finalizer_budget_has_expected_values");
let budget = finalizer_budget();
crate::assert_with_log!(
budget.poll_quota == FINALIZER_POLL_BUDGET,
"poll_quota",
FINALIZER_POLL_BUDGET,
budget.poll_quota
);
crate::test_complete!("finalizer_budget_has_expected_values");
}
#[test]
fn finalizer_debug_impl() {
init_test("finalizer_debug_impl");
let sync_finalizer = Finalizer::Sync(Box::new(|| {}));
let debug_str = format!("{sync_finalizer:?}");
let sync_debug_present = debug_str.contains("Sync");
crate::assert_with_log!(sync_debug_present, "sync debug", true, sync_debug_present);
let async_finalizer = Finalizer::Async(Box::pin(async {}));
let debug_str = format!("{async_finalizer:?}");
let async_debug_present = debug_str.contains("Async");
crate::assert_with_log!(
async_debug_present,
"async debug",
true,
async_debug_present
);
crate::test_complete!("finalizer_debug_impl");
}
#[test]
fn finalizer_escalation_debug_clone_copy_eq_default() {
let e = FinalizerEscalation::BoundedLog;
let dbg = format!("{e:?}");
assert!(dbg.contains("BoundedLog"), "{dbg}");
let copied = e;
let cloned = e;
assert_eq!(copied, cloned);
let def = FinalizerEscalation::default();
assert_eq!(def, FinalizerEscalation::BoundedLog);
}
#[test]
fn finalizer_stack_debug_default() {
let stack = FinalizerStack::default();
let dbg = format!("{stack:?}");
assert!(dbg.contains("FinalizerStack"), "{dbg}");
assert!(stack.is_empty());
}
#[test]
fn finalizer_policy_table_snapshot() {
insta::assert_snapshot!("finalizer_policy_table", finalizer_policy_table());
}
fn budget_configuration_table() -> String {
let budget = finalizer_budget();
vec![
format!("poll_quota={}", budget.poll_quota),
format!("cost_quota={:?}", budget.cost_quota),
format!("priority={}", budget.priority),
format!("deadline={:?}", budget.deadline),
format!("constants.poll_budget={}", FINALIZER_POLL_BUDGET),
format!("constants.time_budget_ns={}", FINALIZER_TIME_BUDGET_NANOS),
]
.join("\n")
}
fn finalizer_debug_representations() -> String {
let sync_finalizer = Finalizer::Sync(Box::new(|| {}));
let async_finalizer = Finalizer::Async(Box::pin(async {}));
vec![
format!("Sync: {:?}", sync_finalizer),
format!("Async: {:?}", async_finalizer),
]
.join("\n")
}
fn finalizer_stack_operations() -> String {
let mut lines = Vec::new();
let mut stack = FinalizerStack::new();
lines.push(format!(
"empty_stack: len={}, is_empty={}, escalation={:?}",
stack.len(),
stack.is_empty(),
stack.escalation()
));
let stack_panic = FinalizerStack::with_escalation(FinalizerEscalation::BoundedPanic);
lines.push(format!(
"panic_stack: len={}, is_empty={}, escalation={:?}",
stack_panic.len(),
stack_panic.is_empty(),
stack_panic.escalation()
));
stack.push_sync(|| {});
lines.push(format!(
"after_sync_push: len={}, is_empty={}",
stack.len(),
stack.is_empty()
));
stack.push_async(async {});
lines.push(format!(
"after_async_push: len={}, is_empty={}",
stack.len(),
stack.is_empty()
));
stack.push_sync(|| {});
lines.push(format!(
"after_second_sync: len={}, is_empty={}",
stack.len(),
stack.is_empty()
));
if let Some(finalizer) = stack.pop() {
match finalizer {
Finalizer::Sync(_) => lines.push("popped: Sync".to_string()),
Finalizer::Async(_) => lines.push("popped: Async".to_string()),
}
}
lines.push(format!(
"after_first_pop: len={}, is_empty={}",
stack.len(),
stack.is_empty()
));
if let Some(finalizer) = stack.pop() {
match finalizer {
Finalizer::Sync(_) => lines.push("popped: Sync".to_string()),
Finalizer::Async(_) => lines.push("popped: Async".to_string()),
}
}
lines.push(format!(
"after_second_pop: len={}, is_empty={}",
stack.len(),
stack.is_empty()
));
if let Some(finalizer) = stack.pop() {
match finalizer {
Finalizer::Sync(_) => lines.push("popped: Sync".to_string()),
Finalizer::Async(_) => lines.push("popped: Async".to_string()),
}
}
lines.push(format!(
"after_third_pop: len={}, is_empty={}",
stack.len(),
stack.is_empty()
));
let empty_pop = stack.pop();
lines.push(format!(
"empty_pop: {}",
if empty_pop.is_none() { "None" } else { "Some" }
));
lines.join("\n")
}
fn escalation_policy_matrix() -> String {
let policies = [
FinalizerEscalation::Soft,
FinalizerEscalation::BoundedLog,
FinalizerEscalation::BoundedPanic,
];
let mut lines = Vec::new();
lines.push("policy|is_soft|allows_continuation|default_match".to_string());
for policy in policies {
let is_default = policy == FinalizerEscalation::default();
lines.push(format!(
"{:?}|{}|{}|{}",
policy,
policy.is_soft(),
policy.allows_continuation(),
is_default
));
}
lines.join("\n")
}
fn finalizer_stack_debug_output() -> String {
let mut lines = Vec::new();
let empty_stack = FinalizerStack::new();
lines.push(format!("empty: {:?}", empty_stack));
let panic_stack = FinalizerStack::with_escalation(FinalizerEscalation::BoundedPanic);
lines.push(format!("panic_escalation: {:?}", panic_stack));
let soft_stack = FinalizerStack::with_escalation(FinalizerEscalation::Soft);
lines.push(format!("soft_escalation: {:?}", soft_stack));
lines.join("\n")
}
#[test]
fn finalizer_budget_configuration() {
insta::assert_snapshot!("budget_configuration", budget_configuration_table());
}
#[test]
fn finalizer_debug_output() {
insta::assert_snapshot!("debug_representations", finalizer_debug_representations());
}
#[test]
fn finalizer_stack_state_transitions() {
insta::assert_snapshot!("stack_operations", finalizer_stack_operations());
}
#[test]
fn finalizer_escalation_behavior_matrix() {
insta::assert_snapshot!("escalation_matrix", escalation_policy_matrix());
}
#[test]
fn finalizer_stack_debug_variants() {
insta::assert_snapshot!("stack_debug_output", finalizer_stack_debug_output());
}
}