use crate::{
barrier::{Barrier, NONE},
consumer::Consumer,
cursor::Cursor,
producer::ProducerBarrier,
producer::{Producer, RingBufferFull},
ringbuffer::RingBuffer,
wait_strategies::{WaitStrategy, WakeupNotifier},
Sequence,
};
use crossbeam_utils::CachePadded;
use std::{
hint, process,
sync::{
atomic::{fence, AtomicI64, AtomicU64, Ordering},
Arc, Mutex,
},
};
use super::{MissingFreeSlots, MutBatchIter};
type AvailableWord = CachePadded<AtomicU64>;
#[inline]
fn new_available_word(v: u64) -> AvailableWord {
CachePadded::new(AtomicU64::new(v))
}
struct SharedProducer {
consumers: Mutex<Vec<Consumer>>,
ref_count: AtomicI64,
}
pub struct MultiProducer<E, C, W>
where
C: Barrier,
W: WaitStrategy,
{
shutdown_at_sequence: Arc<CachePadded<AtomicI64>>,
ring_buffer: Arc<RingBuffer<E>>,
shared_producer: Arc<SharedProducer>,
producer_barrier: Arc<MultiProducerBarrier>,
consumer_barrier: Arc<C>,
notifier: W::Notifier,
claimed_sequence: Sequence,
sequence_clear_of_consumers: Sequence,
}
impl<E, C, W> Producer<E> for MultiProducer<E, C, W>
where
C: Barrier,
W: WaitStrategy,
{
#[inline]
fn try_publish<F>(&mut self, update: F) -> Result<Sequence, RingBufferFull>
where
F: FnOnce(&mut E),
{
self.next_sequences(1).map_err(|_| RingBufferFull)?;
let sequence = self.apply_update(update);
Ok(sequence)
}
#[inline]
fn publish<F>(&mut self, update: F)
where
F: FnOnce(&mut E),
{
while self.next_sequences(1).is_err() {
hint::spin_loop();
}
self.apply_update(update);
}
#[inline]
fn try_batch_publish<F>(&mut self, n: usize, update: F) -> Result<Sequence, MissingFreeSlots>
where
F: for<'a> FnOnce(MutBatchIter<'a, E>),
{
self.next_sequences(n)?;
let sequence = self.apply_updates(n, update);
Ok(sequence)
}
#[inline]
fn batch_publish<F>(&mut self, n: usize, update: F)
where
F: for<'a> FnOnce(MutBatchIter<'a, E>),
{
debug_assert!(
n <= self.ring_buffer.capacity() as usize,
"batch_publish(n) requires n <= ring buffer capacity"
);
while self.next_sequences(n).is_err() {
hint::spin_loop();
}
self.apply_updates(n, update);
}
}
impl<E, C, W> Clone for MultiProducer<E, C, W>
where
C: Barrier,
W: WaitStrategy,
{
fn clone(&self) -> Self {
let count = self
.shared_producer
.ref_count
.fetch_add(1, Ordering::AcqRel);
if count > i64::MAX / 2 {
process::abort();
}
let shutdown_at_sequence = Arc::clone(&self.shutdown_at_sequence);
let producer_barrier = Arc::clone(&self.producer_barrier);
let shared_producer = Arc::clone(&self.shared_producer);
let consumer_barrier = Arc::clone(&self.consumer_barrier);
let ring_buffer = Arc::clone(&self.ring_buffer);
let notifier = self.notifier.clone();
MultiProducer {
shutdown_at_sequence,
ring_buffer,
shared_producer,
producer_barrier,
consumer_barrier,
notifier,
claimed_sequence: NONE,
sequence_clear_of_consumers: 0,
}
}
}
impl<E, C, W> Drop for MultiProducer<E, C, W>
where
C: Barrier,
W: WaitStrategy,
{
fn drop(&mut self) {
let old_count = self
.shared_producer
.ref_count
.fetch_sub(1, Ordering::AcqRel);
if old_count == 1 {
let sequence = self.producer_barrier.current() + 1;
self.shutdown_at_sequence.store(sequence, Ordering::Relaxed);
self.notifier.wake();
let mut consumers = self.shared_producer.consumers.lock().unwrap();
consumers.iter_mut().for_each(|c| c.join());
}
}
}
impl<E, C, W> MultiProducer<E, C, W>
where
C: Barrier,
W: WaitStrategy,
{
pub(crate) fn new(
shutdown_at_sequence: Arc<CachePadded<AtomicI64>>,
ring_buffer: Arc<RingBuffer<E>>,
producer_barrier: Arc<MultiProducerBarrier>,
consumers: Vec<Consumer>,
consumer_barrier: C,
notifier: W::Notifier,
) -> Self {
let shared_producer = Arc::new(SharedProducer {
consumers: Mutex::new(consumers),
ref_count: AtomicI64::new(1),
});
let consumer_barrier = Arc::new(consumer_barrier);
let sequence_clear_of_consumers = ring_buffer.capacity() - 1;
MultiProducer {
shutdown_at_sequence,
ring_buffer,
shared_producer,
producer_barrier,
consumer_barrier,
notifier,
claimed_sequence: NONE,
sequence_clear_of_consumers,
}
}
#[inline]
fn next_sequences(&mut self, n: usize) -> Result<Sequence, MissingFreeSlots> {
let n = n as i64;
let mut current = self.producer_barrier.current();
let mut n_next = current + n;
loop {
if self.sequence_clear_of_consumers < n_next {
let rear_sequence_read = self.consumer_barrier.get_after(current);
let available_capacity = self
.ring_buffer
.available_capacity(current, rear_sequence_read);
if available_capacity < n {
return Err(MissingFreeSlots((n - available_capacity) as u64));
}
fence(Ordering::Acquire);
self.sequence_clear_of_consumers = current + available_capacity;
}
match self.producer_barrier.compare_exchange(current, n_next) {
Ok(_) => {
self.claimed_sequence = n_next;
break;
}
Err(new_current) => {
current = new_current;
n_next = current + n;
}
}
}
Ok(n_next)
}
#[inline]
fn apply_update<F>(&mut self, update: F) -> Sequence
where
F: FnOnce(&mut E),
{
let sequence = self.claimed_sequence;
let event_ptr = unsafe { self.ring_buffer.get(sequence).as_ptr() };
let event = unsafe { &mut *event_ptr };
update(event);
self.producer_barrier.publish(sequence);
self.notifier.wake();
sequence
}
#[inline]
fn apply_updates<F>(&mut self, n: usize, updates: F) -> Sequence
where
F: for<'a> FnOnce(MutBatchIter<'a, E>),
{
let n = n as i64;
let upper = self.claimed_sequence;
let lower = upper - n + 1;
let iter = MutBatchIter::new(lower, upper, &self.ring_buffer);
updates(iter);
fence(Ordering::Release);
self.producer_barrier.publish_range_relaxed(lower, n);
self.notifier.wake();
self.claimed_sequence
}
}
pub struct MultiProducerBarrier {
cursor: Cursor,
available: Box<[AvailableWord]>,
index_mask: usize,
index_shift: usize,
}
impl MultiProducerBarrier {
pub(crate) fn new(size: usize) -> Self {
assert!(
size >= 64,
"Multi Producer Disruptor must have a size of minimum 64 slots."
);
assert!(size.is_power_of_two(), "Size must be power of 2.");
let cursor = Cursor::new(NONE);
let i64_needed = size / 64;
let all_ones = !0_u64;
let available = (0..i64_needed)
.map(|_i| new_available_word(all_ones))
.collect();
let index_mask = size - 1;
let index_shift = Self::log2(size);
Self {
cursor,
available,
index_mask,
index_shift,
}
}
fn log2(i: usize) -> usize {
std::mem::size_of::<usize>() * 8 - (i.leading_zeros() as usize) - 1
}
#[inline]
fn current(&self) -> Sequence {
self.cursor.relaxed_value()
}
#[inline]
fn compare_exchange(&self, current: Sequence, next: Sequence) -> Result<i64, i64> {
self.cursor.compare_exchange(current, next)
}
#[inline]
fn calculate_availability_indices(&self, sequence: Sequence) -> (usize, usize) {
let slot_index = self.slot_index(sequence);
let availability_index = slot_index >> 6; let bit_index = slot_index & 63;
(availability_index, bit_index)
}
#[inline]
fn slot_index(&self, sequence: Sequence) -> usize {
sequence as usize & self.index_mask
}
#[inline]
fn calculate_availability_flag(&self, sequence: Sequence) -> u64 {
let round = (sequence >> self.index_shift) as u64;
round & 1
}
#[inline]
fn availability_at(&self, index: usize) -> &AvailableWord {
unsafe { self.available.get_unchecked(index) }
}
#[inline]
fn publish_range_relaxed(&self, from: Sequence, n: i64) {
if n <= 0 {
return;
}
let mut sequence = from;
let mut remaining = n as usize;
while remaining != 0 {
let (availability_index, bit_index) = self.calculate_availability_indices(sequence);
let take = remaining.min(64 - bit_index);
let mask = if take == 64 {
!0_u64
} else {
((1_u64 << take) - 1) << bit_index
};
self.availability_at(availability_index)
.fetch_xor(mask, Ordering::Relaxed);
sequence += take as i64;
remaining -= take;
}
}
#[inline]
fn publish_with_ordering(&self, sequence: Sequence, ordering: Ordering) {
let (availability_index, bit_index) = self.calculate_availability_indices(sequence);
let availability = self.availability_at(availability_index);
let mask = 1 << bit_index;
availability.fetch_xor(mask, ordering);
}
}
impl Barrier for MultiProducerBarrier {
#[inline]
fn get_after(&self, prev: Sequence) -> Sequence {
let mut availability_flag = self.calculate_availability_flag(prev);
let (mut availability_index, mut bit_index) = self.calculate_availability_indices(prev);
let mut sequence = prev;
loop {
let word = self
.availability_at(availability_index)
.load(Ordering::Relaxed);
let bits_remaining = 64 - bit_index;
let relevant_mask = if bits_remaining == 64 {
!0_u64
} else {
(1_u64 << bits_remaining) - 1
};
let shifted = (word >> bit_index) & relevant_mask;
let mismatch = if availability_flag == 0 {
shifted
} else {
(!shifted) & relevant_mask
};
if mismatch != 0 {
let matches = mismatch.trailing_zeros() as i64;
return sequence + matches - 1;
}
sequence += bits_remaining as i64;
availability_index += 1;
bit_index = 0;
if availability_index == self.available.len() {
availability_index = 0;
availability_flag ^= 1;
}
}
}
}
impl ProducerBarrier for MultiProducerBarrier {
#[inline]
fn publish(&self, sequence: Sequence) {
self.publish_with_ordering(sequence, Ordering::Release);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn log2() {
assert_eq!(1, MultiProducerBarrier::log2(2));
assert_eq!(1, MultiProducerBarrier::log2(3));
assert_eq!(3, MultiProducerBarrier::log2(8));
assert_eq!(3, MultiProducerBarrier::log2(9));
assert_eq!(3, MultiProducerBarrier::log2(10));
assert_eq!(3, MultiProducerBarrier::log2(11));
}
#[test]
fn initial_available_sequence_number() {
let barrier = MultiProducerBarrier::new(64);
assert_eq!(barrier.get_after(0), -1);
}
#[test]
fn publication_of_single_event_for_small_barrier() {
let barrier = MultiProducerBarrier::new(64);
barrier.publish_range_relaxed(0, 1);
assert_eq!(barrier.get_after(0), 0);
}
#[test]
fn publication_of_range_for_small_barrier() {
let barrier = MultiProducerBarrier::new(64);
barrier.publish_range_relaxed(0, 10);
assert_eq!(barrier.get_after(0), 9);
}
#[test]
fn publication_of_range_wrapping_ringbuffer_for_small_barrier() {
let barrier = MultiProducerBarrier::new(64);
barrier.publish_range_relaxed(0, 50);
assert_eq!(barrier.get_after(0), 49);
barrier.publish_range_relaxed(50, 50);
assert_eq!(barrier.get_after(49), 99);
}
#[test]
fn publication_of_range_wrapping_ringbuffer_for_barrier() {
let barrier = MultiProducerBarrier::new(128);
barrier.publish_range_relaxed(0, 100);
assert_eq!(barrier.get_after(0), 99);
barrier.publish_range_relaxed(100, 100);
assert_eq!(barrier.get_after(99), 199);
barrier.publish_range_relaxed(200, 100);
assert_eq!(barrier.get_after(199), 299);
}
}