use std::{cmp, ops};
use std::sync::atomic;
use {hazard, local};
#[cfg(debug_assertions)]
use std::cell::Cell;
#[cfg(debug_assertions)]
thread_local! {
static CURRENT_CREATING: Cell<usize> = Cell::new(0);
}
pub fn debug_assert_no_create() {
#[cfg(debug_assertions)]
CURRENT_CREATING.with(|x| assert_eq!(x.get(), 0));
}
#[must_use = "\
You are getting a `conc::Guard<T>` without using it, which means it is potentially \
unnecessary overhead. Consider replacing the method with something that doesn't \
return a guard.\
"]
#[derive(Debug)]
pub struct Guard<T: 'static + ?Sized> {
hazard: hazard::Writer,
pointer: &'static T,
}
impl<T: ?Sized> Guard<T> {
pub fn try_new<F, E>(ptr: F) -> Result<Guard<T>, E>
where F: FnOnce() -> Result<&'static T, E> {
#[cfg(debug_assertions)]
CURRENT_CREATING.with(|x| x.set(x.get() + 1));
let hazard = local::get_hazard();
atomic::fence(atomic::Ordering::SeqCst);
let res = ptr();
#[cfg(debug_assertions)]
CURRENT_CREATING.with(|x| x.set(x.get() - 1));
match res {
Ok(ptr) => {
hazard.protect(ptr as *const T as *const u8);
Ok(Guard {
hazard: hazard,
pointer: ptr,
})
},
Err(err) => {
hazard.free();
Err(err)
}
}
}
pub fn new<F>(ptr: F) -> Guard<T>
where F: FnOnce() -> &'static T {
Guard::try_new::<_, ()>(|| Ok(ptr())).unwrap()
}
pub fn maybe_new<F>(ptr: F) -> Option<Guard<T>>
where F: FnOnce() -> Option<&'static T> {
Guard::try_new(|| ptr().ok_or(())).ok()
}
pub fn map<U, F>(self, f: F) -> Guard<U>
where F: FnOnce(&T) -> &U {
Guard {
hazard: self.hazard,
pointer: f(self.pointer),
}
}
pub fn as_ptr(&self) -> *const T {
self.pointer
}
}
impl<T> cmp::PartialEq for Guard<T> {
fn eq(&self, other: &Guard<T>) -> bool {
self.as_ptr() == other.as_ptr()
}
}
impl<T> cmp::Eq for Guard<T> {}
impl<T> ops::Deref for Guard<T> {
type Target = T;
fn deref(&self) -> &T {
self.pointer
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem;
#[test]
#[should_panic]
fn panic_during_guard_creation() {
let _ = Guard::new(|| -> &'static u8 { panic!() });
}
#[test]
fn nested_guard_creation() {
for _ in 0..100 {
let _ = Guard::new(|| {
mem::forget(Guard::new(|| "blah"));
"blah"
});
}
}
#[cfg(debug_assertions)]
#[test]
#[should_panic]
fn debug_catch_infinite_blockage() {
let _ = Guard::new(|| {
local::export_garbage();
"blah"
});
}
}