extern crate std;
use crate::channel::{Subscriber, TryRecvError};
use crate::pod::Pod;
use crate::wait::WaitStrategy;
use alloc::sync::Arc;
use core::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use std::thread::{self, JoinHandle};
use super::{STAGE_COMPLETED, STAGE_PANICKED, STAGE_RUNNING};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DrainPolicy {
Drain,
Immediate,
}
pub struct Consumer {
handle: Option<JoinHandle<()>>,
status: Arc<AtomicU8>,
shutdown: Arc<AtomicBool>,
}
impl Consumer {
pub fn spawn<T, F>(
mut sub: Subscriber<T>,
strategy: WaitStrategy,
drain: DrainPolicy,
mut f: F,
) -> Consumer
where
T: Pod,
F: FnMut(T, u64, bool) + Send + 'static,
{
let status = Arc::new(AtomicU8::new(STAGE_RUNNING));
let status_inner = status.clone();
let shutdown = Arc::new(AtomicBool::new(false));
let shutdown_inner = shutdown.clone();
let handle = thread::spawn(move || {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let mut idle: u32 = 0;
loop {
if shutdown_inner.load(Ordering::Acquire) {
if drain == DrainPolicy::Drain {
drain_batch(&mut sub, &mut f);
}
return;
}
if drain_batch(&mut sub, &mut f) == 0 {
strategy.wait(idle);
idle = idle.saturating_add(1);
} else {
idle = 0;
}
}
}));
match result {
Ok(()) => status_inner.store(STAGE_COMPLETED, Ordering::Release),
Err(_) => status_inner.store(STAGE_PANICKED, Ordering::Release),
}
});
Consumer {
handle: Some(handle),
status,
shutdown,
}
}
pub fn shutdown(&self) {
self.shutdown.store(true, Ordering::Release);
}
pub fn join(mut self) {
if let Some(h) = self.handle.take() {
let _ = h.join();
}
}
pub fn panicked(&self) -> bool {
self.status.load(Ordering::Acquire) == STAGE_PANICKED
}
pub fn is_healthy(&self) -> bool {
self.status.load(Ordering::Acquire) == STAGE_RUNNING
}
}
impl Drop for Consumer {
fn drop(&mut self) {
self.shutdown.store(true, Ordering::Release);
}
}
#[inline]
fn drain_batch<T: Pod>(sub: &mut Subscriber<T>, f: &mut impl FnMut(T, u64, bool)) -> u64 {
let batch = sub.pending();
if batch == 0 {
return 0;
}
let mut done = 0;
for i in 0..batch {
let seq = sub.cursor();
match sub.try_recv() {
Ok(value) => {
f(value, seq, i + 1 == batch);
done += 1;
}
Err(TryRecvError::Lagged { .. }) => {}
Err(TryRecvError::Empty) => break,
}
}
done
}