use spin::Mutex;
use std::collections::HashSet;
use std::{mem, panic};
use {rand, hazard, mpsc, debug, settings};
use garbage::Garbage;
lazy_static! {
static ref STATE: State = State::new();
}
pub fn create_hazard() -> hazard::Writer {
STATE.create_hazard()
}
pub fn export_garbage(garbage: Vec<Garbage>) {
STATE.export_garbage(garbage)
}
pub fn try_gc() -> Result<(), ()> {
STATE.try_gc()
}
pub fn tick() {
if rand::random::<usize>() < settings::get().gc_probability {
let _ = try_gc();
}
}
enum Message {
Garbage(Vec<Garbage>),
NewHazard(hazard::Reader),
}
struct State {
chan: mpsc::Sender<Message>,
garbo: Mutex<Garbo>,
}
impl State {
fn new() -> State {
let (send, recv) = mpsc::channel();
State {
chan: send,
garbo: Mutex::new(Garbo {
chan: recv,
garbage: Vec::new(),
hazards: Vec::new(),
})
}
}
fn create_hazard(&self) -> hazard::Writer {
let (writer, reader) = hazard::create();
self.chan.send(Message::NewHazard(reader));
writer
}
fn export_garbage(&self, garbage: Vec<Garbage>) {
self.chan.send(Message::Garbage(garbage));
}
fn try_gc(&self) -> Result<(), ()> {
if let Some(mut garbo) = self.garbo.try_lock() {
garbo.gc();
Ok(())
} else {
Err(())
}
}
}
impl panic::RefUnwindSafe for State {}
struct Garbo {
chan: mpsc::Receiver<Message>,
garbage: Vec<Garbage>,
hazards: Vec<hazard::Reader>,
}
impl Garbo {
fn handle(&mut self, msg: Message) {
match msg {
Message::Garbage(mut garbage) => self.garbage.append(&mut garbage),
Message::NewHazard(hazard) => self.hazards.push(hazard),
}
}
fn gc(&mut self) {
debug::exec(|| println!("Collecting garbage."));
for msg in self.chan.recv_all() {
self.handle(msg);
}
let mut active = HashSet::with_capacity(self.hazards.len());
let len = self.hazards.len(); for hazard in mem::replace(&mut self.hazards, Vec::with_capacity(len)) {
match hazard.get() {
hazard::State::Dead => unsafe { hazard.destroy() },
hazard::State::Free => self.hazards.push(hazard),
hazard::State::Protect(ptr) => {
active.insert(ptr);
self.hazards.push(hazard);
},
}
}
self.garbage.retain(|garbage| active.contains(&garbage.ptr()))
}
}
impl Drop for Garbo {
fn drop(&mut self) {
self.gc();
}
}
#[cfg(test)]
mod tests {
use super::*;
use garbage::Garbage;
use std::{panic, ptr};
#[test]
fn dtor_runs() {
fn dtor(x: *const u8) {
unsafe {
*(x as *mut u8) = 1;
}
}
let s = State::new();
for _ in 0..1000 {
let b = Box::new(0);
let h = s.create_hazard();
h.protect(&*b);
s.export_garbage(vec![Garbage::new(&*b, dtor)]);
while s.try_gc().is_err() {}
assert_eq!(*b, 0);
while s.try_gc().is_err() {}
h.free();
while s.try_gc().is_err() {}
assert_eq!(*b, 1);
h.kill();
}
}
#[test]
fn clean_up_state() {
fn dtor(x: *const u8) {
unsafe {
*(x as *mut u8) = 1;
}
}
for _ in 0..1000 {
let b = Box::new(0);
{
let s = State::new();
s.export_garbage(vec![Garbage::new(&*b, dtor)]);
}
assert_eq!(*b, 1);
}
}
#[test]
fn panic_invalidate_state() {
fn panic(_: *const u8) {
panic!();
}
fn dtor(x: *const u8) {
unsafe {
*(x as *mut u8) = 1;
}
}
let s = State::new();
let b = Box::new(0);
let h = create_hazard();
h.protect(&*b);
s.export_garbage(vec![Garbage::new(&*b, dtor), Garbage::new(0x2 as *const u8, panic)]);
let _ = panic::catch_unwind(|| {
while s.try_gc().is_err() {}
});
assert_eq!(*b, 0);
h.free();
while s.try_gc().is_err() {}
assert_eq!(*b, 1);
}
#[test]
#[should_panic]
fn panic_in_dtor() {
fn dtor(_: *const u8) {
panic!();
}
let s = State::new();
s.export_garbage(vec![Garbage::new(ptr::null(), dtor)]);
while s.try_gc().is_err() {}
}
#[cfg(debug_assertions)]
#[test]
#[should_panic]
fn debug_more_hazards() {
let s = State::new();
let h = s.create_hazard();
h.free();
mem::forget(h);
}
}