use std::sync::atomic::{self, AtomicPtr};
use std::{mem, thread};
use {debug, local};
static BLOCKED: u8 = 0;
static FREE: u8 = 0;
static DEAD: u8 = 0;
#[derive(PartialEq, Debug)]
#[must_use = "Hazard states are expensive to fetch and have no value unless used."]
pub enum State {
Free,
Dead,
Protect(*const u8),
}
pub fn create() -> (Writer, Reader) {
let ptr = unsafe {
&*Box::into_raw(Box::new(AtomicPtr::new(&BLOCKED as *const u8 as *mut u8)))
};
(Writer {
ptr: ptr,
}, Reader {
ptr: ptr,
})
}
pub struct Reader {
ptr: &'static AtomicPtr<u8>,
}
impl Reader {
pub fn get(&self) -> State {
let mut spins = 0;
loop {
let ptr = self.ptr.load(atomic::Ordering::Acquire) as *const u8;
if ptr == &BLOCKED {
spins += 1;
debug_assert!(spins < 100_000_000, "\
Hazard blocked for 100 millions rounds. Panicking as chances are that it will \
never get unblocked.\
");
continue;
} else if ptr == &FREE {
return State::Free;
} else if ptr == &DEAD {
return State::Dead;
} else {
return State::Protect(ptr);
}
}
}
pub unsafe fn destroy(self) {
debug_assert!(self.get() == State::Dead, "Prematurely freeing an active hazard.");
Box::from_raw(self.ptr as *const AtomicPtr<u8> as *mut AtomicPtr<u8>);
mem::forget(self);
}
}
impl Drop for Reader {
fn drop(&mut self) {
panic!("\
Hazard readers ought to be destroyed manually through the `Reader::destroy()` method.\
");
}
}
#[derive(Debug)]
pub struct Writer {
ptr: &'static AtomicPtr<u8>,
}
impl Writer {
pub fn is_blocked(&self) -> bool {
self.ptr.load(atomic::Ordering::Acquire) as *const u8 == &BLOCKED
}
pub fn block(&self) {
self.ptr.store(&BLOCKED as *const u8 as *mut u8, atomic::Ordering::Release);
}
pub fn free(&self) {
self.ptr.store(&FREE as *const u8 as *mut u8, atomic::Ordering::Release);
}
pub fn protect(&self, ptr: *const u8) {
debug::exec(|| println!("Protecting: 0x{:x}", ptr as usize));
self.ptr.store(ptr as *mut u8, atomic::Ordering::Release);
}
unsafe fn dead(&self) {
self.ptr.store(&DEAD as *const u8 as *mut u8, atomic::Ordering::Release);
}
pub fn kill(self) {
unsafe { self.dead(); }
mem::forget(self);
}
}
impl Drop for Writer {
fn drop(&mut self) {
if thread::panicking() {
unsafe { self.dead(); }
} else {
local::free_hazard(Writer {
ptr: self.ptr,
});
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{ptr, thread};
#[test]
fn set_get() {
let (w, r) = create();
assert!(w.is_blocked());
w.free();
assert!(!w.is_blocked());
assert_eq!(r.get(), State::Free);
w.free();
assert!(!w.is_blocked());
assert_eq!(r.get(), State::Free);
let x = 2;
w.protect(&x);
assert_eq!(r.get(), State::Protect(&x));
w.protect(ptr::null());
assert_eq!(r.get(), State::Protect(ptr::null()));
w.protect(0x1 as *const u8);
assert_eq!(r.get(), State::Protect(0x1 as *const u8));
w.kill();
unsafe {
r.destroy();
}
}
#[test]
fn hazard_pair() {
let (w, r) = create();
let x = 2;
w.free();
assert_eq!(r.get(), State::Free);
w.protect(&x);
assert_eq!(r.get(), State::Protect(&x));
w.kill();
assert_eq!(r.get(), State::Dead);
unsafe {
r.destroy();
}
}
#[test]
fn cross_thread() {
for _ in 0..64 {
let (w, r) = create();
thread::spawn(move || {
w.kill();
}).join().unwrap();
assert_eq!(r.get(), State::Dead);
unsafe { r.destroy(); }
}
}
#[test]
fn drop() {
for _ in 0..9000 {
let (w, r) = create();
w.kill();
unsafe {
r.destroy();
}
}
}
}