use crate::registry::EntryStats;
use ek_event::{Event, HandlerFn};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use crossbeam_channel::Sender;
pub const SPILL_REPORT_EVERY: u64 = 128;
pub(crate) struct Forwarder {
tx: Sender<Event>,
alive: AtomicBool,
stats: Arc<EntryStats>,
reported: AtomicU64,
soft_cap: u64,
target: String,
}
impl Forwarder {
pub(crate) fn new(
tx: Sender<Event>,
stats: Arc<EntryStats>,
soft_cap: u64,
target: String,
) -> Arc<Self> {
Arc::new(Self {
tx,
alive: AtomicBool::new(true),
stats,
reported: AtomicU64::new(0),
soft_cap,
target,
})
}
pub fn forward(&self, event: &Event) {
if !self.alive.load(Ordering::Acquire) {
return;
}
if self.stats.pending.load(Ordering::Acquire) >= self.soft_cap {
self.on_spill();
return;
}
let event2 = Event::with_data(event.event_type.clone(), Arc::clone(&event.data));
let depth = self
.stats
.pending
.fetch_add(1, Ordering::AcqRel)
.saturating_add(1);
self.stats.peak.fetch_max(depth, Ordering::AcqRel);
match self.tx.try_send(event2) {
Ok(()) => {}
Err(_) => {
self.alive.store(false, Ordering::Release);
let _ = self
.stats
.pending
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |v| {
Some(v.saturating_sub(1))
});
}
}
}
fn on_spill(&self) {
let total = self.stats.spilled.fetch_add(1, Ordering::AcqRel) + 1;
let last = self.reported.load(Ordering::Acquire);
if total >= last + SPILL_REPORT_EVERY
&& self
.reported
.compare_exchange(last, total, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
eprintln!(
"ek-core: mailbox soft-cap spill: plugin {:?} dropped {} events in total (depth ≥ soft cap;pending={})",
self.target,
total,
self.stats.pending.load(Ordering::Acquire)
);
}
}
}
pub(crate) fn make_forwarder(fwd: Arc<Forwarder>) -> HandlerFn {
Arc::new(move |event: &Event| fwd.forward(event))
}