use crate::record::finalizer::{Finalizer, FinalizerStack};
use crate::record::task::TaskOutcome;
use crate::runtime::region_heap::{HeapIndex, RegionHeap};
use crate::tracing_compat::{Span, debug, info_span};
use crate::types::rref::{RRef, RRefAccessWitness, RRefError};
use crate::types::{
Budget, CancelReason, CapabilityBudget, CurveBudget, RRefAccess, RegionId, TaskId, Time,
};
use parking_lot::RwLock;
use std::cell::Cell;
use std::sync::Arc;
use std::sync::atomic::{AtomicU8, AtomicU32, AtomicU64, Ordering};
thread_local! {
static IN_REGION_WITH_CALL: Cell<bool> = const { Cell::new(false) };
}
struct ReentryGuard {
was_in_call: bool,
}
impl ReentryGuard {
fn new() -> Self {
let was_in_call = IN_REGION_WITH_CALL.with(|flag| {
let was_in = flag.get();
if !was_in {
flag.set(true);
}
was_in
});
ReentryGuard { was_in_call }
}
fn is_reentrant(&self) -> bool {
self.was_in_call
}
}
impl Drop for ReentryGuard {
fn drop(&mut self) {
if !self.was_in_call {
IN_REGION_WITH_CALL.with(|flag| flag.set(false));
}
}
}
#[derive(Debug)]
pub struct RegionCloseState {
pub closed: bool,
pub waiters: Vec<std::task::Waker>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegionState {
Open,
Closing,
Draining,
Finalizing,
Closed,
}
impl RegionState {
#[must_use]
pub const fn as_u8(self) -> u8 {
match self {
Self::Open => 0,
Self::Closing => 1,
Self::Draining => 2,
Self::Finalizing => 3,
Self::Closed => 4,
}
}
#[must_use]
pub const fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(Self::Open),
1 => Some(Self::Closing),
2 => Some(Self::Draining),
3 => Some(Self::Finalizing),
4 => Some(Self::Closed),
_ => None,
}
}
#[must_use]
pub const fn is_terminal(self) -> bool {
matches!(self, Self::Closed)
}
#[must_use]
pub const fn can_spawn(self) -> bool {
matches!(self, Self::Open)
}
#[must_use]
pub const fn can_accept_work(self) -> bool {
matches!(self, Self::Open)
}
#[must_use]
pub const fn can_accept_cleanup_work(self) -> bool {
matches!(self, Self::Open | Self::Finalizing)
}
#[must_use]
pub const fn is_draining(self) -> bool {
matches!(self, Self::Draining)
}
#[must_use]
pub const fn is_closing(self) -> bool {
matches!(self, Self::Closing | Self::Draining | Self::Finalizing)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RegionLimits {
pub max_children: Option<usize>,
pub max_tasks: Option<usize>,
pub max_obligations: Option<usize>,
pub max_heap_bytes: Option<usize>,
pub curve_budget: Option<CurveBudget>,
}
impl RegionLimits {
pub const UNLIMITED: Self = Self {
max_children: None,
max_tasks: None,
max_obligations: None,
max_heap_bytes: None,
curve_budget: None,
};
#[must_use]
pub const fn unlimited() -> Self {
Self::UNLIMITED
}
#[must_use]
pub fn with_curve_budget(mut self, curve_budget: CurveBudget) -> Self {
self.curve_budget = Some(curve_budget);
self
}
#[must_use]
pub fn without_curve_budget(mut self) -> Self {
self.curve_budget = None;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AdmissionKind {
Child,
Task,
Obligation,
HeapBytes,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AdmissionError {
Closed,
LimitReached {
kind: AdmissionKind,
limit: usize,
live: usize,
},
}
#[derive(Debug)]
pub struct AtomicRegionState {
inner: AtomicU8,
}
impl AtomicRegionState {
#[must_use]
pub fn new(state: RegionState) -> Self {
Self {
inner: AtomicU8::new(state.as_u8()),
}
}
#[must_use]
pub fn load(&self) -> RegionState {
RegionState::from_u8(self.inner.load(Ordering::Acquire)).expect("invalid region state")
}
pub fn store(&self, state: RegionState) {
self.inner.store(state.as_u8(), Ordering::Release);
}
pub fn transition(&self, from: RegionState, to: RegionState) -> bool {
self.inner
.compare_exchange(
from.as_u8(),
to.as_u8(),
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
}
}
#[derive(Debug)]
struct RegionInner {
budget: Budget,
capability_budget: CapabilityBudget,
children: Vec<RegionId>,
tasks: Vec<TaskId>,
finalizers: FinalizerStack,
cancel_reason: Option<CancelReason>,
close_outcome: Option<TaskOutcome>,
limits: RegionLimits,
pending_obligations: usize,
heap: RegionHeap,
}
#[derive(Debug, Default)]
pub struct PendingSpawnCounter {
count: AtomicU32,
underflow_count: AtomicU32,
}
impl PendingSpawnCounter {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[inline]
#[must_use]
pub fn count(&self) -> u32 {
self.count.load(Ordering::SeqCst)
}
#[must_use]
pub fn reserve(self: &Arc<Self>) -> PendingSpawnReservation {
self.count.fetch_add(1, Ordering::SeqCst);
PendingSpawnReservation {
counter: Arc::clone(self),
}
}
#[must_use]
pub fn underflow_count(&self) -> u32 {
self.underflow_count.load(Ordering::SeqCst)
}
fn release(&self) {
let result = self
.count
.try_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
count.checked_sub(1)
});
if result.is_err() {
debug_assert!(false, "pending-spawn counter released below zero");
self.underflow_count.fetch_add(1, Ordering::SeqCst);
}
}
}
#[derive(Debug)]
pub struct PendingSpawnReservation {
counter: Arc<PendingSpawnCounter>,
}
impl Drop for PendingSpawnReservation {
fn drop(&mut self) {
self.counter.release();
}
}
#[derive(Debug)]
pub struct RegionRecord {
pub id: RegionId,
pub parent: Option<RegionId>,
pub created_at: Time,
pub close_notify: std::sync::Arc<parking_lot::Mutex<RegionCloseState>>,
state: AtomicRegionState,
inner: RwLock<RegionInner>,
double_resolve_count: AtomicU64,
pending_spawns: Arc<PendingSpawnCounter>,
#[cfg(feature = "tracing-integration")]
span: Span,
#[cfg(not(feature = "tracing-integration"))]
span: Span,
}
impl RegionRecord {
#[must_use]
pub fn new(id: RegionId, parent: Option<RegionId>, budget: Budget) -> Self {
Self::new_with_time(id, parent, budget, Time::from_nanos(1_000_000_000))
}
#[must_use]
pub fn new_with_capability_budget(
id: RegionId,
parent: Option<RegionId>,
budget: Budget,
capability_budget: CapabilityBudget,
) -> Self {
Self::new_with_time_and_capability_budget(
id,
parent,
budget,
Time::from_nanos(1_000_000_000),
capability_budget,
)
}
#[must_use]
pub fn new_with_time(
id: RegionId,
parent: Option<RegionId>,
budget: Budget,
created_at: Time,
) -> Self {
Self::new_with_time_and_capability_budget(
id,
parent,
budget,
created_at,
CapabilityBudget::UNSPECIFIED,
)
}
#[must_use]
pub fn new_with_time_and_capability_budget(
id: RegionId,
parent: Option<RegionId>,
budget: Budget,
created_at: Time,
capability_budget: CapabilityBudget,
) -> Self {
let span = info_span!(
"region",
region_id = ?id,
parent_region_id = ?parent,
state = "Open",
initial_budget_deadline = ?budget.deadline,
initial_budget_poll_quota = budget.poll_quota,
);
debug!(
parent: &span,
region_id = ?id,
parent_region_id = ?parent,
state = "Open",
budget_deadline = ?budget.deadline,
budget_poll_quota = budget.poll_quota,
"region created"
);
Self {
id,
parent,
created_at,
close_notify: std::sync::Arc::new(parking_lot::Mutex::new(RegionCloseState {
closed: false,
waiters: Vec::new(),
})),
state: AtomicRegionState::new(RegionState::Open),
inner: RwLock::new(RegionInner {
budget,
capability_budget,
children: Vec::new(),
tasks: Vec::new(),
finalizers: FinalizerStack::new(),
cancel_reason: None,
close_outcome: None,
limits: RegionLimits::UNLIMITED,
pending_obligations: 0,
heap: RegionHeap::new(),
}),
double_resolve_count: AtomicU64::new(0),
pending_spawns: Arc::new(PendingSpawnCounter::new()),
span,
}
}
#[must_use]
pub const fn created_at(&self) -> Time {
self.created_at
}
#[inline]
#[must_use]
pub fn state(&self) -> RegionState {
self.state.load()
}
#[inline]
#[must_use]
pub fn budget(&self) -> Budget {
self.inner.read().budget
}
pub fn set_budget(&self, budget: Budget) {
self.inner.write().budget = budget;
}
#[inline]
#[must_use]
pub fn capability_budget(&self) -> CapabilityBudget {
self.inner.read().capability_budget
}
pub fn set_capability_budget(&self, capability_budget: CapabilityBudget) {
self.inner.write().capability_budget = capability_budget;
}
#[inline]
#[must_use]
pub fn limits(&self) -> RegionLimits {
self.inner.read().limits.clone()
}
pub fn set_limits(&self, limits: RegionLimits) {
self.inner.write().limits = limits;
}
#[inline]
#[must_use]
pub fn pending_obligations(&self) -> usize {
self.inner.read().pending_obligations
}
#[must_use]
pub fn cancel_reason(&self) -> Option<CancelReason> {
self.inner.read().cancel_reason.clone()
}
#[must_use]
pub fn close_outcome(&self) -> Option<TaskOutcome> {
self.inner.read().close_outcome.clone()
}
pub fn record_close_outcome(&self, outcome: TaskOutcome) {
let mut inner = self.inner.write();
inner.close_outcome = Some(match inner.close_outcome.take() {
Some(existing) => existing.join(outcome),
None => outcome,
});
}
pub fn strengthen_cancel_reason(&self, reason: CancelReason) {
let mut inner = self.inner.write();
if let Some(existing) = &mut inner.cancel_reason {
existing.strengthen(&reason);
} else {
inner.cancel_reason = Some(reason);
}
}
#[inline]
#[must_use]
pub fn child_count(&self) -> usize {
self.inner.read().children.len()
}
#[must_use]
pub fn child_ids(&self) -> Vec<RegionId> {
self.inner.read().children.clone()
}
#[inline]
pub fn copy_child_ids_into(&self, buf: &mut Vec<RegionId>) {
let inner = self.inner.read();
buf.extend_from_slice(&inner.children);
}
#[inline]
#[must_use]
pub fn task_count(&self) -> usize {
self.inner.read().tasks.len()
}
#[must_use]
pub fn task_ids(&self) -> Vec<TaskId> {
self.inner.read().tasks.clone()
}
#[inline]
pub fn copy_task_ids_into(&self, buf: &mut Vec<TaskId>) {
let inner = self.inner.read();
buf.extend_from_slice(&inner.tasks);
}
#[inline]
#[must_use]
pub fn task_ids_small(&self) -> smallvec::SmallVec<[TaskId; 8]> {
let inner = self.inner.read();
smallvec::SmallVec::from_slice(&inner.tasks)
}
#[inline]
#[must_use]
pub fn has_live_work(&self) -> bool {
let inner = self.inner.read();
!inner.children.is_empty()
|| !inner.tasks.is_empty()
|| inner.pending_obligations > 0
|| self.pending_spawns.count() > 0
}
#[inline]
#[must_use]
pub fn pending_spawn_count(&self) -> u32 {
self.pending_spawns.count()
}
#[inline]
#[must_use]
pub fn pending_spawn_handle(&self) -> Arc<PendingSpawnCounter> {
Arc::clone(&self.pending_spawns)
}
#[inline]
#[must_use]
pub fn reserve_pending_spawn(&self) -> PendingSpawnReservation {
self.pending_spawns.reserve()
}
pub fn add_child(&self, child: RegionId) -> Result<(), AdmissionError> {
if !self.state.load().can_spawn() {
return Err(AdmissionError::Closed);
}
let mut inner = self.inner.write();
if !self.state.load().can_spawn() {
return Err(AdmissionError::Closed);
}
if inner.children.contains(&child) {
return Ok(());
}
if let Some(limit) = inner.limits.max_children {
if inner.children.len() >= limit {
return Err(AdmissionError::LimitReached {
kind: AdmissionKind::Child,
limit,
live: inner.children.len(),
});
}
}
inner.children.push(child);
drop(inner);
Ok(())
}
pub fn remove_child(&self, child: RegionId) {
let mut inner = self.inner.write();
inner.children.retain(|&c| c != child);
}
fn add_task_internal(&self, task: TaskId, cleanup_task: bool) -> Result<(), AdmissionError> {
let can_admit = |state: RegionState| {
if cleanup_task {
state.can_accept_cleanup_work()
} else {
state.can_accept_work()
}
};
let state = self.state.load();
if !can_admit(state) {
return Err(AdmissionError::Closed);
}
let mut inner = self.inner.write();
let state = self.state.load();
if !can_admit(state) {
return Err(AdmissionError::Closed);
}
if inner.tasks.contains(&task) {
return Ok(());
}
let bypass_limit = cleanup_task && state == RegionState::Finalizing;
if !bypass_limit {
if let Some(limit) = inner.limits.max_tasks {
if inner.tasks.len() >= limit {
return Err(AdmissionError::LimitReached {
kind: AdmissionKind::Task,
limit,
live: inner.tasks.len(),
});
}
}
}
inner.tasks.push(task);
drop(inner);
Ok(())
}
pub fn add_task(&self, task: TaskId) -> Result<(), AdmissionError> {
self.add_task_internal(task, false)
}
pub fn add_cleanup_task(&self, task: TaskId) -> Result<(), AdmissionError> {
self.add_task_internal(task, true)
}
pub fn remove_task(&self, task: TaskId) {
let mut inner = self.inner.write();
inner.tasks.retain(|&t| t != task);
}
pub fn try_reserve_obligation(&self) -> Result<(), AdmissionError> {
if !self.state.load().can_accept_work() {
return Err(AdmissionError::Closed);
}
let mut inner = self.inner.write();
if !self.state.load().can_accept_work() {
return Err(AdmissionError::Closed);
}
if let Some(limit) = inner.limits.max_obligations {
if inner.pending_obligations >= limit {
return Err(AdmissionError::LimitReached {
kind: AdmissionKind::Obligation,
limit,
live: inner.pending_obligations,
});
}
}
inner.pending_obligations = inner.pending_obligations.saturating_add(1);
drop(inner);
Ok(())
}
pub fn resolve_obligation(&self) {
let mut inner = self.inner.write();
match inner.pending_obligations.checked_sub(1) {
Some(n) => inner.pending_obligations = n,
None => {
debug_assert!(
false,
"double-resolve detected on Region: \
resolve_obligation called when pending_obligations was 0 \
(br-asupersync-bjrqu3)"
);
self.double_resolve_count.fetch_add(1, Ordering::Relaxed);
#[cfg(feature = "tracing-integration")]
tracing::warn!(
"Region::resolve_obligation called when pending_obligations==0 \
(br-asupersync-bjrqu3) — counter saturated, double-resolve \
count incremented"
);
}
}
}
#[must_use]
pub fn double_resolve_count(&self) -> u64 {
self.double_resolve_count.load(Ordering::Relaxed)
}
pub fn add_finalizer(&self, finalizer: Finalizer) {
let mut inner = self.inner.write();
inner.finalizers.push(finalizer);
}
pub fn pop_finalizer(&self) -> Option<Finalizer> {
let mut inner = self.inner.write();
if self.state.load() != RegionState::Finalizing {
return None;
}
inner.finalizers.pop()
}
#[must_use]
pub fn finalizer_count(&self) -> usize {
self.inner.read().finalizers.len()
}
#[must_use]
pub fn finalizers_empty(&self) -> bool {
self.inner.read().finalizers.is_empty()
}
pub fn heap_alloc<T: Send + Sync + 'static>(
&self,
value: T,
) -> Result<HeapIndex, AdmissionError> {
if self.state().is_terminal() {
return Err(AdmissionError::Closed);
}
let size_hint = std::mem::size_of::<T>();
let mut inner = self.inner.write();
if self.state().is_terminal() {
return Err(AdmissionError::Closed);
}
if let Some(limit) = inner.limits.max_heap_bytes {
let live_bytes = inner.heap.stats().bytes_live;
let requested = live_bytes.saturating_add(size_hint as u64);
if requested > limit as u64 {
let live = usize::try_from(live_bytes).unwrap_or(usize::MAX);
return Err(AdmissionError::LimitReached {
kind: AdmissionKind::HeapBytes,
limit,
live,
});
}
}
Ok(inner.heap.alloc(value))
}
#[must_use]
pub fn heap_get<T>(&self, index: HeapIndex) -> Option<T>
where
T: Clone + 'static,
{
let inner = self.inner.read();
inner.heap.get::<T>(index).cloned()
}
pub fn heap_with<T: 'static, R, F: FnOnce(&T) -> R>(
&self,
index: HeapIndex,
f: F,
) -> Option<R> {
let guard = ReentryGuard::new();
if guard.is_reentrant() {
let inner = self.inner.try_read()?;
return inner.heap.get::<T>(index).map(f);
}
let inner = self.inner.read();
inner.heap.get::<T>(index).map(f)
}
#[must_use]
pub fn heap_len(&self) -> usize {
self.inner.read().heap.len()
}
#[must_use]
pub fn heap_stats(&self) -> crate::runtime::region_heap::HeapStats {
self.inner.read().heap.stats()
}
#[must_use]
pub fn is_quiescent(&self) -> bool {
let inner = self.inner.read();
inner.children.is_empty()
&& inner.tasks.is_empty()
&& inner.pending_obligations == 0
&& inner.finalizers.is_empty()
&& self.pending_spawns.count() == 0
}
pub fn cancel_request(&self, reason: CancelReason) -> bool {
let mut inner = self.inner.write();
if let Some(existing) = &mut inner.cancel_reason {
existing.strengthen(&reason);
false
} else {
inner.cancel_reason = Some(reason);
true
}
}
pub fn begin_close(&self, reason: Option<CancelReason>) -> bool {
let mut inner = self.inner.write();
if self.state.load() == RegionState::Closed {
return false;
}
if let Some(reason) = reason {
if let Some(existing) = &mut inner.cancel_reason {
existing.strengthen(&reason);
} else {
inner.cancel_reason = Some(reason);
}
}
let transitioned = self
.state
.transition(RegionState::Open, RegionState::Closing);
drop(inner);
if transitioned {
self.trace_state_change(RegionState::Closing);
}
transitioned
}
pub fn begin_drain(&self) -> bool {
let transitioned = self
.state
.transition(RegionState::Closing, RegionState::Draining);
if transitioned {
self.trace_state_change(RegionState::Draining);
}
transitioned
}
pub fn begin_finalize(&self) -> bool {
if let Some(parent_id) = self.parent {
debug_assert!(
true, "br-asupersync-mg70eb: beginning finalization with parent region present \
(region={:?}, parent={:?}) - ensure proper parent-child ordering",
self.id,
parent_id
);
}
let transitioned = self
.state
.transition(RegionState::Closing, RegionState::Finalizing)
|| self
.state
.transition(RegionState::Draining, RegionState::Finalizing);
if transitioned {
self.trace_state_change(RegionState::Finalizing);
}
transitioned
}
pub fn complete_close(&self) -> bool {
let mut inner = self.inner.write();
if self.state.load() != RegionState::Finalizing {
return false;
}
if !(inner.children.is_empty()
&& inner.tasks.is_empty()
&& inner.pending_obligations == 0
&& inner.finalizers.is_empty()
&& self.pending_spawns.count() == 0)
{
return false;
}
let default_outcome = if let Some(reason) = inner.cancel_reason.as_ref() {
crate::types::Outcome::Cancelled(reason.clone())
} else {
crate::types::Outcome::Ok(())
};
inner.close_outcome.get_or_insert(default_outcome);
let transitioned = self
.state
.transition(RegionState::Finalizing, RegionState::Closed);
if transitioned {
self.trace_state_change(RegionState::Closed);
inner.heap.reclaim_all();
let waiters = {
let mut notify = self.close_notify.lock();
notify.closed = true;
std::mem::take(&mut notify.waiters)
};
drop(inner);
for waker in waiters {
waker.wake();
}
}
transitioned
}
pub fn set_state(&self, state: RegionState) {
self.state.store(state);
self.trace_state_change(state);
}
fn trace_state_change(&self, new_state: RegionState) {
let state_name = match new_state {
RegionState::Open => "Open",
RegionState::Closing => "Closing",
RegionState::Draining => "Draining",
RegionState::Finalizing => "Finalizing",
RegionState::Closed => "Closed",
};
debug!(
parent: &self.span,
region_id = ?self.id,
state = state_name,
"region state transition"
);
self.span.record("state", state_name);
}
fn clear_heap(&self) {
let mut inner = self.inner.write();
inner.heap.reclaim_all();
}
pub fn rref_get<T: Clone + 'static>(&self, rref: &RRef<T>) -> Result<T, RRefError> {
if rref.region_id() != self.id {
return Err(RRefError::RegionMismatch {
expected: rref.region_id(),
actual: self.id,
});
}
if self.state().is_terminal() {
return Err(RRefError::RegionClosed);
}
let inner = self.inner.read();
inner
.heap
.get::<T>(rref.heap_index())
.cloned()
.ok_or(RRefError::AllocationInvalid)
}
pub fn rref_with<T: 'static, R, F: FnOnce(&T) -> R>(
&self,
rref: &RRef<T>,
f: F,
) -> Result<R, RRefError> {
if rref.region_id() != self.id {
return Err(RRefError::RegionMismatch {
expected: rref.region_id(),
actual: self.id,
});
}
if self.state().is_terminal() {
return Err(RRefError::RegionClosed);
}
let guard = ReentryGuard::new();
if guard.is_reentrant() {
let inner = self.inner.try_read().ok_or(RRefError::RegionClosed)?; return inner
.heap
.get::<T>(rref.heap_index())
.map(f)
.ok_or(RRefError::AllocationInvalid);
}
let inner = self.inner.read();
inner
.heap
.get::<T>(rref.heap_index())
.map(f)
.ok_or(RRefError::AllocationInvalid)
}
pub fn access_witness(&self) -> Result<RRefAccessWitness, RRefError> {
if self.state().is_terminal() {
return Err(RRefError::RegionClosed);
}
Ok(RRefAccessWitness::new(self.id))
}
pub fn rref_get_with<T: Clone + 'static>(
&self,
rref: &RRef<T>,
witness: RRefAccessWitness,
) -> Result<T, RRefError> {
if witness.region() != self.id {
return Err(RRefError::WrongRegion);
}
rref.validate_witness(&witness)?;
if self.state().is_terminal() {
return Err(RRefError::RegionClosed);
}
let inner = self.inner.read();
inner
.heap
.get::<T>(rref.heap_index())
.cloned()
.ok_or(RRefError::AllocationInvalid)
}
pub fn rref_with_witness<T: 'static, R, F: FnOnce(&T) -> R>(
&self,
rref: &RRef<T>,
witness: RRefAccessWitness,
f: F,
) -> Result<R, RRefError> {
if witness.region() != self.id {
return Err(RRefError::WrongRegion);
}
rref.validate_witness(&witness)?;
if self.state().is_terminal() {
return Err(RRefError::RegionClosed);
}
let inner = self.inner.read();
inner
.heap
.get::<T>(rref.heap_index())
.map(f)
.ok_or(RRefError::AllocationInvalid)
}
#[must_use]
pub fn should_begin_close(&self) -> bool {
let state = self.state();
matches!(state, RegionState::Open)
}
#[must_use]
pub fn should_begin_drain(&self) -> bool {
let state = self.state();
state == RegionState::Closing
}
#[must_use]
pub fn can_finalize(&self) -> bool {
let state = self.state();
matches!(state, RegionState::Closing | RegionState::Draining)
}
#[must_use]
pub fn can_complete_close(&self) -> bool {
let state = self.state();
state == RegionState::Finalizing
}
#[must_use]
pub fn children_closed(&self, closed: &dyn Fn(RegionId) -> bool) -> bool {
let inner = self.inner.read();
inner.children.iter().all(|child| closed(*child))
}
#[must_use]
pub fn tasks_completed(&self, completed: &dyn Fn(TaskId) -> bool) -> bool {
let inner = self.inner.read();
inner.tasks.iter().all(|task| completed(*task))
}
#[must_use]
pub fn obligations_resolved(&self) -> bool {
self.pending_obligations() == 0
}
#[must_use]
pub fn ready_to_finalize(&self, completed: &dyn Fn(TaskId) -> bool) -> bool {
let inner = self.inner.read();
inner.children.is_empty()
&& inner.tasks.iter().all(|task| completed(*task))
&& inner.pending_obligations == 0
}
pub fn apply_distributed_snapshot(
&self,
state: RegionState,
budget: Budget,
children: Vec<RegionId>,
tasks: Vec<TaskId>,
cancel_reason: Option<CancelReason>,
) {
let prev_state = self.state.load();
let mut inner = self.inner.write();
inner.budget = budget;
for child_id in children {
if !inner.children.contains(&child_id) {
inner.children.push(child_id);
}
}
for task_id in tasks {
if !inner.tasks.contains(&task_id) {
inner.tasks.push(task_id);
}
}
inner.cancel_reason = cancel_reason;
self.state.store(state);
drop(inner);
if state == RegionState::Closed && prev_state != RegionState::Closed {
self.clear_heap();
let waiters = {
let mut notify = self.close_notify.lock();
notify.closed = true;
std::mem::take(&mut notify.waiters)
};
for waker in waiters {
waker.wake();
}
}
}
}
impl RRefAccess for RegionRecord {
fn rref_get<T: Clone + 'static>(&self, rref: &RRef<T>) -> Result<T, RRefError> {
self.rref_get(rref)
}
fn rref_with<T: 'static, R, F: FnOnce(&T) -> R>(
&self,
rref: &RRef<T>,
f: F,
) -> Result<R, RRefError> {
self.rref_with(rref, f)
}
fn rref_get_with<T: Clone + 'static>(
&self,
rref: &RRef<T>,
witness: RRefAccessWitness,
) -> Result<T, RRefError> {
self.rref_get_with(rref, witness)
}
fn rref_with_witness<T: 'static, R, F: FnOnce(&T) -> R>(
&self,
rref: &RRef<T>,
witness: RRefAccessWitness,
f: F,
) -> Result<R, RRefError> {
self.rref_with_witness(rref, witness, f)
}
}
#[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 crate::record::finalizer::Finalizer;
use crate::util::ArenaIndex;
use parking_lot::Mutex;
fn test_region_id() -> RegionId {
RegionId::from_arena(ArenaIndex::new(1, 0))
}
fn rref_get_via_trait<A: RRefAccess, T: Clone + 'static>(accessor: &A, rref: &RRef<T>) -> T {
accessor.rref_get(rref).expect("trait get")
}
fn rref_with_via_trait<A: RRefAccess, T: 'static, R, F: FnOnce(&T) -> R>(
accessor: &A,
rref: &RRef<T>,
f: F,
) -> R {
accessor.rref_with(rref, f).expect("trait with")
}
#[test]
fn ready_to_finalize_requires_no_children() {
let region = RegionRecord::new(test_region_id(), None, Budget::INFINITE);
region
.add_child(RegionId::from_arena(ArenaIndex::new(2, 0)))
.expect("add child");
region
.add_task(TaskId::from_arena(ArenaIndex::new(3, 0)))
.expect("add task");
assert!(!region.ready_to_finalize(&|_task| true));
region.remove_child(RegionId::from_arena(ArenaIndex::new(2, 0)));
assert!(region.ready_to_finalize(&|_task| true));
}
fn rref_get_with_via_trait<A: RRefAccess, T: Clone + 'static>(
accessor: &A,
rref: &RRef<T>,
witness: RRefAccessWitness,
) -> T {
accessor
.rref_get_with(rref, witness)
.expect("trait get_with")
}
fn rref_with_witness_via_trait<A: RRefAccess, T: 'static, R, F: FnOnce(&T) -> R>(
accessor: &A,
rref: &RRef<T>,
witness: RRefAccessWitness,
f: F,
) -> R {
accessor
.rref_with_witness(rref, witness, f)
.expect("trait with_witness")
}
#[test]
fn region_initial_state() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
assert_eq!(region.state(), RegionState::Open);
}
#[test]
fn region_state_transitions() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
assert!(region.begin_close(None));
assert_eq!(region.state(), RegionState::Closing);
assert!(region.begin_drain());
assert_eq!(region.state(), RegionState::Draining);
assert!(region.begin_finalize());
assert_eq!(region.state(), RegionState::Finalizing);
assert!(region.complete_close());
assert_eq!(region.state(), RegionState::Closed);
}
#[test]
fn region_state_invalid_transitions() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
assert!(!region.begin_drain());
assert_eq!(region.state(), RegionState::Open);
assert!(!region.begin_finalize());
assert_eq!(region.state(), RegionState::Open);
assert!(!region.complete_close());
assert_eq!(region.state(), RegionState::Open);
}
#[test]
fn region_admission_limits() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region.set_limits(RegionLimits {
max_children: Some(1),
max_tasks: Some(2),
max_obligations: Some(1),
max_heap_bytes: None,
curve_budget: None,
});
assert!(
region
.add_child(RegionId::from_arena(ArenaIndex::new(2, 0)))
.is_ok()
);
assert!(
region
.add_child(RegionId::from_arena(ArenaIndex::new(3, 0)))
.is_err()
);
assert!(
region
.add_task(TaskId::from_arena(ArenaIndex::new(1, 0)))
.is_ok()
);
assert!(
region
.add_task(TaskId::from_arena(ArenaIndex::new(2, 0)))
.is_ok()
);
assert!(
region
.add_task(TaskId::from_arena(ArenaIndex::new(3, 0)))
.is_err()
);
assert!(region.try_reserve_obligation().is_ok());
assert!(region.try_reserve_obligation().is_err());
}
#[test]
fn region_obligation_tracking() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
assert_eq!(region.pending_obligations(), 0);
assert!(region.try_reserve_obligation().is_ok());
assert_eq!(region.pending_obligations(), 1);
region.resolve_obligation();
assert_eq!(region.pending_obligations(), 0);
}
#[test]
fn region_obligation_limit_released_after_resolve() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region.set_limits(RegionLimits {
max_obligations: Some(1),
..RegionLimits::unlimited()
});
assert!(region.try_reserve_obligation().is_ok());
assert!(matches!(
region.try_reserve_obligation(),
Err(AdmissionError::LimitReached {
kind: AdmissionKind::Obligation,
..
})
));
assert_eq!(region.pending_obligations(), 1);
region.resolve_obligation();
assert_eq!(region.pending_obligations(), 0);
assert!(region.try_reserve_obligation().is_ok());
}
#[test]
fn region_quiescence() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
assert!(region.is_quiescent());
region
.add_child(RegionId::from_arena(ArenaIndex::new(2, 0)))
.expect("add child");
assert!(!region.is_quiescent());
region.remove_child(RegionId::from_arena(ArenaIndex::new(2, 0)));
assert!(region.is_quiescent());
}
#[test]
fn region_finalizer_stack() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
let log = std::sync::Arc::new(Mutex::new(Vec::new()));
region.add_finalizer(Finalizer::Sync(Box::new({
let log_ref = log.clone();
move || log_ref.lock().push("first")
})));
region.add_finalizer(Finalizer::Sync(Box::new({
let log_ref = log.clone();
move || log_ref.lock().push("second")
})));
assert!(region.begin_close(None));
assert!(region.begin_finalize());
while let Some(finalizer) = region.pop_finalizer() {
if let Finalizer::Sync(f) = finalizer {
f();
}
}
let log = log.lock().clone();
assert_eq!(log, vec!["second", "first"]); }
#[test]
fn region_heap_alloc_and_access() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
let idx = region.heap_alloc(42u32).expect("heap alloc");
let value = region.heap_get::<u32>(idx).expect("heap get");
assert_eq!(value, 42);
let doubled = region.heap_with(idx, |v: &u32| v * 2).expect("heap with");
assert_eq!(doubled, 84);
}
#[test]
fn region_heap_bytes_limit_enforced() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
let limit = std::mem::size_of::<u32>();
region.set_limits(RegionLimits {
max_heap_bytes: Some(limit),
..RegionLimits::unlimited()
});
let _idx = region.heap_alloc(7u32).expect("heap alloc");
let err = region.heap_alloc(1u8).expect_err("heap limit enforced");
assert!(matches!(
err,
AdmissionError::LimitReached {
kind: AdmissionKind::HeapBytes,
limit: _,
live: _
}
));
}
#[test]
fn begin_close_with_reason() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
let reason = CancelReason::user("test shutdown");
assert!(region.begin_close(Some(reason.clone())));
assert_eq!(region.cancel_reason(), Some(reason));
}
#[test]
fn begin_close_on_closed_region_preserves_terminal_cancel_reason() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
let initial_reason = CancelReason::timeout();
assert!(region.begin_close(Some(initial_reason.clone())));
assert!(region.begin_finalize());
assert!(region.complete_close());
assert_eq!(region.cancel_reason(), Some(initial_reason.clone()));
assert!(!region.begin_close(Some(CancelReason::resource_unavailable())));
assert_eq!(region.cancel_reason(), Some(initial_reason));
}
#[test]
fn complete_close_defaults_close_outcome_to_ok() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
assert!(region.begin_close(None));
assert!(region.begin_finalize());
assert!(region.complete_close());
assert!(matches!(
region.close_outcome(),
Some(crate::types::Outcome::Ok(()))
));
}
#[test]
fn complete_close_defaults_close_outcome_to_cancelled_when_cancel_reason_set() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
let cancel_reason = CancelReason::shutdown();
assert!(region.begin_close(Some(cancel_reason.clone())));
assert!(region.begin_finalize());
assert!(region.complete_close());
match region.close_outcome() {
Some(crate::types::Outcome::Cancelled(observed)) => {
assert_eq!(
observed.kind, cancel_reason.kind,
"close_outcome must reflect the cancel reason set on the region"
);
}
other => panic!(
"br-asupersync-n0lthy: cancelled-but-empty region must close \
with Outcome::Cancelled(reason), not {other:?}"
),
}
}
#[test]
fn complete_close_preserves_pre_recorded_outcome_under_cancel() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
let cancel_reason = CancelReason::shutdown();
assert!(region.begin_close(Some(cancel_reason)));
region.record_close_outcome(crate::types::Outcome::Panicked(
crate::types::PanicPayload::new("finalizer panic"),
));
assert!(region.begin_finalize());
assert!(region.complete_close());
assert!(
matches!(
region.close_outcome(),
Some(crate::types::Outcome::Panicked(payload)) if payload.message() == "finalizer panic"
),
"pre-recorded Panicked outcome must survive cancel-aware default; \
got {:?}",
region.close_outcome()
);
}
#[test]
fn record_close_outcome_keeps_worst_severity() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region.record_close_outcome(crate::types::Outcome::Err(crate::error::Error::new(
crate::error::ErrorKind::Internal,
)));
region.record_close_outcome(crate::types::Outcome::Cancelled(CancelReason::timeout()));
region.record_close_outcome(crate::types::Outcome::Panicked(
crate::types::PanicPayload::new("boom"),
));
assert!(matches!(
region.close_outcome(),
Some(crate::types::Outcome::Panicked(payload)) if payload.message() == "boom"
));
}
#[test]
fn region_heap_reclaimed_on_close() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
let _idx = region.heap_alloc(42u32).expect("heap alloc");
assert_eq!(region.heap_len(), 1);
assert!(region.begin_close(None));
assert!(region.begin_finalize());
assert!(region.complete_close());
assert_eq!(region.heap_len(), 0);
}
#[test]
fn rref_invalid_after_close() {
let region_id = test_region_id();
let region = RegionRecord::new(region_id, None, Budget::default());
let index = region.heap_alloc(123u32).expect("heap alloc");
let rref = RRef::<u32>::new(region_id, index);
assert!(region.begin_close(None));
assert!(region.begin_finalize());
assert!(region.complete_close());
let err = region
.rref_get(&rref)
.expect_err("rref should be invalid after close");
assert_eq!(err, RRefError::RegionClosed);
}
#[test]
fn invalid_state_transitions_are_rejected() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
assert!(!region.begin_drain());
assert_eq!(region.state(), RegionState::Open);
assert!(!region.begin_finalize());
assert_eq!(region.state(), RegionState::Open);
assert!(!region.complete_close());
assert_eq!(region.state(), RegionState::Open);
region.begin_close(None);
region.begin_drain();
assert!(!region.complete_close());
assert_eq!(region.state(), RegionState::Draining);
}
#[test]
fn finalizer_registration() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
assert!(region.finalizers_empty());
assert_eq!(region.finalizer_count(), 0);
region.add_finalizer(Finalizer::Sync(Box::new(|| {})));
assert!(!region.finalizers_empty());
assert_eq!(region.finalizer_count(), 1);
region.add_finalizer(Finalizer::Async(Box::pin(async {})));
assert_eq!(region.finalizer_count(), 2);
}
#[test]
fn finalizer_lifo_order() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
let order = std::sync::Arc::new(Mutex::new(Vec::new()));
let o1 = order.clone();
let o2 = order.clone();
let o3 = order.clone();
region.add_finalizer(Finalizer::Sync(Box::new(move || {
o1.lock().push(1);
})));
region.add_finalizer(Finalizer::Sync(Box::new(move || {
o2.lock().push(2);
})));
region.add_finalizer(Finalizer::Sync(Box::new(move || {
o3.lock().push(3);
})));
assert!(region.begin_close(None));
assert!(region.begin_finalize());
while let Some(finalizer) = region.pop_finalizer() {
if let Finalizer::Sync(f) = finalizer {
f();
}
}
assert_eq!(*order.lock(), vec![3, 2, 1]);
}
#[test]
fn finalizer_pop_returns_none_when_empty() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
assert!(region.pop_finalizer().is_none());
region.add_finalizer(Finalizer::Sync(Box::new(|| {})));
assert!(region.begin_close(None));
assert!(region.begin_finalize());
assert!(region.pop_finalizer().is_some());
assert!(region.pop_finalizer().is_none());
}
#[test]
fn admission_rejected_when_closing() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region.begin_close(None);
assert_eq!(region.state(), RegionState::Closing);
let task = TaskId::from_arena(ArenaIndex::new(1, 0));
assert_eq!(region.add_task(task), Err(AdmissionError::Closed));
let child = RegionId::from_arena(ArenaIndex::new(1, 0));
assert_eq!(region.add_child(child), Err(AdmissionError::Closed));
assert_eq!(region.try_reserve_obligation(), Err(AdmissionError::Closed));
}
#[test]
fn admission_rejected_when_draining() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region.begin_close(None);
region.begin_drain();
assert_eq!(region.state(), RegionState::Draining);
let task = TaskId::from_arena(ArenaIndex::new(1, 0));
assert_eq!(region.add_task(task), Err(AdmissionError::Closed));
}
#[test]
fn normal_task_admission_rejected_when_finalizing() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region.begin_close(None);
assert!(region.begin_finalize()); assert_eq!(region.state(), RegionState::Finalizing);
let task = TaskId::from_arena(ArenaIndex::new(1, 0));
assert_eq!(region.add_task(task), Err(AdmissionError::Closed));
assert!(region.add_cleanup_task(task).is_ok());
assert_eq!(region.try_reserve_obligation(), Err(AdmissionError::Closed));
}
#[test]
fn cleanup_task_bypasses_task_limit_when_finalizing() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region.set_limits(RegionLimits {
max_tasks: Some(0),
..RegionLimits::unlimited()
});
region.begin_close(None);
assert!(region.begin_finalize());
let task = TaskId::from_arena(ArenaIndex::new(1, 0));
assert_eq!(region.add_task(task), Err(AdmissionError::Closed));
assert!(region.add_cleanup_task(task).is_ok());
}
#[test]
fn child_admission_rejected_when_finalizing() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region.begin_close(None);
assert!(region.begin_finalize()); assert_eq!(region.state(), RegionState::Finalizing);
let child = RegionId::from_arena(ArenaIndex::new(1, 0));
assert_eq!(region.add_child(child), Err(AdmissionError::Closed));
}
#[test]
fn admission_rejected_when_closed() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region.begin_close(None);
region.begin_finalize();
region.complete_close();
assert_eq!(region.state(), RegionState::Closed);
let task = TaskId::from_arena(ArenaIndex::new(1, 0));
assert_eq!(region.add_task(task), Err(AdmissionError::Closed));
}
#[test]
fn add_task_idempotent() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region.set_limits(RegionLimits {
max_tasks: Some(1),
..RegionLimits::unlimited()
});
let task = TaskId::from_arena(ArenaIndex::new(1, 0));
assert!(region.add_task(task).is_ok());
assert!(region.add_task(task).is_ok());
assert_eq!(region.task_ids().len(), 1);
}
#[test]
fn add_child_idempotent() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region.set_limits(RegionLimits {
max_children: Some(1),
..RegionLimits::unlimited()
});
let child = RegionId::from_arena(ArenaIndex::new(1, 0));
assert!(region.add_child(child).is_ok());
assert!(region.add_child(child).is_ok());
assert_eq!(region.child_ids().len(), 1);
}
#[test]
fn remove_task_frees_slot() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region.set_limits(RegionLimits {
max_tasks: Some(1),
..RegionLimits::unlimited()
});
let task1 = TaskId::from_arena(ArenaIndex::new(1, 0));
let task2 = TaskId::from_arena(ArenaIndex::new(2, 0));
assert!(region.add_task(task1).is_ok());
assert!(region.add_task(task2).is_err());
region.remove_task(task1);
assert!(region.add_task(task2).is_ok());
}
#[test]
fn remove_child_frees_slot() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region.set_limits(RegionLimits {
max_children: Some(1),
..RegionLimits::unlimited()
});
let child1 = RegionId::from_arena(ArenaIndex::new(1, 0));
let child2 = RegionId::from_arena(ArenaIndex::new(2, 0));
assert!(region.add_child(child1).is_ok());
assert!(region.add_child(child2).is_err());
region.remove_child(child1);
assert!(region.add_child(child2).is_ok());
}
#[test]
fn unlimited_admits_many_tasks() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
assert_eq!(region.limits(), RegionLimits::UNLIMITED);
for i in 0..100 {
let task = TaskId::from_arena(ArenaIndex::new(i, 0));
assert!(region.add_task(task).is_ok());
}
assert_eq!(region.task_ids().len(), 100);
}
#[test]
fn unlimited_admits_many_obligations() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
for _ in 0..100 {
assert!(region.try_reserve_obligation().is_ok());
}
assert_eq!(region.pending_obligations(), 100);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "double-resolve detected")]
fn resolve_obligation_panics_on_unpaired_resolve_in_debug() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
assert_eq!(region.pending_obligations(), 0);
region.resolve_obligation();
}
#[test]
fn admission_error_carries_exact_counts() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region.set_limits(RegionLimits {
max_tasks: Some(3),
..RegionLimits::unlimited()
});
for i in 0..3 {
let task = TaskId::from_arena(ArenaIndex::new(i, 0));
assert!(region.add_task(task).is_ok());
}
let overflow_task = TaskId::from_arena(ArenaIndex::new(99, 0));
let err = region
.add_task(overflow_task)
.expect_err("expected admission error");
match err {
AdmissionError::LimitReached { kind, limit, live } => {
assert_eq!(kind, AdmissionKind::Task);
assert_eq!(limit, 3);
assert_eq!(live, 3);
}
AdmissionError::Closed => unreachable!("expected LimitReached, got Closed"),
}
}
#[test]
fn has_live_work_tracks_all_categories() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
assert!(!region.has_live_work());
let task = TaskId::from_arena(ArenaIndex::new(1, 0));
assert!(region.add_task(task).is_ok());
assert!(region.has_live_work());
region.remove_task(task);
assert!(!region.has_live_work());
let child = RegionId::from_arena(ArenaIndex::new(1, 0));
assert!(region.add_child(child).is_ok());
assert!(region.has_live_work());
region.remove_child(child);
assert!(!region.has_live_work());
assert!(region.try_reserve_obligation().is_ok());
assert!(region.has_live_work());
region.resolve_obligation();
assert!(!region.has_live_work());
}
#[test]
fn heap_admits_up_to_exact_limit() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
let u32_size = std::mem::size_of::<u32>();
region.set_limits(RegionLimits {
max_heap_bytes: Some(u32_size * 2),
..RegionLimits::unlimited()
});
assert!(region.heap_alloc(1u32).is_ok());
assert!(region.heap_alloc(2u32).is_ok());
let err = region.heap_alloc(3u32).expect_err("heap limit");
assert!(matches!(
err,
AdmissionError::LimitReached {
kind: AdmissionKind::HeapBytes,
..
}
));
}
#[test]
fn close_prevents_subsequent_admission() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
let task1 = TaskId::from_arena(ArenaIndex::new(1, 0));
assert!(region.add_task(task1).is_ok());
region.begin_close(None);
let task2 = TaskId::from_arena(ArenaIndex::new(2, 0));
assert_eq!(region.add_task(task2), Err(AdmissionError::Closed));
let child = RegionId::from_arena(ArenaIndex::new(1, 0));
assert_eq!(region.add_child(child), Err(AdmissionError::Closed));
assert_eq!(region.try_reserve_obligation(), Err(AdmissionError::Closed));
}
#[test]
fn saturated_limit_rejects_all_types() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region.set_limits(RegionLimits {
max_tasks: Some(2),
max_children: Some(2),
max_obligations: Some(2),
max_heap_bytes: Some(std::mem::size_of::<u64>()),
curve_budget: None,
});
let t1 = TaskId::from_arena(ArenaIndex::new(1, 0));
let t2 = TaskId::from_arena(ArenaIndex::new(2, 0));
assert!(region.add_task(t1).is_ok());
assert!(region.add_task(t2).is_ok());
let c1 = RegionId::from_arena(ArenaIndex::new(1, 0));
let c2 = RegionId::from_arena(ArenaIndex::new(2, 0));
assert!(region.add_child(c1).is_ok());
assert!(region.add_child(c2).is_ok());
assert!(region.try_reserve_obligation().is_ok());
assert!(region.try_reserve_obligation().is_ok());
assert!(region.heap_alloc(42u64).is_ok());
let t3 = TaskId::from_arena(ArenaIndex::new(3, 0));
assert!(matches!(
region.add_task(t3),
Err(AdmissionError::LimitReached {
kind: AdmissionKind::Task,
..
})
));
let c3 = RegionId::from_arena(ArenaIndex::new(3, 0));
assert!(matches!(
region.add_child(c3),
Err(AdmissionError::LimitReached {
kind: AdmissionKind::Child,
..
})
));
assert!(matches!(
region.try_reserve_obligation(),
Err(AdmissionError::LimitReached {
kind: AdmissionKind::Obligation,
..
})
));
assert!(matches!(
region.heap_alloc(1u8),
Err(AdmissionError::LimitReached {
kind: AdmissionKind::HeapBytes,
..
})
));
}
#[test]
fn heap_stats_return_to_zero_single_region() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region
.heap_alloc(1u32)
.expect("should allocate u32 for heap stats test");
region
.heap_alloc(2u64)
.expect("should allocate u64 for heap stats test");
region
.heap_alloc("hello".to_string())
.expect("should allocate string for heap stats test");
assert_eq!(region.heap_stats().live, 3);
assert_eq!(region.heap_stats().allocations, 3);
assert!(region.begin_close(None));
assert!(region.begin_finalize());
assert!(region.complete_close());
assert_eq!(region.heap_stats().live, 0);
assert_eq!(region.heap_stats().reclaimed, 3);
assert_eq!(region.heap_len(), 0);
}
#[test]
fn multi_region_hierarchy_reclamation() {
let parent_id = RegionId::from_arena(ArenaIndex::new(100, 0));
let child1_id = RegionId::from_arena(ArenaIndex::new(101, 0));
let child2_id = RegionId::from_arena(ArenaIndex::new(102, 0));
let parent = RegionRecord::new(parent_id, None, Budget::default());
let child1 = RegionRecord::new(child1_id, Some(parent_id), Budget::default());
let child2 = RegionRecord::new(child2_id, Some(parent_id), Budget::default());
parent
.add_child(child1_id)
.expect("should add first child to parent");
parent
.add_child(child2_id)
.expect("should add second child to parent");
parent
.heap_alloc(10u32)
.expect("should allocate in parent region");
parent
.heap_alloc(20u32)
.expect("should allocate second item in parent");
child1
.heap_alloc(30u64)
.expect("should allocate in child1 region");
child2
.heap_alloc(40u64)
.expect("should allocate first item in child2");
child2
.heap_alloc(50u64)
.expect("should allocate second item in child2");
assert_eq!(parent.heap_stats().live, 2);
assert_eq!(child1.heap_stats().live, 1);
assert_eq!(child2.heap_stats().live, 2);
assert!(child1.begin_close(None));
assert!(child1.begin_finalize());
assert!(child1.complete_close());
assert_eq!(child1.heap_stats().live, 0);
assert_eq!(child1.heap_stats().reclaimed, 1);
assert!(child2.begin_close(None));
assert!(child2.begin_finalize());
assert!(child2.complete_close());
assert_eq!(child2.heap_stats().live, 0);
assert_eq!(child2.heap_stats().reclaimed, 2);
assert_eq!(parent.heap_stats().live, 2);
parent.remove_child(child1_id);
parent.remove_child(child2_id);
assert!(parent.begin_close(None));
assert!(parent.begin_finalize());
assert!(parent.complete_close());
assert_eq!(parent.heap_stats().live, 0);
assert_eq!(parent.heap_stats().reclaimed, 2);
}
#[test]
fn heap_alloc_allowed_during_cleanup_phases() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region
.heap_alloc(42u32)
.expect("should allocate during normal phase");
assert_eq!(region.heap_len(), 1);
assert!(region.begin_close(None));
region
.heap_alloc(99u32)
.expect("should allocate during closing phase");
assert_eq!(region.heap_len(), 2);
assert!(region.begin_finalize());
region
.heap_alloc(200u32)
.expect("should allocate during finalizing phase");
assert_eq!(region.heap_len(), 3);
assert!(region.complete_close());
assert_eq!(region.heap_len(), 0);
}
#[test]
fn heap_alloc_rejected_when_closed() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
assert!(region.begin_close(None));
assert!(region.begin_finalize());
assert!(region.complete_close());
assert_eq!(region.heap_alloc(42u32), Err(AdmissionError::Closed));
}
#[test]
fn heap_reclamation_timing_matches_state_machine() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region
.heap_alloc(1u32)
.expect("should allocate u32 for state machine test");
region
.heap_alloc(2u64)
.expect("should allocate u64 for state machine test");
assert!(region.begin_close(None));
assert_eq!(region.heap_len(), 2);
assert_eq!(region.state(), RegionState::Closing);
assert!(region.begin_drain());
assert_eq!(region.heap_len(), 2);
assert_eq!(region.state(), RegionState::Draining);
assert!(region.begin_finalize());
assert_eq!(region.heap_len(), 2);
assert_eq!(region.state(), RegionState::Finalizing);
assert!(region.complete_close());
assert_eq!(region.heap_len(), 0);
assert_eq!(region.state(), RegionState::Closed);
}
#[test]
fn heap_stats_consistent_through_lifecycle() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region.heap_alloc(42u32).unwrap();
region.heap_alloc(std::f64::consts::PI).unwrap();
region.heap_alloc(vec![1u8, 2, 3]).unwrap();
let stats_before = region.heap_stats();
assert_eq!(stats_before.allocations, 3);
assert_eq!(stats_before.live, 3);
assert_eq!(stats_before.reclaimed, 0);
assert!(region.begin_close(None));
assert!(region.begin_finalize());
assert!(region.complete_close());
let stats_after = region.heap_stats();
assert_eq!(stats_after.allocations, 3);
assert_eq!(stats_after.live, 0);
assert_eq!(stats_after.reclaimed, 3);
}
#[test]
fn rref_accessible_through_finalizing_invalid_after_closed() {
let region_id = test_region_id();
let region = RegionRecord::new(region_id, None, Budget::default());
let idx = region.heap_alloc(99u32).unwrap();
let rref = RRef::<u32>::new(region_id, idx);
assert_eq!(region.rref_get(&rref).unwrap(), 99);
assert!(region.begin_close(None));
assert_eq!(region.rref_get(&rref).unwrap(), 99);
assert!(region.begin_drain());
assert_eq!(region.rref_get(&rref).unwrap(), 99);
assert!(region.begin_finalize());
assert_eq!(region.rref_get(&rref).unwrap(), 99);
assert!(region.complete_close());
let err = region.rref_get(&rref).expect_err("invalid after close");
assert_eq!(err, RRefError::RegionClosed);
}
#[test]
fn complete_close_is_idempotent_for_reclamation() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region
.heap_alloc(1u32)
.expect("should allocate first item for reclamation test");
region
.heap_alloc(2u32)
.expect("should allocate second item for reclamation test");
assert!(region.begin_close(None));
assert!(region.begin_finalize());
assert!(region.complete_close());
assert_eq!(region.heap_stats().live, 0);
assert_eq!(region.heap_stats().reclaimed, 2);
assert!(!region.complete_close());
assert_eq!(region.heap_stats().reclaimed, 2);
}
#[test]
fn interleaved_add_remove_never_over_admits() {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
region.set_limits(RegionLimits {
max_tasks: Some(3),
..RegionLimits::unlimited()
});
for round in 0..10u32 {
let base = round * 3;
let a = TaskId::from_arena(ArenaIndex::new(base, 0));
let b = TaskId::from_arena(ArenaIndex::new(base + 1, 0));
let c = TaskId::from_arena(ArenaIndex::new(base + 2, 0));
assert!(region.add_task(a).is_ok());
assert!(region.add_task(b).is_ok());
assert!(region.add_task(c).is_ok());
let overflow = TaskId::from_arena(ArenaIndex::new(base + 3, 0));
assert!(region.add_task(overflow).is_err());
region.remove_task(a);
region.remove_task(b);
region.remove_task(c);
assert_eq!(region.task_ids().len(), 0);
}
}
#[test]
fn access_witness_available_while_open() {
let region = RegionRecord::new(test_region_id(), None, Budget::INFINITE);
let witness = region.access_witness();
assert!(witness.is_ok());
assert_eq!(witness.unwrap().region(), test_region_id());
}
#[test]
fn access_witness_available_through_closing_phases() {
let region = RegionRecord::new(test_region_id(), None, Budget::INFINITE);
assert!(region.access_witness().is_ok());
region.begin_close(None);
assert!(region.access_witness().is_ok());
region.begin_drain();
assert!(region.access_witness().is_ok());
region.begin_finalize();
assert!(region.access_witness().is_ok());
}
#[test]
fn access_witness_denied_after_close() {
let region = RegionRecord::new(test_region_id(), None, Budget::INFINITE);
region.begin_close(None);
region.begin_drain();
region.begin_finalize();
region.complete_close();
let witness = region.access_witness();
assert!(witness.is_err());
assert_eq!(witness.unwrap_err(), RRefError::RegionClosed);
}
#[test]
fn witness_gated_get_succeeds_with_matching_region() {
let rid = test_region_id();
let region = RegionRecord::new(rid, None, Budget::INFINITE);
let index = region.heap_alloc(42u32).expect("heap alloc");
let rref = RRef::<u32>::new(rid, index);
let witness = region.access_witness().expect("witness");
let value = region.rref_get_with(&rref, witness).expect("get_with");
assert_eq!(value, 42);
}
#[test]
fn witness_gated_with_succeeds_with_matching_region() {
let rid = test_region_id();
let region = RegionRecord::new(rid, None, Budget::INFINITE);
let index = region.heap_alloc("hello".to_string()).expect("heap alloc");
let rref = RRef::<String>::new(rid, index);
let witness = region.access_witness().expect("witness");
let len = region
.rref_with_witness(&rref, witness, String::len)
.expect("with_witness");
assert_eq!(len, 5);
}
#[test]
fn witness_from_wrong_region_rejected() {
let rid_a = test_region_id();
let rid_b = RegionId::from_arena(ArenaIndex::new(99, 0));
let region_a = RegionRecord::new(rid_a, None, Budget::INFINITE);
let region_b = RegionRecord::new(rid_b, None, Budget::INFINITE);
let index = region_a.heap_alloc(7u32).expect("heap alloc");
let rref = RRef::<u32>::new(rid_a, index);
let wrong_witness = region_b.access_witness().expect("witness");
let err = region_a.rref_get_with(&rref, wrong_witness);
assert_eq!(err.unwrap_err(), RRefError::WrongRegion);
}
#[test]
fn witness_rref_region_mismatch_rejected() {
let rid_a = test_region_id();
let rid_b = RegionId::from_arena(ArenaIndex::new(99, 0));
let region_a = RegionRecord::new(rid_a, None, Budget::INFINITE);
let index = region_a.heap_alloc(7u32).expect("heap alloc");
let rref = RRef::<u32>::new(rid_b, index);
let witness = region_a.access_witness().expect("witness");
let err = region_a.rref_get_with(&rref, witness);
assert_eq!(err.unwrap_err(), RRefError::WrongRegion);
}
#[test]
fn stale_witness_rejected_after_close() {
let rid = test_region_id();
let region = RegionRecord::new(rid, None, Budget::INFINITE);
let index = region.heap_alloc(42u32).expect("heap alloc");
let rref = RRef::<u32>::new(rid, index);
let witness = region.access_witness().expect("witness");
region.begin_close(None);
region.begin_drain();
region.begin_finalize();
region.complete_close();
let err = region.rref_get_with(&rref, witness);
assert_eq!(err.unwrap_err(), RRefError::RegionClosed);
}
#[test]
fn rref_access_trait_get_works() {
let rid = test_region_id();
let region = RegionRecord::new(rid, None, Budget::INFINITE);
let index = region.heap_alloc(99i64).expect("heap alloc");
let rref = RRef::<i64>::new(rid, index);
let value = rref_get_via_trait(®ion, &rref);
assert_eq!(value, 99);
}
#[test]
fn rref_access_trait_with_works() {
let rid = test_region_id();
let region = RegionRecord::new(rid, None, Budget::INFINITE);
let index = region.heap_alloc(vec![1, 2, 3]).expect("heap alloc");
let rref = RRef::<Vec<i32>>::new(rid, index);
let len = rref_with_via_trait(®ion, &rref, Vec::len);
assert_eq!(len, 3);
}
#[test]
fn rref_access_trait_witness_methods_work() {
let rid = test_region_id();
let region = RegionRecord::new(rid, None, Budget::INFINITE);
let index = region
.heap_alloc("witness".to_string())
.expect("heap alloc");
let rref = RRef::<String>::new(rid, index);
let witness = region.access_witness().expect("witness");
let value = rref_get_with_via_trait(®ion, &rref, witness);
assert_eq!(value, "witness");
let len = rref_with_witness_via_trait(®ion, &rref, witness, String::len);
assert_eq!(len, 7);
}
#[test]
fn admission_kind_debug_clone_copy_eq() {
let k = AdmissionKind::Task;
let dbg = format!("{k:?}");
assert!(dbg.contains("Task"), "{dbg}");
let copied: AdmissionKind = k;
let cloned = k;
assert_eq!(copied, cloned);
assert_ne!(k, AdmissionKind::Child);
}
#[test]
fn admission_error_debug_clone_copy_eq() {
let e = AdmissionError::Closed;
let dbg = format!("{e:?}");
assert!(dbg.contains("Closed"), "{dbg}");
let copied: AdmissionError = e;
let cloned = e;
assert_eq!(copied, cloned);
let e2 = AdmissionError::LimitReached {
kind: AdmissionKind::HeapBytes,
limit: 1024,
live: 1024,
};
let dbg2 = format!("{e2:?}");
assert!(dbg2.contains("LimitReached"), "{dbg2}");
assert_ne!(e, e2);
}
#[test]
fn region_limits_debug_clone_default_eq() {
let l = RegionLimits::default();
assert_eq!(l, RegionLimits::UNLIMITED);
let dbg = format!("{l:?}");
assert!(dbg.contains("RegionLimits"), "{dbg}");
let cloned = l.clone();
assert_eq!(l, cloned);
}
#[test]
fn obligation_bounded_by_region_limit() {
crate::test_utils::init_test_logging();
crate::test_phase!("obligation_bounded_by_region_limit");
let region = RegionRecord::new(test_region_id(), None, Budget::default());
let bound: usize = 5;
region.set_limits(RegionLimits {
max_obligations: Some(bound),
..RegionLimits::unlimited()
});
for i in 0..bound {
assert!(
region.try_reserve_obligation().is_ok(),
"reserve {i} should succeed within bound {bound}"
);
}
assert_eq!(region.pending_obligations(), bound);
let err = region
.try_reserve_obligation()
.expect_err("expected rejection at bound");
match err {
AdmissionError::LimitReached { kind, limit, live } => {
assert_eq!(kind, AdmissionKind::Obligation);
assert_eq!(limit, bound);
assert_eq!(live, bound);
}
AdmissionError::Closed => unreachable!("expected LimitReached, got Closed"),
}
region.resolve_obligation();
assert_eq!(region.pending_obligations(), bound - 1);
assert!(
region.try_reserve_obligation().is_ok(),
"should succeed after resolving one"
);
assert_eq!(region.pending_obligations(), bound);
}
#[test]
fn metamorphic_region_state_monotone() {
crate::test_utils::init_test_logging();
crate::test_phase!("metamorphic_region_state_monotone");
use std::path::Path;
let metamorphic_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/metamorphic/region_state_monotone.rs");
if !metamorphic_path.exists() {
run_inline_metamorphic_tests();
return;
}
run_inline_metamorphic_tests();
}
fn run_inline_metamorphic_tests() {
let mut passed = 0;
let failed = 0;
{
let region = RegionRecord::new(test_region_id(), None, Budget::default());
let initial_numeric = region.state().as_u8();
assert!(region.begin_close(None));
let after_close = region.state().as_u8();
assert!(
after_close > initial_numeric,
"Open→Closing should increase numeric value"
);
assert!(region.begin_drain());
let after_drain = region.state().as_u8();
assert!(
after_drain > after_close,
"Closing→Draining should increase numeric value"
);
assert!(region.begin_finalize());
let after_finalize = region.state().as_u8();
assert!(
after_finalize > after_drain,
"Draining→Finalizing should increase numeric value"
);
passed += 1;
}
{
let region = RegionRecord::new(test_region_id(), None, Budget::default());
assert!(region.begin_close(None));
assert_eq!(region.state(), RegionState::Closing);
let after_close = region.state().as_u8();
assert!(region.begin_drain());
assert!(
region.state().as_u8() >= after_close,
"Closing→Draining must not move backward"
);
assert_eq!(region.state(), RegionState::Draining);
assert!(!region.begin_close(None));
assert!(region.begin_finalize());
assert_eq!(region.state(), RegionState::Finalizing);
assert!(!region.begin_close(None));
assert!(!region.begin_drain());
assert_eq!(region.state(), RegionState::Finalizing);
passed += 1;
}
{
let region = RegionRecord::new(test_region_id(), None, Budget::default());
assert!(region.begin_close(None));
assert!(region.begin_finalize()); assert!(region.complete_close());
assert_eq!(region.state(), RegionState::Closed);
assert!(!region.begin_close(None));
assert!(!region.begin_drain());
assert!(!region.begin_finalize());
assert!(!region.complete_close());
assert_eq!(region.state(), RegionState::Closed);
passed += 1;
}
{
let region = RegionRecord::new(test_region_id(), None, Budget::default());
let initial_numeric = region.state().as_u8();
assert!(region.begin_close(None));
assert!(region.begin_finalize()); let final_numeric = region.state().as_u8();
assert!(
final_numeric > initial_numeric,
"Skip transition should preserve monotonic ordering"
);
passed += 1;
}
{
let region = RegionRecord::new(test_region_id(), None, Budget::default());
assert!(!region.begin_drain(), "Cannot drain from Open state");
assert!(
!region.complete_close(),
"Cannot complete_close from Open state"
);
assert_eq!(region.state(), RegionState::Open);
assert!(region.begin_close(None));
assert!(region.begin_drain());
assert_eq!(region.state(), RegionState::Draining);
assert!(
!region.complete_close(),
"Cannot complete_close from Draining state"
);
assert_eq!(region.state(), RegionState::Draining);
passed += 1;
}
{
let states = [
(RegionState::Open, 0),
(RegionState::Closing, 1),
(RegionState::Draining, 2),
(RegionState::Finalizing, 3),
(RegionState::Closed, 4),
];
for &(state, expected_numeric) in &states {
assert_eq!(
state.as_u8(),
expected_numeric,
"State {:?} should have numeric value {}",
state,
expected_numeric
);
let decoded = RegionState::from_u8(expected_numeric).expect("valid state");
assert_eq!(
decoded, state,
"Numeric value {} should decode to {:?}",
expected_numeric, state
);
}
passed += 1;
}
println!(
"🧪 Metamorphic tests completed: {} passed, {} failed",
passed, failed
);
assert_eq!(failed, 0, "All metamorphic tests should pass");
assert!(passed >= 6, "Should have at least 6 metamorphic relations");
}
#[test]
fn proptest_region_state_monotone() {
use proptest::prelude::*;
proptest!(|(transitions in prop::collection::vec(0u8..=3, 1..=10))| {
let region = RegionRecord::new(test_region_id(), None, Budget::default());
let mut prev_numeric = 0u8;
for &transition_type in &transitions {
let current_state = region.state();
let _result = match transition_type {
0 if current_state == RegionState::Open => region.begin_close(None),
1 if current_state == RegionState::Closing => region.begin_drain(),
2 if matches!(current_state, RegionState::Closing | RegionState::Draining) =>
region.begin_finalize(),
3 if current_state == RegionState::Finalizing => region.complete_close(),
_ => false, };
let new_numeric = region.state().as_u8();
prop_assert!(new_numeric >= prev_numeric,
"Monotonicity violation: {} → {} in sequence {:?}",
prev_numeric, new_numeric, transitions);
prev_numeric = new_numeric;
}
});
}
#[test]
fn heap_with_panic_safety() {
let region = RegionRecord::new(test_region_id(), None, Budget::INFINITE);
let index = region.heap_alloc(42i32).expect("heap alloc");
let result = region.heap_with(index, |val: &i32| *val * 2);
assert_eq!(result, Some(84));
let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
region.heap_with(index, |_: &i32| panic!("test panic"))
}));
assert!(panic_result.is_err());
let result = region.heap_with(index, |val: &i32| *val + 1);
assert_eq!(result, Some(43));
let result = region.heap_with(index, |_val: &i32| {
region.heap_with(index, |inner_val: &i32| *inner_val)
});
assert_eq!(result, Some(Some(42)));
}
#[test]
fn rref_with_panic_safety() {
let region = RegionRecord::new(test_region_id(), None, Budget::INFINITE);
let index = region.heap_alloc("test".to_string()).expect("heap alloc");
let rref = RRef::<String>::new(region.id, index);
let result = region.rref_with(&rref, |val| val.len());
assert_eq!(result, Ok(4));
let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
region.rref_with(&rref, |_| panic!("test panic"))
}));
assert!(panic_result.is_err());
let result = region.rref_with(&rref, |val| val.chars().count());
assert_eq!(result, Ok(4));
let result = region.rref_with(&rref, |_val: &String| {
region.rref_with(&rref, |inner_val: &String| inner_val.len())
});
assert_eq!(result, Ok(Ok(4)));
}
#[test]
fn apply_distributed_snapshot_is_commutative_and_associative() {
use std::collections::BTreeSet;
fn snapshot_apply(r: &RegionRecord, children: Vec<RegionId>, tasks: Vec<TaskId>) {
r.apply_distributed_snapshot(
RegionState::Open,
Budget::INFINITE,
children,
tasks,
None,
);
}
fn ids_set<I: Ord>(v: Vec<I>) -> BTreeSet<I> {
v.into_iter().collect()
}
let rid = |n: u32| RegionId::from_arena(ArenaIndex::new(n, 0));
let tid = |n: u32| TaskId::from_arena(ArenaIndex::new(n, 0));
let snap_a = (vec![rid(101), rid(102)], vec![tid(201), tid(202)]);
let snap_b = (vec![rid(103), rid(104)], vec![tid(203), tid(204)]);
let snap_c = (vec![rid(105), rid(106)], vec![tid(205), tid(206)]);
let region_ab = RegionRecord::new(test_region_id(), None, Budget::INFINITE);
snapshot_apply(®ion_ab, snap_a.0.clone(), snap_a.1.clone());
snapshot_apply(®ion_ab, snap_b.0.clone(), snap_b.1.clone());
let region_ba = RegionRecord::new(test_region_id(), None, Budget::INFINITE);
snapshot_apply(®ion_ba, snap_b.0.clone(), snap_b.1.clone());
snapshot_apply(®ion_ba, snap_a.0.clone(), snap_a.1.clone());
assert_eq!(
ids_set(region_ab.child_ids()),
ids_set(region_ba.child_ids()),
"G-set union of children must be commutative"
);
assert_eq!(
ids_set(region_ab.task_ids()),
ids_set(region_ba.task_ids()),
"G-set union of tasks must be commutative"
);
let region_abc_left = RegionRecord::new(test_region_id(), None, Budget::INFINITE);
snapshot_apply(®ion_abc_left, snap_a.0.clone(), snap_a.1.clone());
snapshot_apply(®ion_abc_left, snap_b.0.clone(), snap_b.1.clone());
snapshot_apply(®ion_abc_left, snap_c.0.clone(), snap_c.1.clone());
let region_abc_right = RegionRecord::new(test_region_id(), None, Budget::INFINITE);
snapshot_apply(®ion_abc_right, snap_b.0.clone(), snap_b.1.clone());
snapshot_apply(®ion_abc_right, snap_c.0.clone(), snap_c.1.clone());
snapshot_apply(®ion_abc_right, snap_a.0.clone(), snap_a.1.clone());
assert_eq!(
ids_set(region_abc_left.child_ids()),
ids_set(region_abc_right.child_ids()),
"G-set union of children must be associative"
);
assert_eq!(
ids_set(region_abc_left.task_ids()),
ids_set(region_abc_right.task_ids()),
"G-set union of tasks must be associative"
);
let region_aa = RegionRecord::new(test_region_id(), None, Budget::INFINITE);
snapshot_apply(®ion_aa, snap_a.0.clone(), snap_a.1.clone());
snapshot_apply(®ion_aa, snap_a.0.clone(), snap_a.1.clone());
assert_eq!(
ids_set(region_aa.child_ids()),
ids_set(snap_a.0.clone()),
"G-set union must be idempotent on children"
);
assert_eq!(
ids_set(region_aa.task_ids()),
ids_set(snap_a.1.clone()),
"G-set union must be idempotent on tasks"
);
let region_local = RegionRecord::new(test_region_id(), None, Budget::INFINITE);
region_local.add_child(rid(101)).expect("add local child 1");
region_local.add_child(rid(102)).expect("add local child 2");
region_local.add_task(tid(201)).expect("add local task 1");
region_local.add_task(tid(202)).expect("add local task 2");
snapshot_apply(®ion_local, snap_b.0.clone(), snap_b.1.clone());
let local_children = ids_set(region_local.child_ids());
let local_tasks = ids_set(region_local.task_ids());
let expected_children: BTreeSet<_> = [rid(101), rid(102), rid(103), rid(104)]
.into_iter()
.collect();
let expected_tasks: BTreeSet<_> = [tid(201), tid(202), tid(203), tid(204)]
.into_iter()
.collect();
assert_eq!(
local_children, expected_children,
"remote snapshot must NOT silently drop local children (silent-loss regression)"
);
assert_eq!(
local_tasks, expected_tasks,
"remote snapshot must NOT silently drop local tasks (silent-loss regression)"
);
}
#[test]
fn resolve_obligation_double_resolve_increments_counter_in_release() {
if cfg!(debug_assertions) {
return;
}
let region = RegionRecord::new(RegionId::new_for_test(1, 0), None, Budget::default());
region
.try_reserve_obligation()
.expect("reservation should succeed on Open region");
region.resolve_obligation();
assert_eq!(region.pending_obligations(), 0);
assert_eq!(region.double_resolve_count(), 0);
region.resolve_obligation();
assert_eq!(region.pending_obligations(), 0);
assert_eq!(
region.double_resolve_count(),
1,
"double-resolve must increment the counter (release-mode behavior)"
);
}
#[test]
fn admission_after_begin_close_is_rejected() {
let region = RegionRecord::new(RegionId::new_for_test(2, 0), None, Budget::default());
region
.add_child(RegionId::new_for_test(3, 0))
.expect("child admission should succeed pre-close");
region
.add_task(TaskId::new_for_test(1, 0))
.expect("task admission should succeed pre-close");
let closed = region.begin_close(None);
assert!(closed, "begin_close should transition Open -> Closing");
assert!(matches!(
region.add_child(RegionId::new_for_test(4, 0)),
Err(AdmissionError::Closed)
));
assert!(matches!(
region.add_task(TaskId::new_for_test(2, 0)),
Err(AdmissionError::Closed)
));
}
#[test]
fn pending_spawn_reservation_increments_and_releases_on_drop() {
let region = RegionRecord::new(RegionId::new_for_test(10, 0), None, Budget::default());
assert_eq!(region.pending_spawn_count(), 0);
let r1 = region.reserve_pending_spawn();
let r2 = region.reserve_pending_spawn();
assert_eq!(region.pending_spawn_count(), 2);
drop(r1);
assert_eq!(region.pending_spawn_count(), 1);
drop(r2);
assert_eq!(region.pending_spawn_count(), 0);
assert_eq!(region.pending_spawn_handle().underflow_count(), 0);
}
#[test]
fn pending_spawn_blocks_quiescence_and_live_work_predicates() {
let region = RegionRecord::new(RegionId::new_for_test(11, 0), None, Budget::default());
assert!(region.is_quiescent());
assert!(!region.has_live_work());
let reservation = region.reserve_pending_spawn();
assert!(
!region.is_quiescent(),
"pending spawn must block quiescence"
);
assert!(region.has_live_work(), "pending spawn is live work");
drop(reservation);
assert!(region.is_quiescent());
assert!(!region.has_live_work());
}
#[test]
fn pending_spawn_blocks_complete_close_until_released() {
let region = RegionRecord::new(RegionId::new_for_test(12, 0), None, Budget::default());
let reservation = region.reserve_pending_spawn();
assert!(region.begin_close(None));
assert!(region.begin_finalize());
assert!(
!region.complete_close(),
"complete_close must refuse with pending spawns"
);
assert_eq!(region.state(), RegionState::Finalizing);
drop(reservation);
assert!(
region.complete_close(),
"complete_close must succeed once pending spawns drain"
);
assert_eq!(region.state(), RegionState::Closed);
}
#[test]
fn pending_spawn_handle_is_shared_with_detached_producers() {
let region = RegionRecord::new(RegionId::new_for_test(13, 0), None, Budget::default());
let handle = region.pending_spawn_handle();
let reservation = handle.reserve();
assert_eq!(region.pending_spawn_count(), 1);
drop(reservation);
assert_eq!(region.pending_spawn_count(), 0);
}
#[test]
fn pending_spawn_reservations_are_balanced_under_contention() {
let region = RegionRecord::new(RegionId::new_for_test(14, 0), None, Budget::default());
let mut handles = Vec::new();
for _ in 0..8 {
let handle = region.pending_spawn_handle();
handles.push(std::thread::spawn(move || {
for _ in 0..1_000 {
let reservation = handle.reserve();
drop(reservation);
}
}));
}
for h in handles {
h.join().expect("reservation thread panicked");
}
assert_eq!(region.pending_spawn_count(), 0);
assert_eq!(region.pending_spawn_handle().underflow_count(), 0);
}
}