use std::cell::RefCell;
use std::ops::{Deref, DerefMut};
type CleanupEntry = Option<Box<dyn FnOnce()>>;
thread_local! {
static CLEANUP_STACK: RefCell<Vec<CleanupEntry>> = const { RefCell::new(Vec::new()) };
}
pub struct BailoutGuard<T> {
value: *mut T,
index: usize,
}
impl<T: 'static> BailoutGuard<T> {
pub fn new(value: T) -> Self {
let ptr = Box::into_raw(Box::new(value));
let index = CLEANUP_STACK.with(|stack| {
let mut stack = stack.borrow_mut();
stack.push(Some(Box::new(move || {
unsafe { drop(Box::from_raw(ptr)) }
})));
stack.len() - 1
});
Self { value: ptr, index }
}
#[inline]
#[must_use]
pub fn get(&self) -> &T {
unsafe { &*self.value }
}
#[inline]
pub fn get_mut(&mut self) -> &mut T {
unsafe { &mut *self.value }
}
#[must_use]
pub fn into_inner(self) -> T {
self.release();
let value = unsafe { *Box::from_raw(self.value) };
std::mem::forget(self);
value
}
}
impl<T> BailoutGuard<T> {
fn release(&self) {
CLEANUP_STACK.with(|stack| {
let mut stack = stack.borrow_mut();
if self.index + 1 == stack.len() {
stack.pop();
while stack.last().is_some_and(Option::is_none) {
stack.pop();
}
} else if let Some(entry) = stack.get_mut(self.index) {
*entry = None;
}
});
}
}
impl<T> Deref for BailoutGuard<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { &*self.value }
}
}
impl<T> DerefMut for BailoutGuard<T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.value }
}
}
impl<T> Drop for BailoutGuard<T> {
fn drop(&mut self) {
self.release();
unsafe { drop(Box::from_raw(self.value)) }
}
}
pub(crate) fn cleanup_depth() -> usize {
CLEANUP_STACK.with(|stack| stack.borrow().len())
}
pub(crate) fn run_cleanups_above(depth: usize) {
let entries: Vec<CleanupEntry> = CLEANUP_STACK.with(|stack| {
let mut stack = stack.borrow_mut();
if depth >= stack.len() {
return Vec::new();
}
stack.drain(depth..).collect()
});
for cleanup in entries.into_iter().rev().flatten() {
cleanup();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
struct DropCounter(Arc<AtomicUsize>);
impl Drop for DropCounter {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
fn counter() -> (Arc<AtomicUsize>, impl Fn() -> DropCounter) {
let count = Arc::new(AtomicUsize::new(0));
let make = {
let count = Arc::clone(&count);
move || DropCounter(Arc::clone(&count))
};
(count, make)
}
fn reset() {
CLEANUP_STACK.with(|stack| stack.borrow_mut().clear());
}
#[test]
fn normal_drop_pops_entry() {
reset();
let (count, make) = counter();
{
let _guard = BailoutGuard::new(make());
assert_eq!(cleanup_depth(), 1);
}
assert_eq!(count.load(Ordering::SeqCst), 1);
assert_eq!(cleanup_depth(), 0);
}
#[test]
fn out_of_order_drop_drains_once_newer_guard_goes() {
reset();
let (count, make) = counter();
let older = BailoutGuard::new(make());
let newer = BailoutGuard::new(make());
drop(older);
assert_eq!(cleanup_depth(), 2);
drop(newer);
assert_eq!(cleanup_depth(), 0);
assert_eq!(count.load(Ordering::SeqCst), 2);
}
#[test]
fn cleanups_above_depth_spare_outer_guard() {
reset();
let (count, make) = counter();
let outer = BailoutGuard::new(make());
let depth = cleanup_depth();
let inner = BailoutGuard::new(make());
std::mem::forget(inner);
run_cleanups_above(depth);
assert_eq!(count.load(Ordering::SeqCst), 1);
assert_eq!(cleanup_depth(), 1);
assert_eq!(outer.get().0.load(Ordering::SeqCst), 1);
drop(outer);
assert_eq!(count.load(Ordering::SeqCst), 2);
assert_eq!(cleanup_depth(), 0);
}
struct Record(Arc<std::sync::Mutex<Vec<u8>>>, u8);
impl Drop for Record {
fn drop(&mut self) {
self.0.lock().expect("poisoned").push(self.1);
}
}
#[test]
fn cleanups_run_newest_first() {
reset();
let order = Arc::new(std::sync::Mutex::new(Vec::new()));
std::mem::forget(BailoutGuard::new(Record(Arc::clone(&order), 1)));
std::mem::forget(BailoutGuard::new(Record(Arc::clone(&order), 2)));
run_cleanups_above(0);
assert_eq!(*order.lock().expect("poisoned"), vec![2, 1]);
}
#[test]
fn into_inner_releases_entry() {
reset();
let (count, make) = counter();
let value = BailoutGuard::new(make()).into_inner();
assert_eq!(cleanup_depth(), 0);
assert_eq!(count.load(Ordering::SeqCst), 0);
drop(value);
assert_eq!(count.load(Ordering::SeqCst), 1);
}
#[test]
fn deref_and_deref_mut() {
let mut guard = BailoutGuard::new(String::from("hello"));
guard.push_str(" world");
assert_eq!(&*guard, "hello world");
assert_eq!(guard.len(), 11);
}
}