Skip to content

Commit

Permalink
Merge branch 'box_immortal'
Browse files Browse the repository at this point in the history
  • Loading branch information
EricLBuehler committed Oct 22, 2023
2 parents 09522c9 + d148826 commit 28b3537
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 45 deletions.
92 changes: 55 additions & 37 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -919,49 +919,67 @@ impl<T> Trc<T> {
Some(elem)
}

#[cfg(immortal)]
/// Create an immortal Trc. After this method call, no writes to any reference counts (local, shared, weak) will occur with the exception
/// of [`Weak::upgrade`]. This also means that any [`Drop`] invocations are ignored. To drop, see [`Trc::drop_immortal`]. It is imperative
/// to call this function to prevent a memory leak.
///
/// Once an immortal object is created, no reference counts are tracked and so it is unsafe to convert to a mortal
/// Trc after this method call.
pub fn create_immortal(self) -> Self {
unsafe { self.shared.as_ref() }
.atomicref
.store(usize::MAX, core::sync::atomic::Ordering::SeqCst);
unsafe { self.shared.as_ref() }
.weakcount
.store(usize::MAX, core::sync::atomic::Ordering::SeqCst);
self
}

#[cfg(immortal)]
/// Drop an immortal Trc. This is a highly dangerous function as any other references being dropped after it is called
/// causes immediate UB.
/// Convert to a [`Box`] if there are no other `Trc`, [`SharedTrc`] or [`Weak`] pointers to the same allocation.
/// Otherwise, return [`None`] because it would be unsafe to move a shared value.
///
/// # Safety
/// - All references to the data held by this Trc (other Trc, SharedTrc, or Weak objects) must have std::mem:forget called
/// # Examples
/// ```
/// use trc::Trc;
///
/// # Panics
/// If this Trc is not immortal (it did not come from [`Trc::create_immortal`]).
pub unsafe fn drop_immortal(self) {
assert!(
unsafe { self.shared.as_ref() }
/// let mut trc = Trc::new(100);
/// let boxed = Trc::to_box(trc).unwrap();
/// assert_eq!(*boxed, 100);
/// ```
#[inline]
pub fn to_box(this: Self) -> Option<Box<T>> {
//Acquire the weakcount if it is == 1
if unsafe { this.shared.as_ref() }
.weakcount
.compare_exchange(
1,
usize::MAX,
core::sync::atomic::Ordering::Acquire,
core::sync::atomic::Ordering::Relaxed,
)
.is_ok()
{
//Acquire the atomicref
let unique = unsafe { this.shared.as_ref() }
.atomicref
.load(core::sync::atomic::Ordering::Acquire)
== usize::MAX
);
drop(unsafe { Box::from_raw(self.threadref.as_ptr()) });
== 1;

core::sync::atomic::fence(core::sync::atomic::Ordering::Acquire);
unsafe { core::ptr::drop_in_place(addr_of_mut!((*self.shared.as_ptr()).data)) };
//Synchronize with the previous Acquire
unsafe { this.shared.as_ref() }
.weakcount
.store(1, core::sync::atomic::Ordering::Release);

if unique && *unsafe { this.threadref.as_ref() } == 1 {
let SharedTrcInternal {
atomicref: _,
weakcount: _,
data,
} = unsafe { core::ptr::read(this.shared.as_ptr()) }; //Unsafety is OK as we have the only ref now

//Drop & free shared, effectively like the weak dropping.
let layout = Layout::for_value(unsafe { &*self.shared.as_ptr() });
unsafe { std::alloc::dealloc(self.shared.as_ptr().cast(), layout) };
// Dropping
drop(unsafe { Box::from_raw(this.threadref.as_ptr()) });

std::mem::forget(self);
core::sync::atomic::fence(core::sync::atomic::Ordering::Acquire);
unsafe { core::ptr::drop_in_place(addr_of_mut!((*this.shared.as_ptr()).data)) };

// Drop & free shared, effectively like the weak dropping.
let layout = Layout::for_value(unsafe { &*this.shared.as_ptr() });
unsafe { std::alloc::dealloc(this.shared.as_ptr().cast(), layout) };

std::mem::forget(this);

Some(Box::new(data))
} else {
None
}
} else {
None
}
}
}

Expand Down Expand Up @@ -1162,7 +1180,7 @@ impl<T: ?Sized> Trc<T> {
unsafe { addr_of_mut!((*sharedptr).data) }
}

/// Get a &mut reference to the internal data if there are no other `Trc` or [`Weak`] pointers to the same allocation.
/// Get a &mut reference to the internal data if there are no other `Trc`, [`SharedTrc`] or [`Weak`] pointers to the same allocation.
/// Otherwise, return [`None`] because it would be unsafe to mutate a shared value.
///
/// # Examples
Expand Down
12 changes: 4 additions & 8 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,13 +375,9 @@ fn test_ex4() {
assert_eq!(*trc, 100);
}

#[cfg(immortal)]
#[test]
fn test_immortal_trc() {
let trc = Trc::new(()).create_immortal();
let trc2 = trc.clone();
assert_eq!(Trc::atomic_count(&trc), usize::MAX);
assert_eq!(Trc::atomic_count(&trc2), usize::MAX);
drop(trc2);
unsafe { Trc::drop_immortal(trc) };
fn test_to_box() {
let trc = Trc::new(123);
let boxed = Trc::to_box(trc);
assert!(boxed.is_some());
}

0 comments on commit 28b3537

Please sign in to comment.