#![doc(html_logo_url = "https://raw.githubusercontent.com/emit-rs/emit/main/asset/logo.svg")]
#![deny(missing_docs)]
use crate::internal_metrics::InternalMetrics;
use std::{
any::Any,
cmp, error, fmt,
future::Future,
mem,
panic::{self, AssertUnwindSafe, UnwindSafe},
pin::Pin,
sync::{Arc, Mutex},
task::{Context, Poll},
time::Duration,
};
mod internal_metrics;
pub trait Channel {
type Item;
fn new() -> Self;
fn with_capacity(capacity_hint: usize) -> Self
where
Self: Sized,
{
let _ = capacity_hint;
Self::new()
}
fn push(&mut self, item: Self::Item);
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn clear(&mut self);
}
impl<T> Channel for Vec<T> {
type Item = T;
fn new() -> Self {
Vec::new()
}
fn with_capacity(capacity: usize) -> Self {
Vec::with_capacity(capacity)
}
fn push<'a>(&mut self, item: Self::Item) {
self.push(item);
}
fn len(&self) -> usize {
self.len()
}
fn is_empty(&self) -> bool {
self.is_empty()
}
fn clear(&mut self) {
self.clear()
}
}
pub fn bounded<T: Channel>(max_capacity: usize) -> (Sender<T>, Receiver<T>) {
let shared = Arc::new(Shared {
metrics: Default::default(),
receiver_notifier: ReceiverNotifier::new(),
state: Mutex::new(State {
next_batch: Batch::new(),
is_open: true,
is_in_batch: false,
}),
});
(
Sender {
max_capacity,
shared: shared.clone(),
},
Receiver {
idle_delay: Delay::new(Duration::from_millis(1), Duration::from_millis(500)),
retry: Retry::new(10),
retry_delay: Delay::new(Duration::from_millis(700), Duration::from_secs(10)),
capacity: Capacity::new(),
shared,
#[cfg(all(not(target_arch = "wasm32"), test))]
test_barriers: TestBarriers::default(),
},
)
}
pub struct Sender<T> {
max_capacity: usize,
shared: Arc<Shared<T>>,
}
impl<T> Drop for Sender<T> {
fn drop(&mut self) {
self.shared.state.lock().unwrap().is_open = false;
self.shared.receiver_notifier.notify();
}
}
impl<T: Channel> Sender<T> {
pub fn send<'a>(&self, msg: T::Item) {
let mut state = self.shared.state.lock().unwrap();
if state.next_batch.channel.len() >= self.max_capacity {
state.next_batch.channel.clear();
self.shared.metrics.queue_full_truncated.increment();
}
if !state.is_open {
return;
}
state.next_batch.channel.push(msg);
let len = state.next_batch.channel.len();
drop(state);
if len == self.notify_threshold() {
self.shared.receiver_notifier.notify();
}
}
pub fn try_send<'a>(&self, msg: T::Item) -> Result<(), BatchError<T::Item>> {
let mut state = self.shared.state.lock().unwrap();
if !state.is_open {
return Err(BatchError::no_retry(TrySendError("the channel is closed")));
}
if state.next_batch.channel.len() < self.max_capacity {
state.next_batch.channel.push(msg);
let len = state.next_batch.channel.len();
drop(state);
if len == self.notify_threshold() {
self.shared.receiver_notifier.notify();
}
Ok(())
} else {
Err(BatchError::retry(TrySendError("the channel is full"), msg))
}
}
fn notify_threshold(&self) -> usize {
cmp::max(1, self.max_capacity / 2)
}
async fn send_or_wait<'a, FWait: Future<Output = ()> + 'a>(
&'a self,
msg: T::Item,
timeout: Duration,
elapsed: impl Fn() -> Duration,
mut wait_until_empty: impl FnMut(&'a Self, Duration) -> FWait,
) -> Result<(), BatchError<T::Item>> {
match self.try_send(msg) {
Ok(()) => Ok(()),
Err(mut err) => {
self.shared.metrics.queue_full_blocked.increment();
loop {
let elapsed = elapsed();
if elapsed >= timeout {
return Err(err);
}
wait_until_empty(self, timeout.saturating_sub(elapsed)).await;
match self.try_send(err.try_into_retryable()?) {
Ok(()) => return Ok(()),
Err(retry) => {
err = retry;
continue;
}
}
}
}
}
}
pub fn when_empty(&self, f: impl FnOnce() + Send + 'static) {
let mut state = self.shared.state.lock().unwrap();
if state.next_batch.channel.is_empty() || !state.is_open {
drop(state);
f();
} else {
state.next_batch.sender_notifiers.push_on_take(Box::new(f));
drop(state);
self.shared.receiver_notifier.notify();
}
}
pub fn when_flushed(&self, f: impl FnOnce() + Send + 'static) {
self.when_flushed_inner(move |_| f())
}
fn when_flushed_inner(&self, f: impl FnOnce(bool) + Send + 'static) {
let mut state = self.shared.state.lock().unwrap();
if !state.is_in_batch && state.next_batch.channel.is_empty() {
drop(state);
f(true);
}
else if !state.is_open {
drop(state);
f(false);
}
else {
let requires_in_flight = state.is_in_batch;
state
.next_batch
.sender_notifiers
.push_on_flush(requires_in_flight, Box::new(f));
drop(state);
self.shared.receiver_notifier.notify();
}
}
pub fn metric_source(&self) -> ChannelMetrics<T> {
ChannelMetrics {
shared: self.shared.clone(),
}
}
}
#[cfg(all(not(target_arch = "wasm32"), test))]
#[derive(Default, Clone)]
struct TestBarriers {
pre_take: Option<Arc<::tokio::sync::Barrier>>,
post_take: Option<Arc<::tokio::sync::Barrier>>,
post_process: Option<Arc<::tokio::sync::Barrier>>,
}
#[cfg(all(not(target_arch = "wasm32"), test))]
impl TestBarriers {
async fn wait_pre_take(&self) {
if let Some(ref barrier) = self.pre_take {
barrier.wait().await;
}
}
async fn wait_post_take(&self) {
if let Some(ref barrier) = self.post_take {
barrier.wait().await;
}
}
async fn wait_post_process(&self) {
if let Some(ref barrier) = self.post_process {
barrier.wait().await;
}
}
}
pub struct Receiver<T> {
idle_delay: Delay,
retry: Retry,
retry_delay: Delay,
capacity: Capacity,
shared: Arc<Shared<T>>,
#[cfg(all(not(target_arch = "wasm32"), test))]
test_barriers: TestBarriers,
}
impl<T> Drop for Receiver<T> {
fn drop(&mut self) {
self.shared.state.lock().unwrap().is_open = false;
}
}
impl<T: Channel> Receiver<T> {
pub async fn exec<
FBatch: Future<Output = Result<(), BatchError<T>>>,
FWait: Future<Output = ()>,
>(
self,
mut wait: impl FnMut(Duration) -> FWait,
on_batch: impl FnMut(T) -> FBatch,
) {
self.exec_inner(move |_, delay| wait(delay), on_batch).await
}
pub(crate) async fn exec_inner<
FBatch: Future<Output = Result<(), BatchError<T>>>,
FWait: Future<Output = ()>,
>(
mut self,
mut wait: impl FnMut(Wait, Duration) -> FWait,
mut on_batch: impl FnMut(T) -> FBatch,
) {
let mut next_batch = Batch::new();
let mut last_batch_flushed = true;
loop {
#[cfg(all(not(target_arch = "wasm32"), test))]
self.test_barriers.wait_pre_take().await;
let (mut current_batch, is_open) = {
let mut state = self.shared.state.lock().unwrap();
if state.next_batch.channel.len() > 0 {
state.is_in_batch = true;
(
mem::replace(&mut state.next_batch, mem::take(&mut next_batch)),
state.is_open,
)
}
else {
state.is_in_batch = false;
let notifiers = mem::take(&mut state.next_batch.sender_notifiers);
let open = state.is_open;
(
Batch {
channel: T::new(),
sender_notifiers: notifiers,
},
open,
)
}
};
current_batch.sender_notifiers.notify_on_take();
#[cfg(all(not(target_arch = "wasm32"), test))]
self.test_barriers.wait_post_take().await;
if current_batch.channel.len() > 0 {
self.retry.reset();
self.retry_delay.reset();
self.idle_delay.reset();
next_batch = Batch {
channel: T::with_capacity(self.capacity.next(current_batch.channel.len())),
sender_notifiers: SenderNotifiers::new(),
};
let mut batch_flushed = false;
loop {
match panic::catch_unwind(AssertUnwindSafe(|| on_batch(current_batch.channel)))
{
Ok(on_batch_future) => {
match CatchUnwind(AssertUnwindSafe(on_batch_future)).await {
Ok(Ok(())) => {
self.shared.metrics.queue_batch_processed.increment();
batch_flushed = true;
break;
}
Ok(Err(BatchError { retryable })) => {
self.shared.metrics.queue_batch_failed.increment();
if let Some(retryable) = retryable {
if retryable.len() > 0 && self.retry.next() {
wait(Wait::Retry, self.retry_delay.next()).await;
current_batch = Batch {
channel: retryable,
sender_notifiers: current_batch.sender_notifiers,
};
self.shared.metrics.queue_batch_retry.increment();
continue;
}
}
break;
}
Err(_) => {
self.shared.metrics.queue_batch_panicked.increment();
break;
}
}
}
Err(_) => {
self.shared.metrics.queue_batch_panicked.increment();
break;
}
}
}
current_batch
.sender_notifiers
.notify_on_flush(batch_flushed, last_batch_flushed);
last_batch_flushed = batch_flushed;
#[cfg(all(not(target_arch = "wasm32"), test))]
self.test_barriers.wait_post_process().await;
}
else {
current_batch
.sender_notifiers
.notify_on_flush(true, last_batch_flushed);
#[cfg(all(not(target_arch = "wasm32"), test))]
self.test_barriers.wait_post_process().await;
if !is_open {
return;
}
wait(Wait::Idle, self.idle_delay.next()).await;
}
}
}
pub fn metric_source(&self) -> ChannelMetrics<T> {
ChannelMetrics {
shared: self.shared.clone(),
}
}
}
#[derive(Debug)]
pub struct BatchError<T> {
retryable: Option<T>,
}
impl<T> BatchError<T> {
pub fn no_retry(_: impl std::error::Error + Send + Sync + 'static) -> Self {
BatchError { retryable: None }
}
pub fn retry(_: impl std::error::Error + Send + Sync + 'static, retryable: T) -> Self {
BatchError {
retryable: Some(retryable),
}
}
pub fn try_into_retryable(self) -> Result<T, BatchError<T>> {
self.retryable.ok_or_else(|| BatchError { retryable: None })
}
pub fn into_retryable(self) -> Option<T> {
self.retryable
}
pub fn map_retryable<U>(self, f: impl FnOnce(Option<T>) -> Option<U>) -> BatchError<U> {
BatchError {
retryable: f(self.retryable),
}
}
}
struct TrySendError(&'static str);
impl fmt::Debug for TrySendError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self.0, f)
}
}
impl fmt::Display for TrySendError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self.0, f)
}
}
impl error::Error for TrySendError {}
struct CatchUnwind<F>(F);
impl<F: Future + UnwindSafe> Future for CatchUnwind<F> {
type Output = Result<F::Output, Box<dyn Any + Send>>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let f = unsafe { Pin::map_unchecked_mut(self, |x| &mut x.0) };
panic::catch_unwind(AssertUnwindSafe(|| f.poll(cx)))?.map(Ok)
}
}
struct Delay {
current: Duration,
step: Duration,
max: Duration,
}
impl Delay {
fn new(step: Duration, max: Duration) -> Delay {
Delay {
current: Duration::ZERO,
step,
max,
}
}
fn reset(&mut self) {
self.current = Duration::ZERO
}
fn next(&mut self) -> Duration {
self.current = cmp::min(self.current * 2 + self.step, self.max);
self.current
}
}
const CAPACITY_WINDOW: usize = 32;
struct Capacity {
rolling_values: [usize; CAPACITY_WINDOW],
idx: usize,
}
impl Capacity {
fn new() -> Self {
Capacity {
rolling_values: [0; CAPACITY_WINDOW],
idx: 0,
}
}
fn next(&mut self, last_len: usize) -> usize {
self.rolling_values[self.idx % CAPACITY_WINDOW] = last_len;
self.idx = self.idx.wrapping_add(1);
let max_len = self.rolling_values.iter().copied().max().unwrap();
max_len.saturating_add(cmp::max(1, max_len / 10))
}
}
struct Retry {
current: u32,
max: u32,
}
impl Retry {
fn new(max: u32) -> Self {
Retry { current: 0, max }
}
fn reset(&mut self) {
self.current = 0;
}
fn next(&mut self) -> bool {
self.current += 1;
self.current <= self.max
}
}
struct Shared<T> {
metrics: InternalMetrics,
receiver_notifier: ReceiverNotifier,
state: Mutex<State<T>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Wait {
Idle,
Retry,
}
pub struct ChannelMetrics<T> {
shared: Arc<Shared<T>>,
}
impl<T: Channel> emit::metric::Source for ChannelMetrics<T> {
fn sample_metrics<S: emit::metric::sampler::Sampler>(&self, sampler: S) {
let queue_length = { self.shared.state.lock().unwrap().next_batch.channel.len() };
self.shared.metrics.sample_metrics(&sampler);
emit::metric::Metric::new(
emit::pkg!(),
emit::Empty,
emit::props! {
metric_name: "queue_length",
metric_agg: "last",
metric_value: queue_length,
},
)
.sample_metrics(&sampler);
}
}
struct State<T> {
next_batch: Batch<T>,
is_open: bool,
is_in_batch: bool,
}
struct Batch<T> {
channel: T,
sender_notifiers: SenderNotifiers,
}
impl<T: Channel> Batch<T> {
fn new() -> Self {
Batch {
channel: T::new(),
sender_notifiers: SenderNotifiers::new(),
}
}
}
impl<T: Channel> Default for Batch<T> {
fn default() -> Self {
Batch::new()
}
}
struct SenderNotifiers {
on_take: Vec<SenderNotifier>,
on_flush: Vec<SenderFlushNotifier>,
}
type SenderNotifier = Box<dyn FnOnce() + Send>;
struct SenderFlushNotifier {
chain_with_last_batch: bool,
notify: Box<dyn FnOnce(bool) + Send>,
}
impl Default for SenderNotifiers {
fn default() -> Self {
SenderNotifiers::new()
}
}
impl SenderNotifiers {
fn new() -> Self {
SenderNotifiers {
on_take: Vec::new(),
on_flush: Vec::new(),
}
}
fn push_on_flush(&mut self, chain_with_last_batch: bool, notify: Box<dyn FnOnce(bool) + Send>) {
self.on_flush.push(SenderFlushNotifier {
chain_with_last_batch,
notify,
});
}
fn notify_on_flush(&mut self, target_batch_flushed: bool, last_batch_flushed: bool) {
for notifier in mem::take(&mut self.on_flush) {
let flushed = if notifier.chain_with_last_batch {
target_batch_flushed && last_batch_flushed
} else {
target_batch_flushed
};
let notify = notifier.notify;
let _ = panic::catch_unwind(AssertUnwindSafe(move || notify(flushed)));
}
}
fn push_on_take(&mut self, notifier: SenderNotifier) {
self.on_take.push(notifier);
}
fn notify_on_take(&mut self) {
for notifier in mem::take(&mut self.on_take) {
let _ = panic::catch_unwind(AssertUnwindSafe(notifier));
}
}
}
struct ReceiverNotifier {
sync: sync::Trigger,
#[cfg(feature = "tokio")]
tokio: tokio::Trigger,
}
impl ReceiverNotifier {
fn new() -> Self {
ReceiverNotifier {
sync: sync::Trigger::new(),
#[cfg(feature = "tokio")]
tokio: tokio::Trigger::new(),
}
}
fn notify(&self) {
self.sync.trigger(true);
#[cfg(feature = "tokio")]
{
self.tokio.trigger(true);
}
}
}
pub mod sync;
#[cfg(all(
feature = "tokio",
not(all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
))
))]
pub mod tokio;
#[cfg(feature = "web")]
#[cfg(all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
))]
pub mod web;
#[cfg(all(
feature = "tokio",
not(all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
))
))]
pub use tokio::{blocking_flush, blocking_send};
#[cfg(not(all(
feature = "tokio",
not(all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
))
)))]
pub use sync::{blocking_flush, blocking_send};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capacity() {
let mut capacity = Capacity::new();
let next = capacity.next(10);
assert_eq!(11, next);
let next = capacity.next(5);
assert_eq!(11, next);
let next = capacity.next(100);
assert_eq!(110, next);
for _ in 0..CAPACITY_WINDOW {
let _ = capacity.next(0);
}
let next = capacity.next(0);
assert_eq!(1, next);
}
}