Lines
100 %
Functions
87.12 %
Branches
use std::cell::UnsafeCell;
use std::mem::ManuallyDrop;
use std::ops::Deref;
use std::ops::DerefMut;
use crate::storage::GlobalTermPoolGuard;
use crate::storage::THREAD_TERM_POOL;
/// A mutex that prevents garbage collection by holding a shared read lock on
/// the [super::GlobalTermPool] for the duration of the guard's lifetime.
/// Returns a [GcMutexGuard] on access.
///
/// # Safety
/// The `GcMutex` returns guards that are tied to the thread-local storage of
/// [crate::storage::THREAD_TERM_POOL]. This means that the guard must be
/// dropped before this thread-local storage is dropped. Otherwise
/// use-after-free will occur, which is undefined behaviour.
pub struct GcMutex<T> {
inner: UnsafeCell<T>,
}
// SAFETY: Sharing GcMutex<T> across threads gives shared &T access, so T must be Send.
unsafe impl<T: Send> Send for GcMutex<T> {}
// SAFETY: Sharing &GcMutex<T> across threads gives shared &T access, so T must be Sync.
unsafe impl<T: Send + Sync> Sync for GcMutex<T> {}
impl<T> GcMutex<T> {
pub fn new(value: T) -> GcMutex<T> {
GcMutex {
inner: UnsafeCell::new(value),
/// Provides shared access to the underlying value, returning a [GcMutexReadGuard].
/// The returned guard holds a read lock on the global term pool, preventing
/// garbage collection for its lifetime. It only provides immutable access.
pub fn lock(&self) -> GcMutexReadGuard<'_, T> {
GcMutexReadGuard {
mutex: self,
guard: ManuallyDrop::new(THREAD_TERM_POOL.with(|tp| unsafe {
std::mem::transmute::<_, GlobalTermPoolGuard<'_>>(
tp.term_pool().read_recursive().expect("Lock poisoned!"),
)
})),
/// Provides exclusive mutable access to the underlying value, returning a [GcMutexGuard].
/// Takes `&mut self` so only one mutable guard can exist at a time; the borrow
/// checker enforces that no other guard (read or write) coexists.
pub fn lock_mut(&mut self) -> GcMutexGuard<'_, T> {
GcMutexGuard {
/// A read-only guard produced by [GcMutex::lock]. Holds a shared read lock on
/// the global term pool for its lifetime, preventing garbage collection.
pub struct GcMutexReadGuard<'a, T> {
mutex: &'a GcMutex<T>,
/// Only used to avoid garbage collection, will be released on drop.
guard: ManuallyDrop<GlobalTermPoolGuard<'a>>,
impl<T> Deref for GcMutexReadGuard<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*self.mutex.inner.get() }
impl<T> Drop for GcMutexReadGuard<'_, T> {
fn drop(&mut self) {
if self.guard.read_depth() == 1 {
THREAD_TERM_POOL.with(|tp| unsafe { tp.trigger_delayed_garbage_collection(&mut self.guard) })
} else {
unsafe { ManuallyDrop::drop(&mut self.guard) };
/// A read-write guard produced by [GcMutex::lock_mut]. Provides both
/// [Deref] and [DerefMut]. Because [GcMutex::lock_mut] takes `&mut self`,
/// the borrow checker guarantees this is the only live guard for its lifetime.
pub struct GcMutexGuard<'a, T> {
impl<T> Deref for GcMutexGuard<'_, T> {
impl<T> DerefMut for GcMutexGuard<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.mutex.inner.get() }
impl<T> Drop for GcMutexGuard<'_, T> {
// If this is the last guard, we can trigger garbage collection when it was delayed earlier.
// Just drop the guard