use std::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: ?Sized, F>(self, f: F) -> Guard<U>
where F: FnOnce(&T) -> &U {
Guard {
hazard: self.hazard,
pointer: f(self.pointer),
}
}
pub fn try_map<U: ?Sized, E, F>(self, f: F) -> Result<Guard<U>, E>
where F: FnOnce(&T) -> Result<&U, E> {
Ok(Guard {
hazard: self.hazard,
pointer: f(self.pointer)?,
})
}
pub fn maybe_map<U: ?Sized, F>(self, f: F) -> Option<Guard<U>>
where F: FnOnce(&T) -> Option<&U> {
let hazard = self.hazard;
f(self.pointer).map(|res| Guard {
hazard: hazard,
pointer: res,
})
}
pub fn as_ptr(&self) -> *const T {
self.pointer
}
}
impl<T: ?Sized> ops::Deref for Guard<T> {
type Target = T;
fn deref(&self) -> &T {
self.pointer
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem;
use Atomic;
use std::sync::atomic;
#[test]
fn new() {
assert_eq!(&*Guard::new(|| "blah"), "blah");
}
#[test]
fn maybe_new() {
assert_eq!(&*Guard::maybe_new(|| Some("blah")).unwrap(), "blah");
assert!(Guard::<u8>::maybe_new(|| None).is_none());
}
#[test]
fn try_new() {
assert_eq!(&*Guard::try_new::<_, u8>(|| Ok("blah")).unwrap(), "blah");
assert_eq!(Guard::<u8>::try_new(|| Err(2)).unwrap_err(), 2);
}
#[test]
fn map() {
let g = Guard::new(|| "blah");
assert_eq!(&*g.map(|x| {
assert_eq!(x, "blah");
"blah2"
}), "blah2");
}
#[test]
fn maybe_map() {
let g = Guard::new(|| "blah");
assert_eq!(&*g.maybe_map(|x| {
assert_eq!(x, "blah");
Some("blah2")
}).unwrap(), "blah2");
let g = Guard::new(|| "blah");
assert_eq!(&*g, "blah");
assert!(g.maybe_map::<u8, _>(|_| None).is_none());
}
#[test]
fn try_map() {
let g = Guard::new(|| "blah");
assert_eq!(&*g.try_map::<_, u8, _>(|x| {
assert_eq!(x, "blah");
Ok("blah2")
}).unwrap(), "blah2");
let g = Guard::new(|| "blah");
assert_eq!(&*g, "blah");
assert_eq!(g.try_map::<u8, _, _>(|_| Err(2)).unwrap_err(), 2);
}
#[test]
fn map_field() {
let a = Atomic::new(Some(Box::new((7, 13))));
let g = a.load(atomic::Ordering::Relaxed).unwrap().map(|&(_, ref b)| b);
drop(a);
::gc();
assert_eq!(*g, 13);
}
#[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"
});
}
}