Skip to content

Commit d1244d2

Browse files
committed
cc: add weakref support
Similar to std::rc::Rc, add downgrade() -> Weak, upgrade() -> Option<Cc> for weakref support.
1 parent b80cc73 commit d1244d2

File tree

4 files changed

+215
-21
lines changed

4 files changed

+215
-21
lines changed

src/cc.rs

Lines changed: 110 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@ use std::ptr::NonNull;
3737
/// The data shared by multiple `RawCc<T, O>` pointers.
3838
#[repr(C)]
3939
pub(crate) struct RawCcBox<T: ?Sized, O: AbstractObjectSpace> {
40-
/// The lowest REF_COUNT_SHIFT bits are used for metadata.
41-
/// The higher bits are used for ref count.
4240
pub(crate) ref_count: O::RefCount,
4341

4442
#[cfg(test)]
@@ -75,16 +73,23 @@ pub struct RawCcBoxWithGcHeader<T: ?Sized, O: AbstractObjectSpace> {
7573
/// ```
7674
pub type Cc<T> = RawCc<T, ObjectSpace>;
7775

76+
/// Weak reference of [`Cc`](type.Cc.html).
77+
pub type Weak<T> = RawWeak<T, ObjectSpace>;
78+
7879
/// Low-level type for [`Cc<T>`](type.Cc.html).
7980
pub struct RawCc<T: ?Sized, O: AbstractObjectSpace>(NonNull<RawCcBox<T, O>>);
8081

82+
/// Low-level type for [`Weak<T>`](type.Weak.html).
83+
pub struct RawWeak<T: ?Sized, O: AbstractObjectSpace>(NonNull<RawCcBox<T, O>>);
84+
8185
// `ManuallyDrop<T>` does not implement `UnwindSafe`. But `CcBox::drop` does
8286
// make sure `T` is dropped. If `T` is unwind-safe, so does `CcBox<T>`.
8387
impl<T: UnwindSafe + ?Sized> UnwindSafe for RawCcBox<T, ObjectSpace> {}
8488

85-
// `NonNull` does not implement `UnwindSafe`. But `Cc` only uses it
89+
// `NonNull` does not implement `UnwindSafe`. But `Cc` and `Weak` only use it
8690
// as a "const" pointer. If `T` is unwind-safe, so does `Cc<T>`.
87-
impl<T: UnwindSafe + ?Sized> UnwindSafe for Cc<T> {}
91+
impl<T: UnwindSafe + ?Sized, O: AbstractObjectSpace> UnwindSafe for RawCc<T, O> {}
92+
impl<T: UnwindSafe + ?Sized, O: AbstractObjectSpace> UnwindSafe for RawWeak<T, O> {}
8893

8994
/// Type-erased `Cc<T>` with interfaces needed by GC.
9095
///
@@ -275,6 +280,11 @@ impl<T: ?Sized, O: AbstractObjectSpace> RawCcBox<T, O> {
275280
self.ref_count.ref_count()
276281
}
277282

283+
#[inline]
284+
fn weak_count(&self) -> usize {
285+
self.ref_count.weak_count()
286+
}
287+
278288
#[inline]
279289
fn set_dropped(&self) -> bool {
280290
self.ref_count.set_dropped()
@@ -348,6 +358,44 @@ impl<T: std::fmt::Debug + ?Sized> OptionalDebug for T {
348358
}
349359
}
350360

361+
impl<T: ?Sized, O: AbstractObjectSpace> RawCc<T, O> {
362+
/// Obtains a "weak reference", a non-owning pointer.
363+
pub fn downgrade(&self) -> RawWeak<T, O> {
364+
let inner = self.inner();
365+
inner.ref_count.inc_weak();
366+
debug::log(|| {
367+
(
368+
inner.debug_name(),
369+
format!("new-weak ({})", inner.ref_count.weak_count()),
370+
)
371+
});
372+
RawWeak(self.0)
373+
}
374+
}
375+
376+
impl<T: ?Sized, O: AbstractObjectSpace> RawWeak<T, O> {
377+
/// Attempts to obtain a "strong reference".
378+
///
379+
/// Returns `None` if the value has already been dropped.
380+
pub fn upgrade(&self) -> Option<RawCc<T, O>> {
381+
let inner = self.inner();
382+
// Make the below operation "atomic".
383+
let _locked = inner.ref_count.locked();
384+
if inner.is_dropped() {
385+
None
386+
} else {
387+
inner.inc_ref();
388+
debug::log(|| {
389+
(
390+
inner.debug_name(),
391+
format!("new-strong ({})", inner.ref_count.ref_count()),
392+
)
393+
});
394+
Some(RawCc(self.0))
395+
}
396+
}
397+
}
398+
351399
impl<T: ?Sized, O: AbstractObjectSpace> RawCc<T, O> {
352400
#[inline]
353401
pub(crate) fn inner(&self) -> &RawCcBox<T, O> {
@@ -380,11 +428,24 @@ impl<T: ?Sized, O: AbstractObjectSpace> RawCc<T, O> {
380428
self.inner().ref_count()
381429
}
382430

431+
#[inline]
432+
fn weak_count(&self) -> usize {
433+
self.inner().weak_count()
434+
}
435+
383436
pub(crate) fn debug_name(&self) -> String {
384437
self.inner().debug_name()
385438
}
386439
}
387440

441+
impl<T: ?Sized, O: AbstractObjectSpace> RawWeak<T, O> {
442+
#[inline]
443+
fn inner(&self) -> &RawCcBox<T, O> {
444+
// safety: CcBox lifetime maintained by ref count. Pointer is valid.
445+
unsafe { self.0.as_ref() }
446+
}
447+
}
448+
388449
impl<T: ?Sized, O: AbstractObjectSpace> Clone for RawCc<T, O> {
389450
#[inline]
390451
fn clone(&self) -> Self {
@@ -398,6 +459,22 @@ impl<T: ?Sized, O: AbstractObjectSpace> Clone for RawCc<T, O> {
398459
}
399460
}
400461

462+
impl<T: ?Sized, O: AbstractObjectSpace> Clone for RawWeak<T, O> {
463+
#[inline]
464+
fn clone(&self) -> Self {
465+
let inner = self.inner();
466+
let ref_count = &inner.ref_count;
467+
ref_count.inc_weak();
468+
debug::log(|| {
469+
(
470+
inner.debug_name(),
471+
format!("clone-weak ({})", ref_count.weak_count()),
472+
)
473+
});
474+
Self(self.0)
475+
}
476+
}
477+
401478
impl<T: ?Sized> Deref for Cc<T> {
402479
type Target = T;
403480

@@ -453,13 +530,41 @@ fn drop_ccbox<T: ?Sized, O: AbstractObjectSpace>(cc_box: *mut RawCcBox<T, O>) {
453530
impl<T: ?Sized, O: AbstractObjectSpace> Drop for RawCc<T, O> {
454531
fn drop(&mut self) {
455532
let ptr: *mut RawCcBox<T, O> = self.0.as_ptr();
533+
let inner = self.inner();
456534
// Block threaded collector. This is needed because "drop()" is a
457535
// complex operation. The whole operation needs to be "atomic".
458-
let _locked = self.inner().ref_count.locked();
536+
let _locked = inner.ref_count.locked();
459537
let old_ref_count = self.dec_ref();
460538
debug::log(|| (self.debug_name(), format!("drop ({})", self.ref_count())));
461539
debug_assert!(old_ref_count >= 1);
462540
if old_ref_count == 1 {
541+
if self.weak_count() == 0 {
542+
// safety: CcBox lifetime maintained by ref count.
543+
drop_ccbox(ptr);
544+
} else {
545+
inner.drop_t();
546+
}
547+
}
548+
}
549+
}
550+
551+
impl<T: ?Sized, O: AbstractObjectSpace> Drop for RawWeak<T, O> {
552+
fn drop(&mut self) {
553+
let ptr: *mut RawCcBox<T, O> = self.0.as_ptr();
554+
let inner = self.inner();
555+
let ref_count = &inner.ref_count;
556+
// Block threaded collector to "freeze" the ref count, for safety.
557+
let _locked = ref_count.locked();
558+
let old_ref_count = ref_count.ref_count();
559+
let old_weak_count = ref_count.dec_weak();
560+
debug::log(|| {
561+
(
562+
inner.debug_name(),
563+
format!("drop-weak ({})", ref_count.weak_count()),
564+
)
565+
});
566+
debug_assert!(old_weak_count >= 1);
567+
if old_ref_count == 0 && old_weak_count == 1 {
463568
// safety: CcBox lifetime maintained by ref count.
464569
drop_ccbox(ptr);
465570
}

src/debug.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ thread_local!(pub(crate) static VERBOSE: bool = std::env::var("VERBOSE").is_ok()
1212

1313
/// Enable debug log for the given scope. Return the debug log.
1414
pub(crate) fn capture_log(mut func: impl FnMut()) -> String {
15+
NEXT_DEBUG_NAME.with(|n| n.set(0));
1516
LAST_NAME.with(|n| n.borrow_mut().clear());
1617
ENABLED.with(|e| e.set(true));
1718
func();

src/lib.rs

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,22 @@
148148
//! }
149149
//! ```
150150
//!
151+
//! ### Weak references
152+
//!
153+
//! Similar to `std::rc::Rc`, use [`Cc::downgrade`](struct.RawCc.html#method.downgrade)
154+
//! to create weak references. Use [`Weak::upgrade`](struct.RawWeak.html#method.upgrade)
155+
//! to test if the value is still alive and to access the value. For example:
156+
//!
157+
//! ```
158+
//! use gcmodule::{Cc, Weak};
159+
//!
160+
//! let value = Cc::new("foo");
161+
//! let weak: Weak<_> = value.downgrade();
162+
//! assert_eq!(*weak.upgrade().unwrap(), "foo");
163+
//! drop(value);
164+
//! assert!(weak.upgrade().is_none()); // Cannot upgrade after dropping value
165+
//! ```
166+
//!
151167
//! # Technical Details
152168
//!
153169
//! ## Memory Layouts
@@ -157,15 +173,16 @@
157173
//! ### Untracked types
158174
//!
159175
//! If [`<T as Trace>::is_type_tracked()`](trait.Trace.html#method.is_type_tracked)
160-
//! returns `false`, the layout is similar to `Rc<T>` but without a `weak_count`:
176+
//! returns `false`, the layout is similar to `Rc<T>`:
161177
//!
162178
//! ```plain,ignore
163179
//! Shared T Pointer
164-
//! +------------------+ .-- Cc<T>
165-
//! | ref_count: usize | <--<
166-
//! |------------------| '-- Cc<T>::clone()
167-
//! | T (shared data) | <--- Cc<T>::deref()
168-
//! +------------------+
180+
//! +-------------------+ .-- Cc<T>
181+
//! | ref_count: usize | <--<
182+
//! | weak_count: usize | '-- Cc<T>::clone()
183+
//! |-------------------|
184+
//! | T (shared data) | <--- Cc<T>::deref()
185+
//! +-------------------+
169186
//! ```
170187
//!
171188
//! ### Tracked types
@@ -176,15 +193,16 @@
176193
//!
177194
//! ```plain,ignore
178195
//! Shared T with GcHeader
179-
//! +------------------+
180-
//! | gc_prev: pointer | ---> GcHeader in a linked list.
181-
//! | gc_next: pointer |
182-
//! | vptr<T>: pointer | ---> Pointer to the `&T as &dyn Trace` virtual table.
183-
//! |------------------|
184-
//! | ref_count: usize | <--- Cc<T>
185-
//! | ---------------- |
186-
//! | T (shared data) | <--- Cc<T>::deref()
187-
//! +------------------+
196+
//! +-------------------+
197+
//! | gc_prev: pointer | ---> GcHeader in a linked list.
198+
//! | gc_next: pointer |
199+
//! | vptr<T>: pointer | ---> Pointer to the `&T as &dyn Trace` virtual table.
200+
//! |-------------------|
201+
//! | ref_count: usize | <--- Cc<T>
202+
//! | weak_count: usize |
203+
//! | ----------------- |
204+
//! | T (shared data) | <--- Cc<T>::deref()
205+
//! +-------------------+
188206
//! ```
189207
//!
190208
//! ## Incorrect `Trace` implementation
@@ -256,7 +274,7 @@ pub mod testutil;
256274
mod trace;
257275
mod trace_impls;
258276

259-
pub use cc::{Cc, RawCc};
277+
pub use cc::{Cc, RawCc, RawWeak, Weak};
260278
pub use collect::{collect_thread_cycles, count_thread_tracked, ObjectSpace};
261279
pub use trace::{Trace, Tracer};
262280

src/tests.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,76 @@ fn test_simple_non_trait_cycles() {
109109
assert_eq!(collect::collect_thread_cycles(), 2);
110110
}
111111

112+
#[test]
113+
fn test_weakref_without_cycles() {
114+
let log = debug::capture_log(|| {
115+
let s1 = Cc::new("S".to_string());
116+
let w1 = s1.downgrade();
117+
let s2 = w1.upgrade().unwrap();
118+
let w2 = w1.clone();
119+
drop(s1);
120+
drop(s2);
121+
let w3 = w2.clone();
122+
assert!(w3.upgrade().is_none());
123+
assert!(w2.upgrade().is_none());
124+
assert!(w1.upgrade().is_none());
125+
});
126+
assert_eq!(
127+
log,
128+
r#"
129+
0: new (CcBox), new-weak (1), new-strong (2), clone-weak (2), drop (1), drop (0), drop (T), clone-weak (3), drop-weak (2), drop-weak (1), drop-weak (0), drop (CcBox)"#
130+
);
131+
}
132+
133+
#[test]
134+
fn test_weakref_with_cycles() {
135+
let log = debug::capture_log(|| {
136+
debug::NEXT_DEBUG_NAME.with(|n| n.set(1));
137+
let a: Cc<RefCell<Vec<Box<dyn Trace>>>> = Cc::new(RefCell::new(Vec::new()));
138+
debug::NEXT_DEBUG_NAME.with(|n| n.set(2));
139+
let b: Cc<RefCell<Vec<Box<dyn Trace>>>> = Cc::new(RefCell::new(Vec::new()));
140+
a.borrow_mut().push(Box::new(b.clone()));
141+
b.borrow_mut().push(Box::new(a.clone()));
142+
let wa = a.downgrade();
143+
let wa1 = wa.clone();
144+
let wb = b.downgrade();
145+
drop(a);
146+
drop(b);
147+
assert!(wa.upgrade().is_some());
148+
assert!(wb.upgrade().is_some());
149+
assert_eq!(collect::collect_thread_cycles(), 2);
150+
assert!(wa.upgrade().is_none());
151+
assert!(wa1.upgrade().is_none());
152+
assert!(wb.upgrade().is_none());
153+
assert!(wb.clone().upgrade().is_none());
154+
});
155+
assert_eq!(
156+
log,
157+
r#"
158+
1: new (CcBoxWithGcHeader)
159+
2: new (CcBoxWithGcHeader), clone (2)
160+
1: clone (2), new-weak (1), clone-weak (2)
161+
2: new-weak (1)
162+
1: drop (1)
163+
2: drop (1)
164+
1: new-strong (2), drop (1)
165+
2: new-strong (2), drop (1)
166+
collect: collect_thread_cycles
167+
2: gc_traverse
168+
1: trace, gc_traverse
169+
2: trace
170+
collect: 2 unreachable objects
171+
2: gc_clone (2)
172+
1: gc_clone (2)
173+
2: drop (T)
174+
1: drop (1), drop (T)
175+
2: drop (1), drop (0)
176+
1: drop (0)
177+
2: clone-weak (2), drop-weak (1), drop-weak (0), drop (CcBoxWithGcHeader)
178+
1: drop-weak (1), drop-weak (0), drop (CcBoxWithGcHeader)"#
179+
);
180+
}
181+
112182
#[test]
113183
fn test_drop_by_ref_count() {
114184
let log = debug::capture_log(|| test_small_graph(3, &[], 0, 0));

0 commit comments

Comments
 (0)