use std::cell::UnsafeCell;
use std::mem::MaybeUninit;
use std::sync::Arc;
use std::sync::atomic::{AtomicI64, Ordering};
const UNPUBLISHED: i64 = -1;
struct DisruptorInner<T> {
buf: Box<[UnsafeCell<MaybeUninit<T>>]>,
published: Box<[AtomicI64]>,
producer_cursor: AtomicI64,
consumer_cursors: Box<[AtomicI64]>,
capacity: i64,
mask: i64,
}
unsafe impl<T: Send> Sync for DisruptorInner<T> {}
unsafe impl<T: Send> Send for DisruptorInner<T> {}
impl<T> Drop for DisruptorInner<T> {
fn drop(&mut self) {
for slot in self.published.iter() {
let seq = slot.load(Ordering::Relaxed);
if seq != UNPUBLISHED {
let idx = (seq & self.mask) as usize;
let min_consumer = self
.consumer_cursors
.iter()
.map(|c| c.load(Ordering::Relaxed))
.min()
.unwrap_or(-1);
if seq > min_consumer {
unsafe {
(*self.buf[idx].get()).assume_init_drop();
}
}
}
}
}
}
pub struct MpmcDisruptor;
impl MpmcDisruptor {
pub fn with_consumers<T: Send + 'static>(
requested_capacity: usize,
consumer_count: usize,
) -> (DisruptorProducer<T>, Vec<DisruptorConsumer<T>>) {
assert!(consumer_count >= 1, "need at least one consumer");
let cap = requested_capacity.max(2).next_power_of_two();
let mut buf = Vec::with_capacity(cap);
let mut published = Vec::with_capacity(cap);
for _ in 0..cap {
buf.push(UnsafeCell::new(MaybeUninit::<T>::uninit()));
published.push(AtomicI64::new(UNPUBLISHED));
}
let mut cursors = Vec::with_capacity(consumer_count);
for _ in 0..consumer_count {
cursors.push(AtomicI64::new(-1));
}
let inner = Arc::new(DisruptorInner {
buf: buf.into_boxed_slice(),
published: published.into_boxed_slice(),
producer_cursor: AtomicI64::new(-1),
consumer_cursors: cursors.into_boxed_slice(),
capacity: cap as i64,
mask: (cap - 1) as i64,
});
let producer = DisruptorProducer {
inner: inner.clone(),
};
let consumers = (0..consumer_count)
.map(|i| DisruptorConsumer {
inner: inner.clone(),
idx: i,
next: 0,
})
.collect();
(producer, consumers)
}
}
pub struct DisruptorProducer<T> {
inner: Arc<DisruptorInner<T>>,
}
impl<T> Clone for DisruptorProducer<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
unsafe impl<T: Send> Send for DisruptorProducer<T> {}
unsafe impl<T: Send> Sync for DisruptorProducer<T> {}
impl<T> DisruptorProducer<T> {
pub fn try_publish(&self, value: T) -> Result<(), T> {
loop {
let cur = self.inner.producer_cursor.load(Ordering::Relaxed);
let next = cur + 1;
let mut min_consumer = i64::MAX;
for c in self.inner.consumer_cursors.iter() {
let v = c.load(Ordering::Acquire);
if v < min_consumer {
min_consumer = v;
}
}
if next - min_consumer > self.inner.capacity {
return Err(value);
}
match self.inner.producer_cursor.compare_exchange_weak(
cur,
next,
Ordering::AcqRel,
Ordering::Relaxed,
) {
Ok(_) => {
let idx = (next & self.inner.mask) as usize;
let prev_round = next - self.inner.capacity;
if prev_round >= 0 {
while self
.inner
.consumer_cursors
.iter()
.any(|c| c.load(Ordering::Acquire) < prev_round)
{
std::hint::spin_loop();
}
}
unsafe {
(*self.inner.buf[idx].get()).write(value);
}
self.inner.published[idx].store(next, Ordering::Release);
return Ok(());
}
Err(_) => {
std::hint::spin_loop();
}
}
}
}
pub fn capacity(&self) -> usize {
self.inner.capacity as usize
}
}
pub struct DisruptorConsumer<T> {
inner: Arc<DisruptorInner<T>>,
idx: usize,
next: i64,
}
unsafe impl<T: Send> Send for DisruptorConsumer<T> {}
impl<T: Clone> DisruptorConsumer<T> {
pub fn try_consume(&mut self) -> Option<T> {
let seq = self.next;
let idx = (seq & self.inner.mask) as usize;
if self.inner.published[idx].load(Ordering::Acquire) != seq {
return None;
}
let v = unsafe { (*self.inner.buf[idx].get()).assume_init_ref() }.clone();
self.next = seq + 1;
self.inner.consumer_cursors[self.idx].store(seq, Ordering::Release);
Some(v)
}
pub fn capacity(&self) -> usize {
self.inner.capacity as usize
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
use super::*;
#[test]
fn single_producer_single_consumer_round_trip() {
let (p, mut consumers) = MpmcDisruptor::with_consumers::<u32>(8, 1);
let c = &mut consumers[0];
p.try_publish(1).unwrap();
p.try_publish(2).unwrap();
assert_eq!(c.try_consume(), Some(1));
assert_eq!(c.try_consume(), Some(2));
assert_eq!(c.try_consume(), None);
}
#[test]
fn two_consumers_both_see_every_item() {
let (p, mut consumers) = MpmcDisruptor::with_consumers::<u32>(8, 2);
let (c1, rest) = consumers.split_at_mut(1);
let c1 = &mut c1[0];
let c2 = &mut rest[0];
for i in 0..5u32 {
p.try_publish(i).unwrap();
}
for i in 0..5u32 {
assert_eq!(c1.try_consume(), Some(i));
assert_eq!(c2.try_consume(), Some(i));
}
assert_eq!(c1.try_consume(), None);
assert_eq!(c2.try_consume(), None);
}
#[test]
fn producer_blocks_when_slowest_consumer_lags() {
let (p, mut consumers) = MpmcDisruptor::with_consumers::<u32>(2, 1);
let c = &mut consumers[0];
for i in 0..2u32 {
p.try_publish(i).unwrap();
}
assert!(p.try_publish(99).is_err());
assert_eq!(c.try_consume(), Some(0));
p.try_publish(99).unwrap();
}
#[test]
fn two_producers_one_consumer_under_threads() {
let (p, mut consumers) = MpmcDisruptor::with_consumers::<u64>(256, 1);
let per_producer = 20_000u64;
let count = Arc::new(AtomicUsize::new(0));
let count_c = count.clone();
let p1 = p.clone();
let p2 = p.clone();
drop(p);
let producer1 = thread::spawn(move || {
for i in 0..per_producer {
let v = i;
while p1.try_publish(v).is_err() {
std::hint::spin_loop();
}
}
});
let producer2 = thread::spawn(move || {
for i in 0..per_producer {
let v = 1_000_000 + i;
while p2.try_publish(v).is_err() {
std::hint::spin_loop();
}
}
});
let target = per_producer * 2;
let consumer = thread::spawn(move || {
let mut local = 0u64;
let c = &mut consumers[0];
while local < target {
if c.try_consume().is_some() {
local += 1;
count_c.fetch_add(1, Ordering::Relaxed);
}
}
});
producer1.join().unwrap();
producer2.join().unwrap();
consumer.join().unwrap();
assert_eq!(count.load(Ordering::Relaxed), target as usize);
}
#[test]
fn try_consume_returns_none_on_empty() {
let (_p, mut consumers) = MpmcDisruptor::with_consumers::<u32>(4, 1);
assert_eq!(consumers[0].try_consume(), None);
}
#[test]
fn capacity_rounded_up_to_power_of_two() {
let (p, _consumers) = MpmcDisruptor::with_consumers::<u32>(5, 1);
assert_eq!(p.capacity(), 8);
let (p, _consumers) = MpmcDisruptor::with_consumers::<u32>(1, 1);
assert_eq!(p.capacity(), 2);
}
#[test]
fn wraps_around_correctly_across_multiple_rounds() {
let (p, mut consumers) = MpmcDisruptor::with_consumers::<u32>(4, 1);
let c = &mut consumers[0];
for round in 0..4u32 {
for i in 0..4u32 {
p.try_publish(round * 4 + i).unwrap();
}
for i in 0..4u32 {
assert_eq!(c.try_consume(), Some(round * 4 + i));
}
}
}
}