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)
        }
    }
}