use core::sync::atomic::{self, AtomicU64};
use crate::std::sync::Arc;
use crate::{
Layer, Service,
extensions::{Extension, ExtensionsRef},
};
use rama_utils::macros::define_inner_service_accessors;
pub trait InputCounterTracker: Extension {
fn concurrent_active_input_count(&self) -> u64;
fn total_input_count(&self) -> u64;
fn input_count(&self) -> u64;
}
pub trait InputCounter: Clone + Send + Sync + 'static {
type Tracker: InputCounterTracker;
fn increment(&self) -> Self::Tracker;
fn decrement(&self);
}
#[derive(Debug, Clone, Default)]
pub struct DefaultInputCounter(Arc<DefaultInputCounterData>);
#[derive(Debug, Clone, Extension)]
pub struct InputCounterExtension {
data: Arc<DefaultInputCounterData>,
input_count: u64,
}
impl InputCounterExtension {
fn new(data: Arc<DefaultInputCounterData>) -> Self {
let input_count = data.total_inputs.fetch_add(1, atomic::Ordering::AcqRel) + 1;
_ = data
.concurrent_inputs
.fetch_add(1, atomic::Ordering::AcqRel);
Self { data, input_count }
}
}
impl InputCounterTracker for InputCounterExtension {
#[inline(always)]
fn concurrent_active_input_count(&self) -> u64 {
self.data.concurrent_inputs.load(atomic::Ordering::Acquire)
}
#[inline(always)]
fn total_input_count(&self) -> u64 {
self.data.total_inputs.load(atomic::Ordering::Acquire)
}
#[inline(always)]
fn input_count(&self) -> u64 {
self.input_count
}
}
impl InputCounter for DefaultInputCounter {
type Tracker = InputCounterExtension;
#[inline(always)]
fn increment(&self) -> Self::Tracker {
InputCounterExtension::new(self.0.clone())
}
#[inline(always)]
fn decrement(&self) {
let prev = self
.0
.concurrent_inputs
.fetch_sub(1, atomic::Ordering::AcqRel);
debug_assert!(
prev > 0,
"concurrent_inputs underflow, decrement called more times than increment"
);
}
}
#[derive(Debug, Default)]
struct DefaultInputCounterData {
total_inputs: AtomicU64,
concurrent_inputs: AtomicU64,
}
struct DecrementGuard<C: InputCounter> {
counter: C,
}
impl<C: InputCounter> DecrementGuard<C> {
#[inline(always)]
fn new(counter: C) -> Self {
Self { counter }
}
}
impl<C: InputCounter> Drop for DecrementGuard<C> {
#[inline(always)]
fn drop(&mut self) {
self.counter.decrement();
}
}
#[derive(Debug, Clone)]
pub struct CountInput<S, C = DefaultInputCounter> {
inner: S,
counter: C,
}
impl<S, C> CountInput<S, C> {
#[inline(always)]
pub const fn new_with_counter(inner: S, counter: C) -> Self {
Self { inner, counter }
}
define_inner_service_accessors!();
}
impl<S> CountInput<S> {
pub fn new(inner: S) -> Self {
Self::new_with_counter(inner, DefaultInputCounter::default())
}
}
impl<S, C, Input> Service<Input> for CountInput<S, C>
where
S: Service<Input>,
C: InputCounter,
Input: ExtensionsRef + Send + 'static,
{
type Output = S::Output;
type Error = S::Error;
async fn serve(&self, input: Input) -> Result<Self::Output, Self::Error> {
let tracker = self.counter.increment();
let _guard = DecrementGuard::new(self.counter.clone());
input.extensions().insert(tracker);
self.inner.serve(input).await
}
}
#[derive(Debug, Clone, Default)]
pub struct CountInputLayer<C = DefaultInputCounter> {
counter: C,
}
impl<C> CountInputLayer<C> {
pub const fn new_with_counter(counter: C) -> Self {
Self { counter }
}
}
impl CountInputLayer {
#[inline(always)]
pub fn new() -> Self {
Self::new_with_counter(DefaultInputCounter::default())
}
}
impl<S, C> Layer<S> for CountInputLayer<C>
where
C: Clone,
{
type Service = CountInput<S, C>;
fn layer(&self, inner: S) -> Self::Service {
CountInput {
inner,
counter: self.counter.clone(),
}
}
fn into_layer(self, inner: S) -> Self::Service {
let Self { counter } = self;
CountInput { inner, counter }
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use core::convert::Infallible;
use crate::{ServiceInput, service::service_fn};
use super::*;
#[test]
fn default_counter_increments_total_and_concurrent() {
let counter = DefaultInputCounter::default();
let t1 = counter.increment();
assert_eq!(t1.input_count(), 1);
assert_eq!(t1.total_input_count(), 1);
assert_eq!(t1.concurrent_active_input_count(), 1);
let t2 = counter.increment();
assert_eq!(t2.input_count(), 2);
assert_eq!(t2.total_input_count(), 2);
assert_eq!(t2.concurrent_active_input_count(), 2);
counter.decrement();
assert_eq!(t2.concurrent_active_input_count(), 1);
assert_eq!(t2.total_input_count(), 2);
counter.decrement();
assert_eq!(t2.concurrent_active_input_count(), 0);
assert_eq!(t2.total_input_count(), 2);
}
#[test]
fn tracker_is_a_snapshot_of_input_count_but_reads_live_totals() {
let counter = DefaultInputCounter::default();
let t1 = counter.increment();
assert_eq!(t1.input_count(), 1);
assert_eq!(t1.total_input_count(), 1);
assert_eq!(t1.concurrent_active_input_count(), 1);
let _t2 = counter.increment();
assert_eq!(t1.input_count(), 1);
assert_eq!(t1.total_input_count(), 2);
assert_eq!(t1.concurrent_active_input_count(), 2);
counter.decrement();
counter.decrement();
assert_eq!(t1.total_input_count(), 2);
assert_eq!(t1.concurrent_active_input_count(), 0);
}
#[test]
fn decrement_guard_decrements_on_drop() {
let counter = DefaultInputCounter::default();
let t1 = counter.increment();
assert_eq!(t1.concurrent_active_input_count(), 1);
{
let _guard = DecrementGuard::new(counter);
}
assert_eq!(t1.concurrent_active_input_count(), 0);
}
#[tokio::test]
async fn input_count_svc() {
let svc = CountInput::new(service_fn(async |input: ServiceInput<()>| {
Ok::<_, Infallible>(
input
.extensions
.get_ref::<InputCounterExtension>()
.unwrap()
.input_count(),
)
}));
for expected_count in 1..3 {
let Ok(count) = svc.serve(ServiceInput::new(())).await;
assert_eq!(expected_count, count);
}
}
}