-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add
Deferred
type for deferred retirement batches
- Loading branch information
1 parent
98c34e3
commit 99d597b
Showing
6 changed files
with
388 additions
and
121 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
use std::cell::UnsafeCell; | ||
use std::ptr; | ||
use std::sync::atomic::{AtomicPtr, AtomicU64, Ordering}; | ||
|
||
use crate::raw::Node; | ||
use crate::tls::Thread; | ||
use crate::{AsLink, Collector, Link}; | ||
|
||
/// A batch of pointers to be reclaimed in the future. | ||
/// | ||
/// Sometimes it is necessary to defer the retirement of a batch of pointers. | ||
/// For example, a set of pointers may be reachable from multiple locations in a data structure | ||
/// and can only be retired after a specific object is reclaimed. In such cases, the [`Deferred`] type | ||
/// can serve as a cheap place to defer the retirement of pointers, without allocating extra memory. | ||
/// | ||
/// [`Deferred`] is a concurrent list, meaning that pointers can be added from multiple threads | ||
/// concurrently. It is not meant to be used to amortize the cost of retirement, which is done | ||
/// through thread-local batches controlled with [`Collector::batch_size`], as access from a single-thread | ||
/// can be more expensive than is required. Deferred batches are useful when you need to control when | ||
/// a batch of nodes is retired directly, a relatively rare use case. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ```rust | ||
/// # use seize::{Deferred, Collector}; | ||
/// let collector = Collector::new().batch_size(10); | ||
/// | ||
/// // allocate a set of pointers | ||
/// let items = (0..10) | ||
/// .map(|i| AtomicPtr::new(collector.link_boxed(i))) | ||
/// .collect::<Arc<[_]>>(); | ||
/// | ||
/// // create a batch of nodes to retire | ||
/// let mut batch = Deferred::new(); | ||
/// | ||
/// for item in items.iter() { | ||
/// let new = collector.link_boxed(0); | ||
/// // make the item unreachable with an atomic swap | ||
/// let old = item.swap(new, Ordering::AcqRel); | ||
/// // don't retire just yet, add the node to the batch | ||
/// unsafe { batch.defer(old) }; | ||
/// } | ||
/// | ||
/// // sometime later... retire all the items in the batch | ||
/// unsafe { batch.retire_all(&collector, reclaim::boxed::<Linked<usize>>) } | ||
/// ``` | ||
Check failure on line 46 in src/deferred.rs
|
||
pub struct Deferred { | ||
head: AtomicPtr<Node>, | ||
pub(crate) min_epoch: AtomicU64, | ||
} | ||
|
||
impl Deferred { | ||
/// Create a new batch of deferred nodes. | ||
pub const fn new() -> Deferred { | ||
Deferred { | ||
head: AtomicPtr::new(ptr::null_mut()), | ||
min_epoch: AtomicU64::new(0), | ||
} | ||
} | ||
|
||
/// Add an object to the batch. | ||
/// | ||
/// # Safety | ||
/// | ||
/// After this method is called, it is *undefined behavior* to add this pointer to the | ||
/// batch again, or any other batch. The pointer must also be valid for access as a [`Link`], | ||
/// per the [`AsLink`] trait. | ||
pub unsafe fn defer<T: AsLink>(&self, ptr: *mut T) { | ||
// `ptr` is guaranteed to be a valid pointer that can be cast to a node (`T: AsLink`) | ||
// | ||
// any other thread with a reference to the pointer only has a shared | ||
// reference to the UnsafeCell<Node>, which is allowed to alias. the caller | ||
// guarantees that the same pointer is not deferred twice, so we can safely read/write | ||
// to the node through this pointer. | ||
let node = UnsafeCell::raw_get(ptr.cast::<UnsafeCell<Node>>()); | ||
|
||
let birth_epoch = unsafe { (*node).birth_epoch }; | ||
|
||
// keep track of the oldest node in the batch | ||
self.min_epoch.fetch_min(birth_epoch, Ordering::Relaxed); | ||
|
||
// relaxed: we never access head | ||
let mut prev = self.head.load(Ordering::Relaxed); | ||
|
||
loop { | ||
unsafe { (*node).next_batch = prev } | ||
|
||
// relaxed: head is only ever accessed through a mutable reference | ||
match self | ||
.head | ||
.compare_exchange_weak(prev, node, Ordering::Relaxed, Ordering::Relaxed) | ||
{ | ||
Ok(_) => return, | ||
Err(found) => prev = found, | ||
} | ||
} | ||
} | ||
|
||
/// Retires a batch of values, running `reclaim` when no threads hold a reference to any | ||
/// nodes in the batch. | ||
/// | ||
/// Note that this method is disconnected from any guards on the current thread, | ||
/// so the pointers may be reclaimed immediately. | ||
/// | ||
/// # Safety | ||
/// | ||
/// The safety requirements of [`Collector::retire`] apply to each node in the batch. | ||
/// | ||
/// [`Collector::retire`]: crate::Collector::retire | ||
pub unsafe fn retire_all(&mut self, collector: &Collector, reclaim: unsafe fn(*mut Link)) { | ||
// note that `add_batch` doesn't ever actually reclaim the pointer immediately if | ||
// the current thread is active, similar to `retire`. | ||
unsafe { collector.raw.add_batch(self, reclaim, Thread::current()) } | ||
} | ||
|
||
/// Run a function for each node in the batch. | ||
/// | ||
/// This function does not consume the batch and can be called multiple | ||
/// times, **before retirement**. | ||
pub fn for_each(&mut self, mut f: impl FnMut(*mut Node)) { | ||
let mut list = *self.head.get_mut(); | ||
|
||
while !list.is_null() { | ||
let curr = list; | ||
|
||
// safety: `curr` is a valid non-null node in the list | ||
list = unsafe { (*curr).next_batch }; | ||
|
||
f(curr); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.