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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
//! Cleaning up while you're away.
//!
//! Reference counting (e.g. via [`Arc`]) is a common strategy for cleaning up
//! resources shared between threads. However, it has limitations; reference
//! counted objects cannot be (easily) stored in atomic variables, due to subtle
//! race conditions. [`housekeeping`] is a concurrent memory reclaimer; it helps
//! manage atomically-swappable resources and clean them up safely.
//!
//! [`Arc`]: https://doc.rust-lang.org/stable/std/sync/struct.Arc.html
//! [`housekeeping`]: crate
//!
//! [`housekeeping`] is designed to run periodically, e.g. in between batches of
//! executed tasks. It is inspired by [`seize`] and [`crossbeam_epoch`], but is
//! simpler and offers lower-level interfaces.
//!
//! [`seize`]: https://crates.io/crates/seize
//! [`crossbeam_epoch`]: https://crates.io/crates/crossbeam_epoch
//!
//! # Usage
//!
//! Consider a program that needs to share atomically swappable data. At first
//! glance, it may be implemented using [`Arc`]:
//!
//! ```no_run
//! # use std::{mem, ptr, sync::{atomic::{AtomicPtr, Ordering}, Arc}};
//! #
//! # #[derive(Default)]
//! # struct Data {}
//! #
//! # fn somehow_compute() -> Data { Data {} }
//! # fn somehow_consume(data: &Data) { let _ = data; }
//! #
//! // The data stored is reference-counted with 'Arc'.
//! let data: Arc<Data> = Default::default();
//! let data_ptr = Arc::into_raw(data);
//! let data: AtomicPtr<Data> = AtomicPtr::new(data_ptr.cast_mut());
//!
//! std::thread::scope(|s| {
//! s.spawn(|| {
//! // Replace the existing data.
//! let new_data = Arc::new(somehow_compute());
//! let new_data_ptr = Arc::into_raw(new_data);
//! let old_data_ptr = data.swap(new_data_ptr.cast_mut(), Ordering::AcqRel);
//!
//! // Clean up the existing data.
//! mem::drop(unsafe { Arc::from_raw(old_data_ptr) });
//! });
//!
//! // Use the data.
//! let data_ptr = data.load(Ordering::Acquire).cast_const();
//! unsafe { Arc::increment_strong_count(data_ptr) }; // <-- unsound!!!
//! let data = unsafe { Arc::from_raw(data_ptr) };
//! somehow_consume(&data);
//! mem::drop(data);
//! });
//!
//! let _ = unsafe { Arc::from_raw(data.into_inner().cast_const()) };
//! ```
//!
//! Unfortunately, **this is unsound**. The spawned thread could drop `old_data`
//! (causing it to be deallocated) _in between_ the main thread's atomic load
//! and its call to `Arc::increment_strong_count()`.
//!
//! This situation calls for [`housekeeping`]. The code can be translated quite
//! easily:
//!
//! ```
//! # use std::{mem, ptr, sync::{atomic::{AtomicPtr, Ordering}, Arc}};
//! # use housekeeping::{Cleanups, Guard};
//! #
//! # #[derive(Default)]
//! # struct Data {}
//! #
//! # fn somehow_compute() -> Data { Data {} }
//! # fn somehow_consume(data: &Data) { let _ = data; }
//! #
//! let cleanups = Arc::new(Cleanups::new());
//! // The data stored is protected by 'cleanups'.
//! let data: Box<Data> = Default::default();
//! let data_ptr = Box::into_raw(data);
//! let data: AtomicPtr<Data> = AtomicPtr::new(data_ptr);
//!
//! std::thread::scope(|s| {
//! s.spawn(|| {
//! // Obtain a guard for safely handling concurrent memory.
//! let guard = cleanups.clone().register();
//!
//! // Replace the existing data.
//! let new_data: Box<Data> = Box::new(somehow_compute());
//! let new_data_ptr = Box::into_raw(new_data);
//! let old_data_ptr = data.swap(new_data_ptr, Ordering::AcqRel);
//!
//! // Defer the cleanup of the existing data.
//! // SAFETY:
//! // - '*mut Data' can be sent between threads safely.
//! // - The closure is 'move' and is 'static.
//! unsafe { guard.defer_unchecked(move || {
//! mem::drop(Box::from_raw(old_data_ptr))
//! }) };
//! });
//!
//! // Obtain a guard for safely handling concurrent memory.
//! let guard = cleanups.clone().register();
//!
//! // Use the data.
//! let data_ptr = data.load(Ordering::Acquire).cast_const();
//! // SAFETY: Safe as long as 'guard' is held.
//! somehow_consume(unsafe { &*data_ptr });
//! });
//!
//! let _ = unsafe { Box::from_raw(data.into_inner()) };
//! ```
//!
//! Every thread requires a [`Guard`], which it can obtain by calling
//! `register()`. While a thread is holding a [`Guard`], it can safely access
//! shared memory; shared memory visible to the thread will not be cleaned up.
//! There is no need for reference counting, so [`Arc`] can be replaced with
//! [`Box`].
//!
//! [`Box`]: https://doc.rust-lang.org/stable/std/boxed/struct.Box.html
//!
//! If a [`Guard`] is held for a long time, it should be
//! [`refresh()`][Guard::refresh()]-ed periodically. This is important for
//! maintaining forward progress. If the guard cannot be refreshed (e.g. if
//! a slow I/O operation is occurring), it should be dropped and a new guard
//! should be registered afterwards.
//!
//! # Rationale
//!
//! [`seize`] and [`crossbeam_epoch`] have different goals and limitations from
//! [`housekeeping`]. [`seize`] only supports atomic pointers, and prioritizes
//! tail latency over average throughput. [`crossbeam_epoch`] supports other
//! more atomic variables, but it has a complex implementation and does not
//! support MIRI.
//!
//! [`housekeeping`] has a very simple implementation, allows arbitrary
//! resources to be cleaned up, and is compatible with MIRI and Loom. It offers
//! flexible low-level interfaces (e.g. [`RawGuard`]) which give the user more
//! control, e.g. with how cleanup actions are batched and stored.
//!
//! # Crate Details
//!
//! [`housekeeping`] does not have any dependencies; it is a small crate that
//! does one thing and does it well. It is OS agnostic and `no_std` compatible,
//! although it require atomic operations and heap allocations.
//!
//! ## Feature Flags
//!
//! [`housekeeping`] has the following feature flags:
//!
//! - `loom`: Enable [Loom] support. **This should only be enabled during
//! testing**. When this flag is enabled, [`housekeeping`] can only be used
//! within Loom tests. Its atomic operations will be exposed to Loom so that
//! different memory orderings can be explored.
//!
//! [Loom]: https://docs.rs/loom
//!
//! # Algorithm
//!
//! [`housekeeping`] implements quiescent state based reclamation (QSBR), which
//! is quite similar to conventional epoch-based reclamation. It operates as
//! follows:
//!
//! During the execution of the program, objects may be _shared_; they may be
//! part of a _global view_. For example, a concurrent hash table can be shared
//! between threads, in which case it and its elements are globally visible.
//! Sharing is transitive: anything accessible from a shared object is also
//! considered shared.
//!
//! Removing a shared object is non-trivial; multiple threads may be accessing
//! it concurrently. The object cannot be dropped or recycled until all threads
//! are guaranteed not to observe it. First, the object will be detached from
//! the global view, so that any threads known to observe the updated global
//! view cannot access it. Then, the object is _retired_ (its drop/recycle is
//! _deferred_) until a point where all threads are known not to observe it.
//!
//! QSBR tracks the progress of the synchronization between threads. It measures
//! progress in units of _phases_. At any time, every thread (more specifically,
//! every guard) is _using_ a particular phase. Each phase represents a snapshot
//! of global memory; all threads using a phase will observe memory at least as
//! recent as that snapshot.
//!
//! When a guard is refreshed or dropped, it will try to make progress: if all
//! threads are using the same phase, a new phase will be made (with a more
//! recent snapshot of memory), and all threads will be directed to move to it.
//! Eventually, shared objects that were removed in old phases are guaranteed
//! not to be used by any threads, so cleanups deferred in those phases can be
//! executed.
//!
//! For efficiency, cleanups are collected in thread-local _batches_ when they
//! are being deferred. Guards hold a local batch that they accumulate into;
//! when the batch is full, it is added to the global list for the current
//! phase. By default, [`SimpleBatch`] is used, but a custom batch type can be
//! specified to support different kinds of cleanup operations.
//!
//! Most of the complexity of this crate is in the QSBR implementation (how
//! phases are managed and the synchronizing operations involved). It is quite
//! difficult to implement correctly, so it has been re-exported publicly (as
//! the [`qsbr`] module) so others can build on it.
extern crate alloc;
pub use Cleanups;
pub use ;
pub use SimpleBatch;
//----------- Loom support -----------------------------------------------------