Skip to content

Commit aa024b4

Browse files
authored
Merge pull request #32 from frengor/dev
* Update to 0.6.0 * Replace `Cc::into_inner` with `Cc::try_unwrap`, which now correctly handles weak pointers * Remove `Cc::is_unique` * Rework cleaners implementation
2 parents 449c8cd + 422adaa commit aa024b4

File tree

5 files changed

+183
-52
lines changed

5 files changed

+183
-52
lines changed

CONTRIBUTING.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,15 @@ Thus, marking should be done only:
5252

5353
## Writing tests
5454

55-
Every unit test should start with a call to `tests::reset_state()` to make sure errors in other tests don't impact the current one.
56-
57-
Also, functions marked as `pub(crate)` and used only in unit tests should have the `for_tests` suffix, like `Cc::new_for_tests`.
55+
Every unit test (which makes use of a part of the collector) should start with a call to `tests::reset_state()` to make
56+
sure errors in other tests don't impact the current one.
5857

5958
## Writing documentation
6059

6160
Docs are always built with every feature enabled. This makes it easier to write and maintain the documentation.
6261

63-
However, this also makes it more difficult to write examples, as those must pass CI even when some of the features they
64-
require are disabled. As such, examples are marked as `ignore`d if a feature they need is missing:
62+
However, this also makes it more difficult to write examples, as those must pass CI even when a features they require is disabled.
63+
As such, examples are marked as `ignore`d if a feature they need is missing:
6564
```rust
6665
#![cfg_attr(
6766
feature = "derive",

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ edition.workspace = true
1414
members = ["derive"]
1515

1616
[workspace.package]
17-
version = "0.5.0" # Also update in [dependencies.rust-cc-derive.version]
17+
version = "0.6.0" # Also update in [dependencies.rust-cc-derive.version]
1818
authors = ["fren_gor <goro@frengor.com>"]
1919
repository = "https://github.com/frengor/rust-cc"
2020
categories = ["memory-management", "no-std"]
@@ -49,7 +49,7 @@ std = ["slotmap?/std", "thiserror/std"]
4949
pedantic-debug-assertions = []
5050

5151
[dependencies]
52-
rust-cc-derive = { path = "./derive", version = "=0.5.0", optional = true }
52+
rust-cc-derive = { path = "./derive", version = "=0.6.0", optional = true }
5353
slotmap = { version = "1.0", optional = true }
5454
thiserror = { version = "1.0", package = "thiserror-core", default-features = false }
5555

src/cc.rs

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use alloc::alloc::Layout;
22
use alloc::rc::Rc;
33
use core::cell::UnsafeCell;
44
use core::marker::PhantomData;
5-
use core::mem;
5+
use core::mem::ManuallyDrop;
66
use core::ops::Deref;
77
use core::ptr::{self, drop_in_place, NonNull};
88
use core::borrow::Borrow;
@@ -66,32 +66,52 @@ impl<T: Trace> Cc<T> {
6666
})
6767
}
6868

69-
/// Takes out the value inside a [`Cc`].
70-
///
71-
/// # Panics
72-
/// Panics if the [`Cc`] is not unique (see [`is_unique`]).
73-
///
74-
/// [`is_unique`]: fn@Cc::is_unique
69+
/// Returns the inner value, if the [`Cc`] has exactly one strong reference and the collector is not collecting, finalizing or dropping.
70+
///
71+
/// Otherwise, an [`Err`] is returned with the same [`Cc`] this method was called on.
72+
///
73+
/// This will succeed even if there are outstanding weak references.
7574
#[inline]
7675
#[track_caller]
77-
pub fn into_inner(self) -> T {
78-
assert!(self.is_unique(), "Cc<_> is not unique");
76+
pub fn try_unwrap(self) -> Result<T, Self> {
77+
let cc = ManuallyDrop::new(self); // Never drop the Cc
7978

80-
assert!(
81-
!self.counter_marker().is_in_list_or_queue(),
82-
"Cc<_> is being used by the collector and inner value cannot be taken out (this might have happen inside Trace, Finalize or Drop implementations)."
83-
);
79+
if cc.strong_count() != 1 {
80+
// No need to access the state here
81+
return Err(ManuallyDrop::into_inner(cc));
82+
}
8483

85-
// Make sure self is not into POSSIBLE_CYCLES before deallocating
86-
remove_from_list(self.inner.cast());
84+
let res = try_state(|state| {
85+
if state.is_collecting() || state.is_dropping() {
86+
return None;
87+
}
8788

88-
// SAFETY: self is unique and is not inside any list
89-
unsafe {
90-
let t = ptr::read(self.inner().get_elem());
91-
let layout = self.inner().layout();
92-
let _ = try_state(|state| cc_dealloc(self.inner, layout, state));
93-
mem::forget(self); // Don't call drop on this Cc
94-
t
89+
#[cfg(feature = "finalization")]
90+
if state.is_finalizing() {
91+
// To make this method behave the same outside of collections, unwrapping while finalizing is prohibited
92+
return None;
93+
}
94+
95+
remove_from_list(cc.inner.cast());
96+
97+
// Disable upgrading weak ptrs
98+
#[cfg(feature = "weak-ptrs")]
99+
cc.inner().drop_metadata();
100+
// There's no reason here to call CounterMarker::set_dropped, since the pointed value will not be dropped
101+
102+
// SAFETY: cc is unique and no weak pointer can be upgraded
103+
let t = unsafe { ptr::read(cc.inner().get_elem()) };
104+
let layout = cc.inner().layout();
105+
// SAFETY: cc is unique, is not inside any list and no weak pointer can be upgraded
106+
unsafe {
107+
cc_dealloc(cc.inner, layout, state);
108+
}
109+
Some(t)
110+
});
111+
112+
match res {
113+
Ok(Some(t)) => Ok(t),
114+
_ => Err(ManuallyDrop::into_inner(cc)),
95115
}
96116
}
97117
}
@@ -109,12 +129,6 @@ impl<T: ?Sized + Trace> Cc<T> {
109129
self.counter_marker().counter() as u32
110130
}
111131

112-
/// Returns `true` if the strong reference count is `1`, `false` otherwise.
113-
#[inline]
114-
pub fn is_unique(&self) -> bool {
115-
self.strong_count() == 1
116-
}
117-
118132
/// Makes the value in the managed allocation finalizable again.
119133
///
120134
/// # Panics

src/cleaners/mod.rs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
//! As such, *when possible* it's suggested to prefer cleaners and disable finalization.
2626
2727
use alloc::boxed::Box;
28-
use core::cell::RefCell;
2928
use core::fmt::{self, Debug, Formatter};
29+
use core::cell::{RefCell, UnsafeCell};
3030
use slotmap::{new_key_type, SlotMap};
3131

3232
use crate::{Cc, Context, Finalize, Trace};
@@ -37,7 +37,7 @@ new_key_type! {
3737
}
3838

3939
struct CleanerMap {
40-
map: RefCell<Option<SlotMap<CleanerKey, CleaningAction>>>, // The Option is used to avoid allocating until a cleaning action is registered
40+
map: RefCell<SlotMap<CleanerKey, CleaningAction>>,
4141
}
4242

4343
unsafe impl Trace for CleanerMap {
@@ -63,17 +63,15 @@ impl Drop for CleaningAction {
6363
///
6464
/// All the cleaning actions registered in a `Cleaner` are run when it is dropped, unless they have been manually executed before.
6565
pub struct Cleaner {
66-
cleaner_map: Cc<CleanerMap>,
66+
cleaner_map: UnsafeCell<Option<Cc<CleanerMap>>>, // The Option is used to avoid allocating until a cleaning action is registered
6767
}
6868

6969
impl Cleaner {
7070
/// Creates a new [`Cleaner`].
7171
#[inline]
7272
pub fn new() -> Cleaner {
7373
Cleaner {
74-
cleaner_map: Cc::new(CleanerMap {
75-
map: RefCell::new(None),
76-
}),
74+
cleaner_map: UnsafeCell::new(None),
7775
}
7876
}
7977

@@ -87,21 +85,27 @@ impl Cleaner {
8785
/// be leaked and the cleaning action will never be executed.
8886
#[inline]
8987
pub fn register(&self, action: impl FnOnce() + 'static) -> Cleanable {
90-
let map_key = self.cleaner_map
91-
.map
92-
.borrow_mut()
93-
.get_or_insert_with(|| SlotMap::with_capacity_and_key(3))
94-
.insert(CleaningAction(Some(Box::new(action))));
88+
let cc = {
89+
// SAFETY: no reference to the Option already exists
90+
let map = unsafe { &mut *self.cleaner_map.get() };
91+
92+
map.get_or_insert_with(|| Cc::new(CleanerMap {
93+
map: RefCell::new(SlotMap::with_capacity_and_key(3)),
94+
}))
95+
};
96+
97+
let map_key = cc.map.borrow_mut().insert(CleaningAction(Some(Box::new(action))));
9598

9699
Cleanable {
97-
cleaner_map: self.cleaner_map.downgrade(),
100+
cleaner_map: cc.downgrade(),
98101
key: map_key,
99102
}
100103
}
101104

102105
#[cfg(all(test, feature = "std"))] // Only used in unit tests
103106
pub(crate) fn has_allocated(&self) -> bool {
104-
self.cleaner_map.map.borrow().is_some()
107+
// SAFETY: no reference to the Option already exists
108+
unsafe { (*self.cleaner_map.get()).is_some() }
105109
}
106110
}
107111

@@ -146,10 +150,11 @@ impl Cleanable {
146150
// Try upgrading to see if the CleanerMap hasn't been deallocated
147151
let Some(cc) = self.cleaner_map.upgrade() else { return };
148152

149-
// Just return in case try_borrow_mut fails or the map is None
150-
// (the latter shouldn't happen, but better be sure)
151-
let Ok(mut ref_mut) = cc.map.try_borrow_mut() else { return };
152-
let Some(map) = &mut *ref_mut else { return };
153+
// Just return in case try_borrow_mut fails
154+
let Ok(mut map) = cc.map.try_borrow_mut() else {
155+
crate::utils::cold(); // Should never happen
156+
return;
157+
};
153158
let _ = map.remove(self.key);
154159
}
155160
}

src/tests/cc.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,3 +448,116 @@ fn buffered_objects_count_test() {
448448
drop(cc);
449449
collect_cycles();
450450
}
451+
452+
#[test]
453+
fn try_unwrap_test() {
454+
reset_state();
455+
456+
let cc = Cc::new(5u32);
457+
458+
#[cfg(feature = "weak-ptrs")]
459+
let weak = cc.downgrade();
460+
461+
let unwrapped = cc.try_unwrap();
462+
assert_eq!(5, unwrapped.unwrap());
463+
464+
#[cfg(feature = "weak-ptrs")]
465+
assert!(weak.upgrade().is_none());
466+
}
467+
468+
#[test]
469+
fn fail_try_unwrap_test() {
470+
reset_state();
471+
472+
let cc = Cc::new(5u32);
473+
let copy = cc.clone();
474+
475+
#[cfg(feature = "weak-ptrs")]
476+
let weak = cc.downgrade();
477+
478+
assert!(cc.try_unwrap().is_err()); // cc dropped here
479+
480+
#[cfg(feature = "weak-ptrs")]
481+
assert!(weak.upgrade().is_some());
482+
483+
let unwrapped = copy.try_unwrap();
484+
assert_eq!(5, unwrapped.unwrap());
485+
486+
#[cfg(feature = "weak-ptrs")]
487+
assert!(weak.upgrade().is_none());
488+
}
489+
490+
#[cfg(feature = "finalization")]
491+
#[test]
492+
fn finalization_try_unwrap_test() {
493+
reset_state();
494+
495+
struct Finalizable {
496+
other: RefCell<Option<Cc<u32>>>,
497+
}
498+
499+
unsafe impl Trace for Finalizable {
500+
fn trace(&self, ctx: &mut Context<'_>) {
501+
self.other.trace(ctx);
502+
}
503+
}
504+
505+
impl Finalize for Finalizable {
506+
fn finalize(&self) {
507+
let res = self.other.take().unwrap().try_unwrap();
508+
match res {
509+
Err(cc) => assert_eq!(5, *cc),
510+
_ => panic!("try_unwrap returned an Ok(...) value during finalization."),
511+
}
512+
}
513+
}
514+
515+
let _ = Cc::new(Finalizable {
516+
other: RefCell::new(Some(Cc::new(5u32))),
517+
});
518+
}
519+
520+
#[cfg(feature = "finalization")]
521+
#[test]
522+
fn cyclic_finalization_try_unwrap_test() {
523+
reset_state();
524+
525+
thread_local! {
526+
static FINALIZED: Cell<bool> = Cell::new(false);
527+
}
528+
529+
struct Cyclic {
530+
cyclic: RefCell<Option<Cc<Self>>>,
531+
other: RefCell<Option<Cc<u32>>>,
532+
}
533+
534+
unsafe impl Trace for Cyclic {
535+
fn trace(&self, ctx: &mut Context<'_>) {
536+
self.cyclic.trace(ctx);
537+
self.other.trace(ctx);
538+
}
539+
}
540+
541+
impl Finalize for Cyclic {
542+
fn finalize(&self) {
543+
FINALIZED.with(|fin| fin.set(true));
544+
state(|state| assert!(state.is_collecting()));
545+
546+
let res = self.other.take().unwrap().try_unwrap();
547+
match res {
548+
Err(cc) => assert_eq!(5, *cc),
549+
_ => panic!("try_unwrap returned an Ok(...) value during collection."),
550+
}
551+
}
552+
}
553+
554+
let cc = Cc::new(Cyclic {
555+
cyclic: RefCell::new(None),
556+
other: RefCell::new(Some(Cc::new(5u32))),
557+
});
558+
*cc.cyclic.borrow_mut() = Some(cc.clone());
559+
drop(cc);
560+
collect_cycles();
561+
562+
FINALIZED.with(|fin| assert!(fin.get()));
563+
}

0 commit comments

Comments
 (0)