use std::{mem, thread};
use std::cell::RefCell;
use {global, hazard, guard, debug, settings};
use garbage::Garbage;
thread_local! {
static STATE: RefCell<State> = RefCell::new(State::default());
}
pub fn add_garbage(garbage: Garbage) {
debug::exec(|| println!("Adding garbage: {:?}", garbage));
guard::debug_assert_no_create();
if STATE.state() == thread::LocalKeyState::Destroyed {
global::export_garbage(vec![garbage]);
} else {
if STATE.with(|s| s.borrow_mut().add_garbage(garbage)) {
global::tick();
}
}
}
pub fn get_hazard() -> hazard::Writer {
if STATE.state() == thread::LocalKeyState::Destroyed {
global::create_hazard()
} else {
STATE.with(|s| s.borrow_mut().get_hazard())
}
}
pub fn free_hazard(hazard: hazard::Writer) {
debug::exec(|| println!("Freeing hazard: {:?}", hazard));
guard::debug_assert_no_create();
debug_assert!(!hazard.is_blocked(), "Illegally freeing a blocked hazards.");
if STATE.state() == thread::LocalKeyState::Destroyed {
hazard.kill();
} else {
STATE.with(|s| s.borrow_mut().free_hazard(hazard));
}
}
pub fn export_garbage() {
guard::debug_assert_no_create();
if STATE.state() != thread::LocalKeyState::Destroyed {
STATE.with(|s| s.borrow_mut().export_garbage());
global::tick();
}
}
#[derive(Default)]
struct State {
garbage: Vec<Garbage>,
available_hazards: Vec<hazard::Writer>,
available_hazards_free_before: usize,
}
impl State {
fn non_free_hazards(&self) -> usize {
self.available_hazards.len() - self.available_hazards_free_before
}
fn get_hazard(&mut self) -> hazard::Writer {
if let Some(hazard) = self.available_hazards.pop() {
hazard.block();
hazard
} else {
global::create_hazard()
}
}
fn free_hazard(&mut self, hazard: hazard::Writer) {
self.available_hazards.push(hazard);
if self.non_free_hazards() > settings::get().max_non_free_hazards {
for i in &self.available_hazards[self.available_hazards_free_before..] {
i.free();
}
self.available_hazards_free_before = self.available_hazards.len();
}
}
fn add_garbage(&mut self, garbage: Garbage) -> bool {
self.garbage.push(garbage);
if self.garbage.len() > settings::get().max_garbage_before_export {
self.export_garbage();
true
} else { false }
}
fn export_garbage(&mut self) {
debug::exec(|| println!("Exporting garbage."));
global::export_garbage(mem::replace(&mut self.garbage, Vec::new()));
}
}
impl Drop for State {
fn drop(&mut self) {
for hazard in self.available_hazards.drain(..) {
hazard.kill();
}
self.export_garbage();
}
}
#[cfg(test)]
mod tests {
use super::*;
use garbage::Garbage;
use hazard;
use std::thread;
#[test]
fn dtor_runs() {
fn dtor(x: *const u8) {
unsafe {
*(x as *mut u8) = 1;
}
}
for _ in 0..1000 {
let b = Box::new(0);
let h = get_hazard();
h.protect(&*b);
add_garbage(Garbage::new(&*b, dtor));
::gc();
assert_eq!(*b, 0);
::gc();
h.free();
::gc();
assert_eq!(*b, 1);
}
}
#[test]
fn dtor_runs_cross_thread() {
fn dtor(x: *const u8) {
unsafe {
*(x as *mut u8) = 1;
}
}
for _ in 0..1000 {
let b = Box::new(0);
let bptr = &*b as *const _ as usize;
let h = thread::spawn(move || {
let h = get_hazard();
h.protect(bptr as *const u8);
h
}).join().unwrap();
add_garbage(Garbage::new(&*b, dtor));
::gc();
assert_eq!(*b, 0);
::gc();
h.free();
::gc();
assert_eq!(*b, 1);
}
}
#[test]
fn clear_hazards() {
let mut s = State::default();
let mut v = Vec::new();
for _ in 0..100 {
let (w, r) = hazard::create();
w.protect(0x1 as *const u8);
v.push(r);
s.free_hazard(w);
}
for i in &v[0..16] {
assert_eq!(i.get(), hazard::State::Free);
}
mem::forget(v);
}
#[test]
fn kill_hazards() {
fn dtor(x: *const u8) {
unsafe {
*(x as *mut u8) = 1;
}
}
for _ in 0..1000 {
let b = thread::spawn(move || {
let b = Box::new(0);
let h = get_hazard();
h.protect(&*b);
add_garbage(Garbage::new(&*b, dtor));
::gc();
assert_eq!(*b, 0);
b
}).join().unwrap();
::gc();
assert_eq!(*b, 1);
}
}
#[cfg(debug_assertions)]
#[test]
#[should_panic]
fn debug_free_blocked() {
use std::mem;
let (writer, reader) = hazard::create();
mem::forget(reader);
free_hazard(writer);
}
}