/rust/registry/src/index.crates.io-1949cf8c6b5b557f/seize-0.5.1/src/collector.rs
Line | Count | Source |
1 | | use crate::raw::{self, membarrier, Thread}; |
2 | | use crate::{LocalGuard, OwnedGuard}; |
3 | | |
4 | | use std::fmt; |
5 | | use std::sync::OnceLock; |
6 | | |
7 | | /// A concurrent garbage collector. |
8 | | /// |
9 | | /// A `Collector` manages the access and retirement of concurrent objects |
10 | | /// Objects can be safely loaded through *guards*, which can be created using |
11 | | /// the [`enter`](Collector::enter) or [`enter_owned`](Collector::enter_owned) |
12 | | /// methods. |
13 | | /// |
14 | | /// Every instance of a concurrent data structure should typically own its |
15 | | /// `Collector`. This allows the garbage collection of non-`'static` values, as |
16 | | /// memory reclamation is guaranteed to run when the `Collector` is dropped. |
17 | | #[repr(transparent)] |
18 | | pub struct Collector { |
19 | | /// The underlying raw collector instance. |
20 | | pub(crate) raw: raw::Collector, |
21 | | } |
22 | | |
23 | | impl Default for Collector { |
24 | 20.6k | fn default() -> Self { |
25 | 20.6k | Self::new() |
26 | 20.6k | } |
27 | | } |
28 | | |
29 | | impl Collector { |
30 | | /// The default batch size for a new collector. |
31 | | const DEFAULT_BATCH_SIZE: usize = 32; |
32 | | |
33 | | /// Creates a new collector. |
34 | 20.6k | pub fn new() -> Self { |
35 | | // Initialize the `membarrier` module, detecting the presence of |
36 | | // operating-system strong barrier APIs. |
37 | 20.6k | membarrier::detect(); |
38 | | |
39 | | // available_parallelism is quite slow (microseconds). |
40 | | static CPUS: OnceLock<usize> = OnceLock::new(); |
41 | 20.6k | let cpus = *CPUS.get_or_init(|| { |
42 | 2 | std::thread::available_parallelism() |
43 | 2 | .map(Into::into) |
44 | 2 | .unwrap_or(1) |
45 | 2 | }); |
46 | | |
47 | | // Ensure every batch accumulates at least as many entries |
48 | | // as there are threads on the system. |
49 | 20.6k | let batch_size = cpus.max(Self::DEFAULT_BATCH_SIZE); |
50 | | |
51 | 20.6k | Self { |
52 | 20.6k | raw: raw::Collector::new(cpus, batch_size), |
53 | 20.6k | } |
54 | 20.6k | } |
55 | | |
56 | | /// Sets the number of objects that must be in a batch before reclamation is |
57 | | /// attempted. |
58 | | /// |
59 | | /// Retired objects are added to thread-local *batches* before starting the |
60 | | /// reclamation process. After `batch_size` is hit, the objects are moved to |
61 | | /// separate *retirement lists*, where reference counting kicks in and |
62 | | /// batches are eventually reclaimed. |
63 | | /// |
64 | | /// A larger batch size amortizes the cost of retirement. However, |
65 | | /// reclamation latency can also grow due to the large number of objects |
66 | | /// needed to be freed. Note that reclamation can not be attempted |
67 | | /// unless the batch contains at least as many objects as the number of |
68 | | /// active threads. |
69 | | /// |
70 | | /// The default batch size is `32`. |
71 | 0 | pub fn batch_size(mut self, batch_size: usize) -> Self { |
72 | 0 | self.raw.batch_size = batch_size; |
73 | 0 | self |
74 | 0 | } |
75 | | |
76 | | /// Marks the current thread as active, returning a guard that protects |
77 | | /// loads of concurrent objects for its lifetime. The thread will be |
78 | | /// marked as inactive when the guard is dropped. |
79 | | /// |
80 | | /// Note that loads of objects that may be retired must be protected with |
81 | | /// the [`Guard::protect`]. See [the |
82 | | /// guide](crate::guide#starting-operations) for an introduction to |
83 | | /// using guards, or the documentation of [`LocalGuard`] for |
84 | | /// more details. |
85 | | /// |
86 | | /// Note that `enter` is reentrant, and it is legal to create multiple |
87 | | /// guards on the same thread. The thread will stay marked as active |
88 | | /// until the last guard is dropped. |
89 | | /// |
90 | | /// [`Guard::protect`]: crate::Guard::protect |
91 | | /// |
92 | | /// # Performance |
93 | | /// |
94 | | /// Performance-wise, creating and destroying a `LocalGuard` is about the |
95 | | /// same as locking and unlocking an uncontended `Mutex`. Because of |
96 | | /// this, guards should be reused across multiple operations if |
97 | | /// possible. However, holding a guard prevents the reclamation of any |
98 | | /// concurrent objects retired during its lifetime, so there is |
99 | | /// a tradeoff between performance and memory usage. |
100 | | /// |
101 | | /// # Examples |
102 | | /// |
103 | | /// ```rust |
104 | | /// # use std::sync::atomic::{AtomicPtr, Ordering}; |
105 | | /// use seize::Guard; |
106 | | /// # let collector = seize::Collector::new(); |
107 | | /// |
108 | | /// // An atomic object. |
109 | | /// let ptr = AtomicPtr::new(Box::into_raw(Box::new(1_usize))); |
110 | | /// |
111 | | /// { |
112 | | /// // Create a guard that is active for this scope. |
113 | | /// let guard = collector.enter(); |
114 | | /// |
115 | | /// // Read the object using a protected load. |
116 | | /// let value = guard.protect(&ptr, Ordering::Acquire); |
117 | | /// unsafe { assert_eq!(*value, 1) } |
118 | | /// |
119 | | /// // If there are other thread that may retire the object, |
120 | | /// // the pointer is no longer valid after the guard is dropped. |
121 | | /// drop(guard); |
122 | | /// } |
123 | | /// # unsafe { drop(Box::from_raw(ptr.load(Ordering::Relaxed))) }; |
124 | | /// ``` |
125 | | #[inline] |
126 | 380k | pub fn enter(&self) -> LocalGuard<'_> { |
127 | 380k | LocalGuard::enter(self) |
128 | 380k | } <seize::collector::Collector>::enter Line | Count | Source | 126 | 380k | pub fn enter(&self) -> LocalGuard<'_> { | 127 | 380k | LocalGuard::enter(self) | 128 | 380k | } |
Unexecuted instantiation: <seize::collector::Collector>::enter |
129 | | |
130 | | /// Create an owned guard that protects objects for its lifetime. |
131 | | /// |
132 | | /// Unlike local guards created with [`enter`](Collector::enter), owned |
133 | | /// guards are independent of the current thread, allowing them to |
134 | | /// implement `Send` and `Sync`. See the documentation of [`OwnedGuard`] |
135 | | /// for more details. |
136 | | #[inline] |
137 | 0 | pub fn enter_owned(&self) -> OwnedGuard<'_> { |
138 | 0 | OwnedGuard::enter(self) |
139 | 0 | } |
140 | | |
141 | | /// Retires a value, running `reclaim` when no threads hold a reference to |
142 | | /// it. |
143 | | /// |
144 | | /// Note that this method is disconnected from any guards on the current |
145 | | /// thread, so the pointer may be reclaimed immediately. Use |
146 | | /// [`Guard::defer_retire`](crate::Guard::defer_retire) if the pointer may |
147 | | /// still be accessed by the current thread while the guard is active. |
148 | | /// |
149 | | /// # Safety |
150 | | /// |
151 | | /// The retired pointer must no longer be accessible to any thread that |
152 | | /// enters after it is removed. It also cannot be accessed by the |
153 | | /// current thread after `retire` is called. |
154 | | /// |
155 | | /// Additionally, the pointer must be valid to pass to the provided |
156 | | /// reclaimer, once it is safe to reclaim. |
157 | | /// |
158 | | /// # Examples |
159 | | /// |
160 | | /// Common reclaimers are provided by the [`reclaim`](crate::reclaim) |
161 | | /// module. |
162 | | /// |
163 | | /// ``` |
164 | | /// # use std::sync::atomic::{AtomicPtr, Ordering}; |
165 | | /// # let collector = seize::Collector::new(); |
166 | | /// use seize::reclaim; |
167 | | /// |
168 | | /// // An atomic object. |
169 | | /// let ptr = AtomicPtr::new(Box::into_raw(Box::new(1_usize))); |
170 | | /// |
171 | | /// // Create a guard. |
172 | | /// let guard = collector.enter(); |
173 | | /// |
174 | | /// // Store a new value. |
175 | | /// let old = ptr.swap(Box::into_raw(Box::new(2_usize)), Ordering::Release); |
176 | | /// |
177 | | /// // Reclaim the old value. |
178 | | /// // |
179 | | /// // Safety: The `swap` above made the old value unreachable for any new threads. |
180 | | /// // Additionally, the old value was allocated with a `Box`, so `reclaim::boxed` |
181 | | /// // is valid. |
182 | | /// unsafe { collector.retire(old, reclaim::boxed) }; |
183 | | /// # unsafe { collector.retire(ptr.load(Ordering::Relaxed), reclaim::boxed) }; |
184 | | /// ``` |
185 | | /// |
186 | | /// Alternative, a custom reclaimer function can be used. |
187 | | /// |
188 | | /// ``` |
189 | | /// use seize::Collector; |
190 | | /// |
191 | | /// let collector = Collector::new(); |
192 | | /// |
193 | | /// // Allocate a value and immediately retire it. |
194 | | /// let value: *mut usize = Box::into_raw(Box::new(1_usize)); |
195 | | /// |
196 | | /// // Safety: The value was never shared. |
197 | | /// unsafe { |
198 | | /// collector.retire(value, |ptr: *mut usize, _collector: &Collector| unsafe { |
199 | | /// // Safety: The value was allocated with `Box::new`. |
200 | | /// let value = Box::from_raw(ptr); |
201 | | /// println!("Dropping {value}"); |
202 | | /// drop(value); |
203 | | /// }); |
204 | | /// } |
205 | | /// ``` |
206 | | #[inline] |
207 | 0 | pub unsafe fn retire<T>(&self, ptr: *mut T, reclaim: unsafe fn(*mut T, &Collector)) { |
208 | 0 | debug_assert!(!ptr.is_null(), "attempted to retire a null pointer"); |
209 | | |
210 | | // Note that `add` doesn't ever actually reclaim the pointer immediately if |
211 | | // the current thread is active. Instead, it adds it to the current thread's |
212 | | // reclamation list, but we don't guarantee that publicly. |
213 | 0 | unsafe { self.raw.add(ptr, reclaim, Thread::current()) } |
214 | 0 | } Unexecuted instantiation: <seize::collector::Collector>::retire::<papaya::raw::Entry<bytes::bytes::Bytes, ()>> Unexecuted instantiation: <seize::collector::Collector>::retire::<_> |
215 | | |
216 | | /// Reclaim any values that have been retired. |
217 | | /// |
218 | | /// This method reclaims any objects that have been retired across *all* |
219 | | /// threads. After calling this method, any values that were previous |
220 | | /// retired, or retired recursively on the current thread during this |
221 | | /// call, will have been reclaimed. |
222 | | /// |
223 | | /// # Safety |
224 | | /// |
225 | | /// This function is **extremely unsafe** to call. It is only sound when no |
226 | | /// threads are currently active, whether accessing values that have |
227 | | /// been retired or accessing the collector through any type of guard. |
228 | | /// This is akin to having a unique reference to the collector. However, |
229 | | /// this method takes a shared reference, as reclaimers to |
230 | | /// be run by this thread are allowed to access the collector recursively. |
231 | | /// |
232 | | /// # Notes |
233 | | /// |
234 | | /// Note that if reclaimers initialize guards across threads, or initialize |
235 | | /// owned guards, objects retired through those guards may not be |
236 | | /// reclaimed. |
237 | 20.6k | pub unsafe fn reclaim_all(&self) { |
238 | 20.6k | unsafe { self.raw.reclaim_all() }; |
239 | 20.6k | } |
240 | | |
241 | | // Create a reference to `Collector` from an underlying `raw::Collector`. |
242 | 0 | pub(crate) fn from_raw(raw: &raw::Collector) -> &Collector { |
243 | 0 | unsafe { &*(raw as *const raw::Collector as *const Collector) } |
244 | 0 | } |
245 | | } |
246 | | |
247 | | impl Eq for Collector {} |
248 | | |
249 | | impl PartialEq for Collector { |
250 | | /// Checks if both references point to the same collector. |
251 | | #[inline] |
252 | 0 | fn eq(&self, other: &Self) -> bool { |
253 | 0 | self.raw.id == other.raw.id |
254 | 0 | } |
255 | | } |
256 | | |
257 | | impl fmt::Debug for Collector { |
258 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
259 | 0 | f.debug_struct("Collector") |
260 | 0 | .field("batch_size", &self.raw.batch_size) |
261 | 0 | .finish() |
262 | 0 | } |
263 | | } |