cycle_ptr 0.1.1

Smart pointers, with cycles
Documentation
//! Implement logic for deferring garbage collections.

#[cfg(feature = "multi_thread")]
use crate::generation::sync_task;
use crate::generation::task;
use std::cell::RefCell;
use std::fmt;
use std::marker::PhantomData;

/// Inner part of task list.
/// Contains the actual tasks.
#[derive(Debug, Default)]
struct TaskListInner {
    /// All deferred [GcTask][crate::GcTask].
    tasks: Vec<task::GcTask>,
    /// All deferred [sync::GcTask][crate::sync::GcTask].
    #[cfg(feature = "multi_thread")]
    sync_tasks: Vec<sync_task::GcTask>,
}

/// List of all deferred tasks.
#[derive(Debug)]
struct TaskList {
    /// Counter which tests if this task-list is active.
    active: usize,
    /// The actual tasks.
    inner: TaskListInner,
}

impl TaskList {
    /// Instantiate a new [TaskList].
    const fn new() -> Self {
        Self {
            active: 0,
            inner: TaskListInner::new(),
        }
    }
}

impl TaskListInner {
    /// Create new empty [TaskListInner].
    const fn new() -> Self {
        Self {
            tasks: Vec::new(),
            #[cfg(feature = "multi_thread")]
            sync_tasks: Vec::new(),
        }
    }

    /// Submit all tasks on this task list for execution.
    ///
    /// Submitted tasks are submitted to their handler, which may defer them in some other way.
    #[inline(never)]
    fn execute_all_tasks(&mut self) {
        std::mem::take(&mut self.tasks)
            .into_iter()
            .for_each(|task| {
                if let Some(task) = task.post_no_defer() {
                    task.run();
                }
            });

        #[cfg(feature = "multi_thread")]
        std::mem::take(&mut self.sync_tasks)
            .into_iter()
            .for_each(|task| {
                if let Some(task) = task.post_no_defer() {
                    task.run();
                }
            });
    }
}

/// Defer all GC (Garbage Collection) in the current thread.
///
/// When the current thread issues a request for a GC,
/// the [GcTask][crate::GcTask] or it's `sync` counterpart
/// will be added to a list.
/// All such collected items will be submitted for GC once the [DeferGc] goes out of scope.
///
/// Multiple [DeferGc] on the same thread will cooperate by sharing their list.
#[non_exhaustive]
pub struct DeferGc {
    /// [DeferGc] implicitly holds on to a thread-local reference.
    /// So it's important it won't be shared across threads.
    no_send_sync: PhantomData<&'static RefCell<TaskList>>,
}

impl DeferGc {
    /// Create a new [DeferGc].
    ///
    /// While it exists, no garbage collections will run.
    /// Any existing and future [DeferGc] in the same thread will cooperate.
    #[inline]
    pub fn new() -> Self {
        DEFERRED_TASK_LIST
            .with_borrow_mut(|task_list| task_list.active = task_list.active.strict_add(1));
        Self {
            no_send_sync: PhantomData,
        }
    }

    /// Run code with garbage collection deferred.
    ///
    /// This is very helpful when dropping a large amount of pointers.
    ///
    /// # Example
    ///
    /// In the example, we drop a large group of pointer inside [DeferGc::run],
    /// thus minimizing the number of garbage collects that'll happen.
    ///
    /// If all pointers were on the same generation,
    /// the code would do only a single garbage collection,
    /// instead of one per pointer.
    ///
    /// ```
    /// # use cycle_ptr::prelude::*;
    /// # use cycle_ptr::{GcPtr, GenerationRef};
    /// #
    /// # struct X;
    /// #
    /// # fn make_big_vec_of_pointers() -> Vec<GcPtr<X>> {
    /// #   let generation = GenerationRef::default();
    /// #   (0..100).map(|_| generation.make(|_| X)).collect()
    /// # }
    /// #
    /// use cycle_ptr::DeferGc;
    ///
    /// let mut ptrs: Vec<GcPtr<_>> = make_big_vec_of_pointers();
    /// DeferGc::run(|| {
    ///   ptrs.clear();
    /// });
    /// ```
    #[inline]
    pub fn run<F, R>(f: F) -> R
    where
        F: FnOnce() -> R,
    {
        let _ = Self::new();
        f()
    }
}

impl Drop for DeferGc {
    /// Drop the [DeferGc] handle.
    ///
    /// If this is the last handle in the current thread,
    /// all pending GC tasks will be submitted for execution.
    #[inline]
    fn drop(&mut self) {
        let inner = DEFERRED_TASK_LIST.with_borrow_mut(|task_list| {
            task_list.active = task_list.active.strict_sub(1);
            if task_list.active == 0 {
                Some(std::mem::take(&mut task_list.inner))
            } else {
                None
            }
        });
        if let Some(mut inner) = inner {
            inner.execute_all_tasks();
        }
    }
}

impl Default for DeferGc {
    #[inline]
    fn default() -> Self {
        DeferGc::new()
    }
}

impl Clone for DeferGc {
    #[inline]
    fn clone(&self) -> Self {
        Self::new()
    }
}

impl fmt::Debug for DeferGc {
    #[inline(never)]
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        DEFERRED_TASK_LIST.with_borrow(|task_list| {
            fmt.debug_struct("DeferGc")
                .field("task_list", task_list)
                .finish()
        })
    }
}

thread_local! {
    static DEFERRED_TASK_LIST: RefCell<TaskList> = const { RefCell::new(TaskList::new()) }
}

/// Try to submit the task to the [TaskList] for deferral.
///
/// # Errors
///
/// Returns the task if there's no active [TaskList].
pub(super) fn maybe_defer_task(t: task::GcTask) -> Result<(), task::GcTask> {
    DEFERRED_TASK_LIST.with_borrow_mut(|task_list| {
        if task_list.active != 0 {
            task_list.inner.tasks.push(t);
            Ok(())
        } else {
            Err(t)
        }
    })
}

/// Try to submit the task to the [TaskList] for deferral.
///
/// # Errors
///
/// Returns the task if there's no active [TaskList].
#[cfg(feature = "multi_thread")]
pub(super) fn maybe_defer_sync_task(t: sync_task::GcTask) -> Result<(), sync_task::GcTask> {
    DEFERRED_TASK_LIST.with_borrow_mut(|task_list| {
        if task_list.active != 0 {
            task_list.inner.sync_tasks.push(t);
            Ok(())
        } else {
            Err(t)
        }
    })
}