use crate::{
barrier::Barrier,
cursor::Cursor,
ringbuffer::RingBuffer,
wait_strategies::{WaitOutcome, Waiter, WakeupNotifier},
Sequence,
};
use crossbeam_utils::CachePadded;
use std::sync::{
atomic::{fence, AtomicI64, Ordering},
Arc,
};
use thiserror::Error;
pub struct EventPoller<E, B, N: WakeupNotifier = crate::wait_strategies::NoopWakeupNotifier> {
ring_buffer: Arc<RingBuffer<E>>,
dependent_barrier: Arc<B>,
shutdown_at_sequence: Arc<CachePadded<AtomicI64>>,
cursor: Arc<Cursor>,
notifier: N,
}
#[derive(Debug, Error, PartialEq)]
pub enum Polling {
#[error("No available events.")]
NoEvents,
#[error("Disruptor is shut down.")]
Shutdown,
}
pub struct EventGuard<'p, E, B, N: WakeupNotifier = crate::wait_strategies::NoopWakeupNotifier> {
parent: &'p mut EventPoller<E, B, N>,
sequence: Sequence,
available: Sequence,
}
impl<E, B, N: WakeupNotifier> EventGuard<'_, E, B, N> {
pub fn len(&self) -> usize {
if self.sequence > self.available {
0
} else {
(self.available - self.sequence + 1) as usize
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<'g, E, B, N: WakeupNotifier> Iterator for &'g mut EventGuard<'_, E, B, N> {
type Item = (Sequence, &'g E);
fn next(&mut self) -> Option<Self::Item> {
if self.sequence > self.available {
return None;
}
let sequence = self.sequence;
let event_ptr = unsafe { self.parent.ring_buffer.get(sequence).as_ptr() };
let event = unsafe { &*event_ptr };
self.sequence += 1;
Some((sequence, event))
}
}
impl<E, B, N: WakeupNotifier> ExactSizeIterator for &mut EventGuard<'_, E, B, N> {
fn len(&self) -> usize {
(**self).len()
}
}
impl<E, B, N: WakeupNotifier> Drop for EventGuard<'_, E, B, N> {
fn drop(&mut self) {
self.parent.cursor.store(self.available);
self.parent.notifier.wake();
}
}
impl<E, B, N> EventPoller<E, B, N>
where
B: Barrier,
N: WakeupNotifier,
{
pub(crate) fn new(
ring_buffer: Arc<RingBuffer<E>>,
dependent_barrier: Arc<B>,
shutdown_at_sequence: Arc<CachePadded<AtomicI64>>,
cursor: Arc<Cursor>,
notifier: N,
) -> Self {
Self {
ring_buffer,
dependent_barrier,
shutdown_at_sequence,
cursor,
notifier,
}
}
pub fn poll(&mut self) -> Result<EventGuard<'_, E, B, N>, Polling> {
self.poll_take(u64::MAX)
}
pub fn poll_take(&mut self, limit: u64) -> Result<EventGuard<'_, E, B, N>, Polling> {
let cursor_at = self.cursor.relaxed_value();
let sequence = cursor_at + 1;
if sequence == self.shutdown_at_sequence.load(Ordering::Relaxed) {
return Err(Polling::Shutdown);
}
let available = self.dependent_barrier.get_after(sequence);
if available < sequence {
return Err(Polling::NoEvents);
}
fence(Ordering::Acquire);
let max_sequence = cursor_at.saturating_add_unsigned(limit);
let available = std::cmp::min(available, max_sequence);
Ok(EventGuard {
parent: self,
sequence,
available,
})
}
pub fn poll_wait(
&mut self,
waiter: &mut impl Waiter,
) -> Result<EventGuard<'_, E, B, N>, Polling> {
let sequence = self.cursor.relaxed_value() + 1;
let available = match waiter.wait_for(
sequence,
self.dependent_barrier.as_ref(),
self.shutdown_at_sequence.as_ref(),
) {
WaitOutcome::Available { upper } => upper,
WaitOutcome::Shutdown => return Err(Polling::Shutdown),
WaitOutcome::Timeout => return Err(Polling::NoEvents),
};
Ok(EventGuard {
parent: self,
sequence,
available,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::consumer::SingleConsumerBarrier;
use crate::ringbuffer::RingBuffer;
use crate::wait_strategies::{NoopWakeupNotifier, WaitStrategy};
use crate::Producer;
#[test]
fn poller_observes_manual_shutdown_without_producer_drop() {
#[derive(Debug)]
struct Event;
let ring_buffer = Arc::new(RingBuffer::new(2, || Event));
let cursor = Arc::new(Cursor::new(-1));
let shutdown = Arc::new(CachePadded::new(AtomicI64::new(-1)));
let barrier = Arc::new(SingleConsumerBarrier::new(Arc::clone(&cursor)));
let mut poller = EventPoller::new(
ring_buffer,
barrier,
Arc::clone(&shutdown),
Arc::clone(&cursor),
NoopWakeupNotifier,
);
shutdown.store(0, Ordering::Relaxed);
assert_eq!(poller.poll().err(), Some(Polling::Shutdown));
}
#[test]
fn poller_exits_on_shutdown_race() {
#[derive(Debug)]
struct Event;
let ring_buffer = Arc::new(RingBuffer::new(2, || Event));
let cursor = Arc::new(Cursor::new(-1));
let shutdown = Arc::new(CachePadded::new(AtomicI64::new(-1)));
let barrier = Arc::new(SingleConsumerBarrier::new(Arc::clone(&cursor)));
let mut poller = EventPoller::new(
ring_buffer,
barrier,
Arc::clone(&shutdown),
Arc::clone(&cursor),
NoopWakeupNotifier,
);
let shutdown_ref = shutdown.clone();
let handle = std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(20));
shutdown_ref.store(0, Ordering::Relaxed);
});
let mut saw_shutdown = false;
for _ in 0..10 {
if matches!(poller.poll(), Err(Polling::Shutdown)) {
saw_shutdown = true;
break;
}
std::thread::sleep(std::time::Duration::from_millis(5));
}
handle.join().unwrap();
assert!(saw_shutdown, "poller should see shutdown promptly");
}
#[test]
fn poll_wait_can_drive_poller_with_library_waiter() {
#[derive(Debug)]
struct Event(i64);
let factory = || Event(0);
let builder = crate::build_single_producer(8, factory, crate::BusySpin);
let (mut poller, builder) = builder.event_poller();
let mut producer = builder.build();
producer.publish(|e| e.0 = 42);
drop(producer);
let mut waiter = crate::BusySpin.new_waiter();
let mut guard = poller
.poll_wait(&mut waiter)
.expect("should see published event");
let (_seq, got) = (&mut guard).next().unwrap();
let got = got.0;
assert_eq!(got, 42);
drop(guard);
assert_eq!(poller.poll_wait(&mut waiter).err(), Some(Polling::Shutdown));
}
}