use std::time::Duration;
use tokio::sync::broadcast;
use tropel_sdk::traits::Output;
use tropel_sdk::types::Sample;
pub(crate) fn spawn_extension_output(
mut rx: broadcast::Receiver<Sample>,
output: Box<dyn Output>,
) -> tokio::task::JoinHandle<()> {
const FLUSH_INTERVAL: Duration = Duration::from_secs(5);
const MAX_BATCH: usize = 10_000;
tokio::spawn(async move {
let mut batch: Vec<Sample> = Vec::with_capacity(1024);
let mut tick = tokio::time::interval(FLUSH_INTERVAL);
tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
loop {
tokio::select! {
res = rx.recv() => match res {
Ok(sample) => {
batch.push(sample);
if batch.len() >= MAX_BATCH {
let b = std::mem::replace(&mut batch, Vec::with_capacity(1024));
if let Err(e) = output.emit(&b).await {
tracing::warn!("extension output '{}' emit failed: {e}", output.name());
}
tokio::task::yield_now().await;
}
}
Err(broadcast::error::RecvError::Closed) => break,
Err(broadcast::error::RecvError::Lagged(n)) => {
tropel_metrics::OUTPUT_SAMPLES_DROPPED.fetch_add(n, std::sync::atomic::Ordering::Relaxed);
tracing::warn!("extension output dropped {n} samples (consumer lag)");
}
},
_ = tick.tick() => {
if !batch.is_empty() {
let b = std::mem::replace(&mut batch, Vec::with_capacity(1024));
if let Err(e) = output.emit(&b).await {
tracing::warn!("extension output '{}' emit failed: {e}", output.name());
}
tokio::task::yield_now().await;
}
}
}
}
if !batch.is_empty() {
if let Err(e) = output.emit(&batch).await {
tracing::warn!(
"extension output '{}' final emit failed: {e}",
output.name()
);
}
}
if let Err(e) = output.flush().await {
tracing::warn!("extension output '{}' flush failed: {e}", output.name());
}
})
}