intern_arc/ref_count.rs
1/*
2 * Copyright 2021 Actyx AG
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16use crate::loom::*;
17use parking_lot::lock_api::RawMutex;
18use std::{
19 cell::UnsafeCell,
20 fmt::{Formatter, Pointer, Result},
21 ops::Deref,
22 ptr::NonNull,
23 sync::{Arc, Weak},
24};
25
26/// sealed trait pattern for trait Interner
27mod private {
28 pub trait Sealed {}
29
30 impl Sealed for () {}
31 impl<T: ?Sized + Eq + std::hash::Hash> Sealed for crate::hash::Hash<T> {}
32 impl<T: ?Sized + Ord> Sealed for crate::tree::Ord<T> {}
33}
34
35/// This is an internal trait that is not meant to be implemented outside this crate.
36pub trait Interner: private::Sealed + Sized {
37 type T: ?Sized;
38 fn remove(&self, value: &Interned<Self>) -> (bool, Option<Interned<Self>>);
39}
40/// This is a bogus shim impl used only for being able to compute the size of a RefCounted structure.
41impl Interner for () {
42 type T = ();
43 fn remove(&self, _value: &Interned<Self>) -> (bool, Option<Interned<Self>>) {
44 (false, None)
45 }
46}
47
48struct State<I> {
49 // inlining the raw mutex manually here to bring overhead down from 24 to 16 bytes
50 // on 64bit platforms (which unfortunately implies writing our own `struct Guard`)
51 mutex: parking_lot::RawMutex,
52 /// 4 billion refs ought to be enough for anybody, plus this allows the RawMutex
53 /// to live inside the same word on 64bit architectures.
54 refs: UnsafeCell<u32>,
55 cleanup: UnsafeCell<Option<Weak<I>>>,
56}
57impl<I: Interner> State<I> {
58 pub fn new() -> Self {
59 Self {
60 mutex: parking_lot::RawMutex::INIT,
61 refs: UnsafeCell::new(1),
62 cleanup: UnsafeCell::new(None),
63 }
64 }
65 pub fn lock(&self) -> Guard<I> {
66 self.mutex.lock();
67 Guard(self)
68 }
69}
70/// Safety: having the Guard is equivalent to owning the mutex lock, which holds a
71/// reference to the wrapped data, so dereferencing those pointers is safe.
72struct Guard<'a, I>(&'a State<I>);
73impl<'a, I> Guard<'a, I> {
74 pub fn refs(&self) -> u32 {
75 unsafe { *self.0.refs.get() }
76 }
77 pub fn refs_mut(&mut self) -> &mut u32 {
78 unsafe { &mut *self.0.refs.get() }
79 }
80 pub fn cleanup(&mut self) -> &mut Option<Weak<I>> {
81 unsafe { &mut *self.0.cleanup.get() }
82 }
83}
84impl<'a, I> Drop for Guard<'a, I> {
85 fn drop(&mut self) {
86 unsafe { self.0.mutex.unlock() };
87 }
88}
89
90// repr(C) is needed so that we can determine the correct allocation layout without
91// knowning the layout of `value`; this is needed to compute the combined layout from
92// these two pieces
93#[repr(C)]
94struct RefCounted<I: Interner> {
95 state: State<I>,
96 value: I::T,
97}
98
99impl<I: Interner> RefCounted<I> {
100 fn from_box(value: Box<I::T>) -> NonNull<Self> {
101 // figure out the needed allocation size — this requires #[repr(C)]
102 let layout = Layout::new::<RefCounted<()>>()
103 .extend(Layout::for_value(value.as_ref()))
104 .unwrap() // fails only on address range overflow
105 .0
106 .pad_to_align();
107 unsafe {
108 // allocate using global allocator
109 let ptr = alloc(layout);
110 // get value pointer with the right metadata (e.g. string length)
111 // while making sure to NOT DROP THE BOX
112 let b = Box::leak(value) as *mut I::T;
113 // construct correct (fat) pointer from allocation result:
114 // - make a copy of the passed-in Box pointer
115 // - overwrite the first sizeof::<usize>() bytes with the new address
116 // this keeps the metadata (second machine word) intact
117 let ptr = {
118 let mut temp = b as *mut Self;
119 // unfortunately <*mut>::set_ptr_value is still experimental, but this is what it does:
120 std::ptr::write(&mut temp as *mut _ as *mut *mut u8, ptr);
121 temp
122 };
123 // write the fields
124 std::ptr::write(&mut (*ptr).state, State::new());
125 let num_bytes = std::mem::size_of_val(&*b);
126 if num_bytes > 0 {
127 std::ptr::copy_nonoverlapping(
128 // copy payload value byte-wise, because what else can we do?
129 b as *const u8,
130 &mut (*ptr).value as *mut _ as *mut u8,
131 num_bytes,
132 );
133 // free the memory of the ex-Box; global allocator does not allow zero-sized allocations
134 // so this must be guarded by num_bytes > 0
135 #[cfg(not(loom))]
136 dealloc(b as *mut u8, Layout::for_value(&*b));
137 #[cfg(loom)]
138 std::alloc::dealloc(b as *mut u8, Layout::for_value(&*b));
139 }
140
141 NonNull::new_unchecked(ptr)
142 }
143 }
144
145 fn from_sized(value: I::T) -> NonNull<Self>
146 where
147 I::T: Sized,
148 {
149 let b = Box::new(Self {
150 state: State::new(),
151 value,
152 });
153 NonNull::from(Box::leak(b))
154 }
155}
156
157pub struct Interned<I: Interner> {
158 inner: NonNull<RefCounted<I>>,
159}
160
161unsafe impl<I: Interner> Send for Interned<I> where I::T: Send + Sync + 'static {}
162unsafe impl<I: Interner> Sync for Interned<I> where I::T: Send + Sync + 'static {}
163
164impl<I: Interner> Interned<I> {
165 pub(crate) fn ref_count(&self) -> u32 {
166 self.lock().refs()
167 }
168
169 fn lock(&self) -> Guard<I> {
170 // this is safe since the existence of &self proves that the pointer is still valid
171 unsafe { self.inner.as_ref().state.lock() }
172 }
173
174 pub(crate) fn from_box(value: Box<I::T>) -> Self {
175 Self {
176 inner: RefCounted::from_box(value),
177 }
178 }
179
180 pub(crate) fn from_sized(value: I::T) -> Self
181 where
182 I::T: Sized,
183 {
184 Self {
185 inner: RefCounted::from_sized(value),
186 }
187 }
188
189 pub(crate) fn make_hot(&mut self, set: &Arc<I>) {
190 let mut state = self.lock();
191 *state.cleanup() = Some(Arc::downgrade(set));
192 }
193}
194
195// use the two upper bits as spin-wait conditions
196const MAX_REFCOUNT: u32 = u32::MAX - 2;
197
198impl<I: Interner> Clone for Interned<I> {
199 fn clone(&self) -> Self {
200 let refs = {
201 let mut state = self.lock();
202 *state.refs_mut() += 1;
203 state.refs()
204 };
205
206 if refs > MAX_REFCOUNT {
207 // the below misspelling is deliberate
208 panic!("either you are running on an 8086 or you are leaking Interned values at a phantastic rate");
209 }
210 let ret = Self { inner: self.inner };
211 #[cfg(feature = "println")]
212 println!("{:?} clone {:p}", current().id(), *self);
213 ret
214 }
215}
216
217impl<I: Interner> Drop for Interned<I> {
218 fn drop(&mut self) {
219 // printing `self` to mark this particular execution (points to the stack)
220 // printing `*self` to mark the interned value (as printed by `clone`)
221 #[cfg(feature = "println")]
222 println!("{:?} dropping {:p} {:p}", current().id(), self, *self);
223
224 // preconditions:
225 // - this Interned may or may not be referenced by an interner (since the interner can be dropped)
226 // - the `self` reference guarantees that the reference count is at least one
227 // - whatever happens, we must decrement the reference count by one
228 // - if the only remaining reference is the interner map, we need to try to remove it
229 // (this races against an `intern` call for the same value and against dropping the interner)
230 //
231 // IMPORTANT NOTE: each Interned starts out with two references! (by virtue of creation and first clone)
232 //
233 // Also, THE REMOVAL POINTER NEEDS TO BE USED EXACTLY ONCE!
234
235 // take the lock — we MUST NOT hold this lock while calling the cleanup function!
236 // (A-B vs B-A deadlock would occur otherwise, since interning first takes the interner lock, then this one)
237 let mut state = self.lock();
238
239 #[cfg(feature = "println")]
240 println!(
241 "{:?} read {} {:p} {:p}",
242 current().id(),
243 state.refs(),
244 self,
245 *self
246 );
247
248 // decrement the count
249 *state.refs_mut() -= 1;
250
251 // two cases require action:
252 // - count was two: perform the removal (unless already done)
253 // - count was one: deallocate
254
255 if state.refs() > 1 {
256 return;
257 }
258
259 if state.refs() == 1 {
260 // the other reference could be the map or external (if the map was dropped and we didn’t get here yet)
261 // so this races against:
262 // 1. map being dropped
263 // 2. same value being interned again
264 // 3. other external reference being dropped
265 // In the dropping cases, the other thread saw read == 1.
266 if let Some(cleanup) = state.cleanup().take() {
267 #[cfg(feature = "println")]
268 println!("{:?} removing {:p} {:p}", current().id(), self, *self);
269 // At this point, the other remaining reference can either be the interner or an
270 // external one (if the interner was already dropped).
271 if let Some(strong) = cleanup.upgrade() {
272 // Interner is still there, so the other reference is in there; we may race
273 // against interning of the same value, which may already have taken the interner
274 // lock, so we cannot just call the cleanup function.
275 drop(state);
276 loop {
277 // in here another thread may have found the interned reference and started cloning it,
278 // it might even have dropped it already (but without running cleanup, since we have that.
279 // see the other `else` further down)
280 let (removed, _value) = strong.remove(self);
281 if removed {
282 // nobody interfered and the value is now removed from the interner, we can safely drop it
283 // which will re-enter this drop() function and decrement refs to zero
284 break;
285 } else {
286 // someone interfered, so we need to take the lock again to put things in order
287 let mut state = self.lock();
288 // precondition: we hold the `cleanup` so there is still at least one reference in the
289 // interner — remember that we hold a strong reference to that one!
290 if state.refs() > 1 {
291 // someone else will drop the refs back down to 1 again later, so hand the cleanup
292 // function to them (this may happen far in the future!)
293 *state.cleanup() = Some(cleanup);
294 break;
295 } else {
296 // whoever interfered already dropped their reference again, so we need to retry;
297 // it is important that we drop the lock before retrying, which would happen
298 // automatically, but let’s write it down to make it dead obvious:
299 drop(state);
300 }
301 }
302 }
303 } else {
304 // Interner is gone or on its way out, which means that no concurrent interning
305 // is possible anymore; it also means that the other reference may well be from
306 // the interner, still (it may be dropping right now). Our job here is done.
307 }
308 #[cfg(feature = "println")]
309 println!("{:?} removed {:p}", current().id(), self);
310 } else {
311 // someone else is currently taking care of correctly dropping this value from the interner, see above
312 #[cfg(feature = "println")]
313 println!("{:?} cleanup gone {:p}", current().id(), self);
314 }
315 } else if state.refs() == 0 {
316 #[cfg(feature = "println")]
317 println!("{:?} drop {:p} {:p}", current().id(), self, *self);
318
319 // drop the lock before freeing the memory, otherwise it would use-after-free
320 drop(state);
321
322 // since we created the pointer with Box::leak(), we can recreate the box to drop it
323 unsafe { Box::from_raw(self.inner.as_ptr()) };
324 }
325
326 #[cfg(feature = "println")]
327 println!("{:?} dropend {:p}", current().id(), self);
328 }
329}
330
331impl<I: Interner> PartialEq for Interned<I> {
332 fn eq(&self, other: &Self) -> bool {
333 std::ptr::eq(self.inner.as_ptr(), other.inner.as_ptr())
334 }
335}
336
337impl<I: Interner> Deref for Interned<I> {
338 type Target = I::T;
339
340 fn deref(&self) -> &Self::Target {
341 // safety: the presence of &self guarantees that the value has not yet been dropped
342 &unsafe { self.inner.as_ref() }.value
343 }
344}
345
346impl<I: Interner> Pointer for Interned<I> {
347 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
348 Pointer::fmt(&(&**self as *const I::T), f)
349 }
350}
351
352#[cfg(all(test, not(loom)))]
353mod tests {
354 use super::*;
355 use crate::OrdInterner;
356
357 #[test]
358 fn pointer() {
359 let interner = OrdInterner::new();
360 let i = interner.intern_sized(42);
361 let i2 = i.clone();
362 assert_eq!(format!("{:p}", i), format!("{:p}", i2));
363 }
364
365 #[test]
366 fn size() {
367 use std::mem::size_of;
368 const SIZE: usize = if size_of::<usize>() == 4 { 12 } else { 16 };
369 assert_eq!(size_of::<RefCounted<()>>(), SIZE);
370
371 let fake = RefCounted::<crate::hash::Hash<i32>> {
372 state: State::new(),
373 value: 42,
374 };
375
376 println!("base: {:p}", &fake);
377 let base = &fake as *const _ as *const u8;
378 println!("state: {:p} (base + {})", &fake.state, unsafe {
379 (&fake.state as *const _ as *const u8).offset_from(base)
380 });
381 println!("mutex: {:p} (base + {})", &fake.state.mutex, unsafe {
382 (&fake.state.mutex as *const _ as *const u8).offset_from(base)
383 });
384 println!("refs: {:p} (base + {})", &fake.state.refs, unsafe {
385 (&fake.state.refs as *const _ as *const u8).offset_from(base)
386 });
387 println!("clean: {:p} (base + {})", &fake.state.cleanup, unsafe {
388 (&fake.state.cleanup as *const _ as *const u8).offset_from(base)
389 });
390 println!("value: {:p} (base + {})", &fake.value, unsafe {
391 (&fake.value as *const _ as *const u8).offset_from(base)
392 });
393 }
394}