use std::fmt; use std::ptr::NonNull; use std::sync::atomic::Ordering::{AcqRel, Acquire, Relaxed, Release};
/// List queue transmit handle. pub(crate) struct Tx<T> { /// Tail in the `Block` mpmc list.
block_tail: AtomicPtr<Block<T>>,
/// Position to push the next message. This references a block and offset /// into the block.
tail_position: AtomicUsize,
}
/// List queue receive handle pub(crate) struct Rx<T> { /// Pointer to the block being processed.
head: NonNull<Block<T>>,
/// Next slot index to process.
index: usize,
/// Pointer to the next block pending release.
free_head: NonNull<Block<T>>,
}
/// Return value of `Rx::try_pop`. pub(crate) enum TryPopResult<T> { /// Successfully popped a value.
Ok(T), /// The channel is empty. /// /// Note that `list.rs` only tracks the close state set by senders. If the /// channel is closed by `Rx::close()`, then `TryPopResult::Empty` is still /// returned, and the close state needs to be handled by `chan.rs`.
Empty, /// The channel is empty and closed. /// /// Returned when the send half is closed (all senders dropped).
Closed, /// The channel is not empty, but the first value is being written.
Busy,
}
pub(crate) fn channel<T>() -> (Tx<T>, Rx<T>) { // Create the initial block shared between the tx and rx halves. let initial_block = Block::new(0); let initial_block_ptr = Box::into_raw(initial_block);
let tx = Tx {
block_tail: AtomicPtr::new(initial_block_ptr),
tail_position: AtomicUsize::new(0),
};
let head = NonNull::new(initial_block_ptr).unwrap();
let rx = Rx {
head,
index: 0,
free_head: head,
};
(tx, rx)
}
impl<T> Tx<T> { /// Pushes a value into the list. pub(crate) fn push(&self, value: T) { // First, claim a slot for the value. `Acquire` is used here to // synchronize with the `fetch_add` in `reclaim_blocks`. let slot_index = self.tail_position.fetch_add(1, Acquire);
// Load the current block and write the value let block = self.find_block(slot_index);
unsafe { // Write the value to the block
block.as_ref().write(slot_index, value);
}
}
/// Closes the send half of the list. /// /// Similar process as pushing a value, but instead of writing the value & /// setting the ready flag, the `TX_CLOSED` flag is set on the block. pub(crate) fn close(&self) { // First, claim a slot for the value. This is the last slot that will be // claimed. let slot_index = self.tail_position.fetch_add(1, Acquire);
let block = self.find_block(slot_index);
unsafe { block.as_ref().tx_close() }
}
fn find_block(&self, slot_index: usize) -> NonNull<Block<T>> { // The start index of the block that contains `index`. let start_index = block::start_index(slot_index);
// The index offset into the block let offset = block::offset(slot_index);
// Load the current head of the block letmut block_ptr = self.block_tail.load(Acquire);
let block = unsafe { &*block_ptr };
// Calculate the distance between the tail ptr and the target block let distance = block.distance(start_index);
// Decide if this call to `find_block` should attempt to update the // `block_tail` pointer. // // Updating `block_tail` is not always performed in order to reduce // contention. // // When set, as the routine walks the linked list, it attempts to update // `block_tail`. If the update cannot be performed, `try_updating_tail` // is unset. letmut try_updating_tail = distance > offset;
// Walk the linked list of blocks until the block with `start_index` is // found. loop { let block = unsafe { &(*block_ptr) };
if block.is_at_index(start_index) { returnunsafe { NonNull::new_unchecked(block_ptr) };
}
let next_block = block
.load_next(Acquire) // There is no allocated next block, grow the linked list.
.unwrap_or_else(|| block.grow());
// If the block is **not** final, then the tail pointer cannot be // advanced any more.
try_updating_tail &= block.is_final();
if try_updating_tail { // Advancing `block_tail` must happen when walking the linked // list. `block_tail` may not advance passed any blocks that are // not "final". At the point a block is finalized, it is unknown // if there are any prior blocks that are unfinalized, which // makes it impossible to advance `block_tail`. // // While walking the linked list, `block_tail` can be advanced // as long as finalized blocks are traversed. // // Release ordering is used to ensure that any subsequent reads // are able to see the memory pointed to by `block_tail`. // // Acquire is not needed as any "actual" value is not accessed. // At this point, the linked list is walked to acquire blocks. ifself
.block_tail
.compare_exchange(block_ptr, next_block.as_ptr(), Release, Relaxed)
.is_ok()
{ // Synchronize with any senders let tail_position = self.tail_position.fetch_add(0, Release);
unsafe {
block.tx_release(tail_position);
}
} else { // A concurrent sender is also working on advancing // `block_tail` and this thread is falling behind. // // Stop trying to advance the tail pointer
try_updating_tail = false;
}
}
block_ptr = next_block.as_ptr();
thread::yield_now();
}
}
/// # Safety /// /// Behavior is undefined if any of the following conditions are violated: /// /// - The `block` was created by [`Box::into_raw`]. /// - The `block` is not currently part of any linked list. /// - The `block` is a valid pointer to a [`Block<T>`]. pub(crate) unsafefn reclaim_block(&self, mut block: NonNull<Block<T>>) { // The block has been removed from the linked list and ownership // is reclaimed. // // Before dropping the block, see if it can be reused by // inserting it back at the end of the linked list. // // First, reset the data // // Safety: caller guarantees the block is valid and not in any list. unsafe {
block.as_mut().reclaim();
}
letmut reused = false;
// Attempt to insert the block at the end // // Walk at most three times let curr_ptr = self.block_tail.load(Acquire);
// The pointer can never be null
debug_assert!(!curr_ptr.is_null());
// Safety: curr_ptr is never null. letmut curr = unsafe { NonNull::new_unchecked(curr_ptr) };
if !reused { // Safety: // // 1. Caller guarantees the block is valid and not in any list. // 2. The block was created by `Box::into_raw`. let _ = unsafe { Box::from_raw(block.as_ptr()) };
}
}
}
// It is possible that a block has no value "now" but the list is still not empty. // To be sure, it is necessary to check the length of the list. self.len(tx) == 0
}
// Guaranteed to return true if `slot_index` is the fake message sent on channel close. // Guaranteed to return false if `slot_index` is a fully sent message. // // For messages that are partially sent, may return either true or false. fn is_maybe_closed(&self, tx: &Tx<T>, slot_index: usize) -> bool { let start_index = block::start_index(slot_index);
let tail = tx.block_tail.load(Acquire); // SAFETY: Only the receiver frees blocks, so since we are the receiver, this will not be // freed right now. let tail_ref = unsafe { &*tail }; if tail_ref.is_at_index(start_index) { return !tail_ref.has_value(slot_index);
}
// This method is optimized for checking whether the last value is present, so most of the // time it is in `block_tail`. However, this isn't always the case since it's possible // that the list was grown with an empty block, in which case `block_tail` points one block // too far. To handle this case, we walk the list from the head. letmut block_ptr = Some(self.head);
whilelet Some(block) = block_ptr { // SAFETY: Only the receiver frees blocks, so since we are the receiver, this will not // be freed right now. let block_ref = unsafe { block.as_ref() }; if block_ref.is_at_index(start_index) { return !block_ref.has_value(slot_index);
}
block_ptr = block_ref.load_next(Acquire);
} true
}
pub(crate) fn len(&self, tx: &Tx<T>) -> usize { let tail_position = tx.tail_position.load(Acquire); letmut len = tail_position.wrapping_sub(self.index);
debug_assert!(0 <= len as isize); if len == 0 { return0;
} // There are messages present in the queue. However, it's possible that the last message is // a fake "closed" message that we do not wish to count. To avoid counting it, we do not // count the last message if the ready bit is unset. // // Note that it is also possible for the ready bit to be unset on a normal message, but // this happens only if that message is currently being sent *right now* in parallel on // another thread. That is okay because it is optional to count messages that are currently // being sent. ifself.is_maybe_closed(tx, tail_position.wrapping_sub(1)) {
len -= 1;
}
len
}
/// Pops the next value off the queue. pub(crate) fn pop(&mutself, tx: &Tx<T>) -> Option<block::Read<T>> { // Advance `head`, if needed if !self.try_advancing_head() { return None;
}
self.reclaim_blocks(tx);
unsafe { let block = self.head.as_ref();
let ret = block.read(self.index);
iflet Some(block::Read::Value(..)) = ret { self.index = self.index.wrapping_add(1);
}
ret
}
}
/// Pops the next value off the queue, detecting whether the block /// is busy or empty on failure. /// /// This function exists because `Rx::pop` can return `None` even if the /// channel's queue contains a message that has been completely written. /// This can happen if the fully delivered message is behind another message /// that is in the middle of being written to the block, since the channel /// can't return the messages out of order. pub(crate) fn try_pop(&mutself, tx: &Tx<T>) -> TryPopResult<T> { let tail_position = tx.tail_position.load(Acquire); let result = self.pop(tx);
match result {
Some(block::Read::Value(t)) => TryPopResult::Ok(t),
Some(block::Read::Closed) => TryPopResult::Closed,
None if tail_position == self.index => TryPopResult::Empty,
None => TryPopResult::Busy,
}
}
/// Tries advancing the block pointer to the block referenced by `self.index`. /// /// Returns `true` if successful, `false` if there is no next block to load. fn try_advancing_head(&mutself) -> bool { let block_index = block::start_index(self.index);
loop { let next_block = { let block = unsafe { self.head.as_ref() };
if block.is_at_index(block_index) { returntrue;
}
block.load_next(Acquire)
};
let next_block = match next_block {
Some(next_block) => next_block,
None => { returnfalse;
}
};
self.head = next_block;
thread::yield_now();
}
}
fn reclaim_blocks(&mutself, tx: &Tx<T>) { whileself.free_head != self.head { unsafe { // Get a handle to the block that will be freed and update // `free_head` to point to the next block. let block = self.free_head;
let observed_tail_position = block.as_ref().observed_tail_position();
let required_index = match observed_tail_position {
Some(i) => i,
None => return,
};
if required_index > self.index { return;
}
// We may read the next pointer with `Relaxed` ordering as it is // guaranteed that the `reclaim_blocks` routine trails the `recv` // routine. Any memory accessed by `reclaim_blocks` has already // been acquired by `recv`. let next_block = block.as_ref().load_next(Relaxed);
// Update the free list head self.free_head = next_block.unwrap();
// Push the emptied block onto the back of the queue, making it // available to senders.
tx.reclaim_block(block);
}
thread::yield_now();
}
}
/// Effectively `Drop` all the blocks. Should only be called once, when /// the list is dropping. pub(super) unsafefn free_blocks(&mutself) {
debug_assert_ne!(self.free_head, NonNull::dangling());
letmut cur = Some(self.free_head);
#[cfg(debug_assertions)]
{ // to trigger the debug assert above so as to catch that we // don't call `free_blocks` more than once. self.free_head = NonNull::dangling(); self.head = NonNull::dangling();
}
whilelet Some(block) = cur {
cur = unsafe { block.as_ref() }.load_next(Relaxed);
drop(unsafe { Box::from_raw(block.as_ptr()) });
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.