1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
use super::ref_count::ThreadedRefCount;
use super::ThreadedCc;
use crate::cc::CcDummy;
use crate::cc::CcDyn;
use crate::collect;
use crate::collect::AbstractObjectSpace;
use crate::collect::Linked;
use crate::debug;
use crate::Trace;
use parking_lot::Mutex;
use parking_lot::RwLock;
use std::cell::Cell;
use std::mem;
use std::pin::Pin;
use std::sync::Arc;
#[repr(C)]
pub struct Header {
next: Cell<*const Header>,
prev: Cell<*const Header>,
/// Vtable of (`&CcBox<T> as &dyn CcDyn`)
ccdyn_vptr: *const (),
/// Lock for mutating the linked list.
linked_list_lock: Arc<Mutex<()>>,
}
/// A collection of tracked [`ThreadedCc`](type.ThreadedCc.html) objects
/// that can be garbage collected.
///
/// [`ThreadedObjectSpace`](struct.ThreadedObjectSpace.html) is similar to
/// [`ObjectSpace`](struct.ObjectSpace.html) but works with multi-thread.
pub struct ThreadedObjectSpace {
/// Linked list to the tracked objects.
list: Pin<Box<Header>>,
/// Whether the collector is running.
collector_lock: Arc<RwLock<()>>,
}
// safety: accesses are protected by mutex
unsafe impl Send for ThreadedObjectSpace {}
unsafe impl Sync for ThreadedObjectSpace {}
impl AbstractObjectSpace for ThreadedObjectSpace {
type RefCount = ThreadedRefCount;
type Header = Header;
fn insert(&self, header: &mut Self::Header, value: &dyn CcDyn) {
debug_assert!(Arc::ptr_eq(
&header.linked_list_lock,
&self.list.linked_list_lock
));
// Should be locked by `create()` already.
debug_assert!(self.list.linked_list_lock.try_lock().is_none());
let prev: &Header = &self.list;
debug_assert!(!collect::is_collecting(prev));
debug_assert!(header.next.get().is_null());
let next = prev.next.get();
header.prev.set(prev);
header.next.set(next);
unsafe {
// safety: The linked list is maintained, and pointers are valid.
(*next).prev.set(header);
// safety: To access vtable pointer. Test by test_gc_header_value.
let fat_ptr: [*mut (); 2] = mem::transmute(value);
header.ccdyn_vptr = fat_ptr[1];
}
prev.next.set(header);
}
#[inline]
fn remove(header: &Self::Header) {
let _linked_list_lock = header.linked_list_lock.lock();
let header: &Header = header;
debug_assert!(!collect::is_collecting(header));
debug_assert!(!header.next.get().is_null());
debug_assert!(!header.prev.get().is_null());
let next = header.next.get();
let prev = header.prev.get();
// safety: The linked list is maintained. Pointers in it are valid.
unsafe {
(*prev).next.set(next);
(*next).prev.set(prev);
}
header.next.set(std::ptr::null_mut());
}
#[inline]
fn new_ref_count(&self, tracked: bool) -> Self::RefCount {
ThreadedRefCount::new(tracked, self.collector_lock.clone())
}
fn empty_header(&self) -> Self::Header {
let linked_list_lock = self.list.linked_list_lock.clone();
Self::Header {
linked_list_lock,
next: Cell::new(std::ptr::null()),
prev: Cell::new(std::ptr::null()),
ccdyn_vptr: CcDummy::ccdyn_vptr(),
}
}
}
impl Default for ThreadedObjectSpace {
/// Constructs an empty [`ThreadedObjectSpace`](struct.ThreadedObjectSpace.html).
fn default() -> Self {
let linked_list_lock = Arc::new(Mutex::new(()));
let pinned = Box::pin(Header {
prev: Cell::new(std::ptr::null()),
next: Cell::new(std::ptr::null()),
ccdyn_vptr: CcDummy::ccdyn_vptr(),
linked_list_lock,
});
let header: &Header = &pinned;
header.prev.set(header);
header.next.set(header);
ThreadedObjectSpace {
list: pinned,
collector_lock: Default::default(),
}
}
}
impl ThreadedObjectSpace {
/// Count objects tracked by this
/// [`ThreadedObjectSpace`](struct.ThreadedObjectSpace.html).
pub fn count_tracked(&self) -> usize {
let _linked_list_lock = self.list.linked_list_lock.lock();
let list: &Header = &self.list;
let mut count = 0;
collect::visit_list(list, |_| count += 1);
count
}
/// Collect cyclic garbage tracked by this
/// [`ThreadedObjectSpace`](struct.ThreadedObjectSpace.html).
/// Return the number of objects collected.
pub fn collect_cycles(&self) -> usize {
// Wait for complex operations (drop). Block operations (drop, deref).
let collector_lock = self.collector_lock.write();
// Block linked list changes (create, remove).
let linked_list_lock = self.list.linked_list_lock.lock();
debug::log(|| ("ThreadedObjectSpace", "start collect_cycles"));
let list: &Header = &self.list;
let result = collect::collect_list(list, (linked_list_lock, collector_lock));
debug::log(|| ("ThreadedObjectSpace", "end collect_cycles"));
result
}
/// Constructs a new [`ThreadedCc<T>`](type.ThreadedCc.html) in this
/// [`ThreadedObjectSpace`](struct.ThreadedObjectSpace.html).
///
/// The returned object should not refer to
/// [`ThreadedCc<T>`](type.ThreadedCc.html) created by a different
/// [`ThreadedObjectSpace`](struct.ThreadedObjectSpace.html).
/// Otherwise the collector might fail to collect cycles.
///
/// The type `T` needs to be `Send + Sync`. This is because the
/// [`ThreadedObjectSpace`](struct.ThreadedObjectSpace.html) is
/// `Send` and `Sync`. The collector can run in a different
/// thread to access `ThreadedCc<T>`, which needs to be
/// `Send + Sync` to be safely accessed by the collector.
///
/// ```compile_fail
/// use gcmodule::{ThreadedObjectSpace, ThreadedCc, Cc};
/// let cc = Cc::new(5);
/// let space = ThreadedObjectSpace::default();
/// # Does not compile since Cc is not Send.
/// let tcc: ThreadedCc<Cc<_>> = space.create(cc);
/// ```
pub fn create<T: Trace + Send + Sync>(&self, value: T) -> ThreadedCc<T> {
let _linked_list_lock = self.list.linked_list_lock.lock();
ThreadedCc::new_in_space(value, self)
}
}
impl Linked for Header {
#[inline]
fn next(&self) -> *const Self {
self.next.get()
}
#[inline]
fn prev(&self) -> *const Self {
self.prev.get()
}
#[inline]
fn set_prev(&self, other: *const Self) {
self.prev.set(other)
}
#[inline]
fn value(&self) -> &dyn CcDyn {
// safety: To build trait object from self and vtable pointer.
// Test by test_gc_header_value_consistency().
unsafe {
let fat_ptr: (*const (), *const ()) =
((self as *const Self).offset(1) as _, self.ccdyn_vptr);
mem::transmute(fat_ptr)
}
}
}