use std::sync::atomic::{AtomicUsize, Ordering};
use std::{cell::UnsafeCell, mem::MaybeUninit};
pub(crate) static CAPTURE: Capture = Capture::new();
pub(crate) struct Capture {
state: AtomicUsize,
slot: UnsafeCell<MaybeUninit<ntex_error::BacktraceRaw>>,
}
unsafe impl Sync for Capture {}
impl Capture {
const IDLE: usize = 0;
const ARMED: usize = 1;
const WRITING: usize = 2;
const READY: usize = 3;
const ABANDONED: usize = 4;
const fn new() -> Self {
Self {
state: AtomicUsize::new(Self::IDLE),
slot: UnsafeCell::new(MaybeUninit::uninit()),
}
}
#[allow(clippy::cast_sign_loss)]
fn state(tid: i32, tag: usize) -> usize {
((tid as u32 as usize) << 3) | tag
}
pub(crate) fn arm(&self, tid: i32) -> bool {
self.state
.compare_exchange(
Self::IDLE,
Self::state(tid, Self::ARMED),
Ordering::Acquire,
Ordering::Relaxed,
)
.is_ok()
}
pub(crate) fn capture(&self, tid: i32, location: &'static str) {
if self
.state
.compare_exchange(
Self::state(tid, Self::ARMED),
Self::state(tid, Self::WRITING),
Ordering::Acquire,
Ordering::Relaxed,
)
.is_err()
{
return;
}
let bt = unsafe { ntex_error::BacktraceRaw::with_filename_unsynchronized(location) };
unsafe { (*self.slot.get()).write(bt) };
if self
.state
.compare_exchange(
Self::state(tid, Self::WRITING),
Self::state(tid, Self::READY),
Ordering::Release,
Ordering::Relaxed,
)
.is_err()
{
unsafe { (*self.slot.get()).assume_init_drop() };
self.state.store(Self::IDLE, Ordering::Release);
}
}
pub(crate) fn take(&self, tid: i32) -> Option<ntex_error::BacktraceRaw> {
if self.state.load(Ordering::Acquire) == Self::state(tid, Self::READY) {
let bt = unsafe { (*self.slot.get()).assume_init_read() };
self.state.store(Self::IDLE, Ordering::Release);
Some(bt)
} else {
None
}
}
pub(crate) fn disarm(&self, tid: i32) {
loop {
let cur = self.state.load(Ordering::Acquire);
let next = if cur == Self::state(tid, Self::ARMED) {
Self::IDLE
} else if cur == Self::state(tid, Self::WRITING) {
Self::state(tid, Self::ABANDONED)
} else if cur == Self::state(tid, Self::READY) {
let _ = self.take(tid);
return;
} else {
return;
};
if self
.state
.compare_exchange(cur, next, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
return;
}
}
}
}
#[cfg(test)]
#[path = "../tests/unit/capture.rs"]
mod tests;