use std::convert::Infallible;
use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::task::{ready, Context, Poll};
use std::time::Duration;
use axum::body::Body;
use pin_project_lite::pin_project;
use tokio::sync::Notify;
use tower::Service;
#[derive(Debug, Default)]
pub(super) struct Activity {
active: AtomicUsize,
changed: Notify,
}
impl Activity {
fn enter(self: &Arc<Self>) -> InFlight {
if self.active.fetch_add(1, Ordering::AcqRel) == 0 {
self.changed.notify_one();
}
InFlight(Arc::clone(self))
}
pub(super) async fn idle_for(&self, timeout: Duration) {
loop {
if self.active.load(Ordering::Acquire) == 0 {
tokio::select! {
() = tokio::time::sleep(timeout) => {
if self.active.load(Ordering::Acquire) == 0 {
return;
}
}
() = self.changed.notified() => {}
}
} else {
self.changed.notified().await;
}
}
}
}
#[derive(Debug)]
struct InFlight(Arc<Activity>);
impl Drop for InFlight {
fn drop(&mut self) {
if self.0.active.fetch_sub(1, Ordering::AcqRel) == 1 {
self.0.changed.notify_one();
}
}
}
#[derive(Clone, Debug)]
pub(super) struct Tracked<S> {
pub(super) inner: S,
pub(super) activity: Arc<Activity>,
}
impl<S, R> Service<R> for Tracked<S>
where
S: Service<R, Response = http::Response<Body>, Error = Infallible>,
{
type Response = http::Response<Body>;
type Error = Infallible;
type Future = TrackedFuture<S::Future>;
#[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Infallible>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, request: R) -> Self::Future {
TrackedFuture {
in_flight: Some(self.activity.enter()),
future: self.inner.call(request),
}
}
}
pin_project! {
pub(super) struct TrackedFuture<F> {
#[pin]
future: F,
in_flight: Option<InFlight>,
}
}
impl<F> Future for TrackedFuture<F>
where
F: Future<Output = Result<http::Response<Body>, Infallible>>,
{
type Output = Result<http::Response<Body>, Infallible>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let response = ready!(this.future.poll(cx))?;
Poll::Ready(Ok(match this.in_flight.take() {
Some(in_flight) => response.map(|body| crate::held::until_end(body, in_flight)),
None => response,
}))
}
}
#[cfg(test)]
mod tests;