use std::sync::Arc;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::time::Duration;
use tokio::sync::{Notify, watch};
use tokio::time::sleep;
use tokio_util::sync::CancellationToken;
use tracing::debug;
const NANOS_NONE: u64 = u64::MAX;
#[derive(Clone, Debug)]
pub struct Gate {
inner: Arc<Inner>,
}
#[derive(Debug)]
struct Inner {
graceful: NotifyOnce,
shutdown: NotifyOnce,
released: Notify,
count: AtomicUsize,
all_done: NotifyOnce,
grace_period_nanos: AtomicU64,
acquire_timeout: Duration,
max_count: Option<usize>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Error {
ShuttingDown,
AcquireTimeout(Duration),
AtCapacity,
}
impl std::fmt::Display for Error {
fn fmt(
&self,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
match self {
Self::ShuttingDown => f.write_str("gate is shutting down"),
Self::AcquireTimeout(timeout) => {
write!(f, "gate was at capacity for {timeout:?}")
}
Self::AtCapacity => f.write_str("gate is at capacity"),
}
}
}
impl std::error::Error for Error {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use = "forced gate shutdown must be handled by the caller"]
pub enum GateDrainOutcome {
Drained,
Forced { remaining: usize },
}
impl Gate {
pub fn new(
max_count: Option<usize>,
acquire_timeout: Duration,
) -> Self {
let inner = Inner {
graceful: NotifyOnce::default(),
shutdown: NotifyOnce::default(),
released: Notify::new(),
count: AtomicUsize::new(0),
all_done: NotifyOnce::default(),
grace_period_nanos: AtomicU64::new(NANOS_NONE),
acquire_timeout,
max_count,
};
Self { inner: Arc::new(inner) }
}
pub fn grace_period(&self) -> Option<Duration> {
match self.inner.grace_period_nanos.load(Ordering::Acquire) {
NANOS_NONE => None,
nanos => Some(Duration::from_nanos(nanos)),
}
}
pub fn count(&self) -> usize {
self.inner.count.load(Ordering::Acquire)
}
pub fn max_count(&self) -> Option<usize> {
self.inner.max_count
}
pub fn force_shutdown(&self) {
self.inner.shutdown.notify_waiters();
}
pub fn graceful_shutdown(
&self,
duration: Option<Duration>,
) {
let nanos = duration.map_or(NANOS_NONE, |d| d.as_nanos() as u64);
self.inner.grace_period_nanos.store(nanos, Ordering::Release);
self.inner.graceful.notify_waiters();
}
pub fn is_shutting_down(&self) -> bool {
self.inner.graceful.is_notified()
}
pub async fn wait_forced_shutdown(&self) {
self.inner.shutdown.notified().await;
}
pub async fn wait_graceful_shutdown(&self) {
self.inner.graceful.notified().await;
}
pub async fn enter(&self) -> Result<Permit, Error> {
let wait_timeout = self.inner.acquire_timeout;
let start = tokio::time::Instant::now();
loop {
match self.try_enter() {
Ok(permit) => return Ok(permit),
Err(Error::AtCapacity) => {
if let Some(max_count) = self.inner.max_count {
debug!(
"Connection limit reached: {}/{} connections in use",
self.count(),
max_count
);
}
}
Err(error) => return Err(error),
}
let elapsed = start.elapsed();
if elapsed >= wait_timeout {
return Err(Error::AcquireTimeout(wait_timeout));
}
let remaining = wait_timeout - elapsed;
tokio::select! {
biased;
_ = self.inner.graceful.notified() => {
return Err(Error::ShuttingDown);
}
_ = self.inner.released.notified() => {
continue;
}
_ = sleep(remaining) => {
return Err(Error::AcquireTimeout(wait_timeout));
}
}
}
}
pub fn try_enter(&self) -> Result<Permit, Error> {
if self.inner.graceful.is_notified() {
return Err(Error::ShuttingDown);
}
if let Some(max) = self.inner.max_count {
let prev = self.inner.count.fetch_add(1, Ordering::AcqRel);
if prev >= max {
self.inner.count.fetch_sub(1, Ordering::AcqRel);
return Err(Error::AtCapacity);
}
} else {
self.inner.count.fetch_add(1, Ordering::AcqRel);
}
Ok(Permit { gate: self.clone() })
}
pub async fn wait_all_done(&self) -> GateDrainOutcome {
if self.inner.count.load(Ordering::Acquire) == 0 {
return GateDrainOutcome::Drained;
}
let deadline = self.grace_period();
match deadline {
Some(duration) => tokio::select! {
biased;
_ = sleep(duration) => {
let remaining = self.count();
self.force_shutdown();
GateDrainOutcome::Forced { remaining }
},
_ = self.inner.all_done.notified() => {
debug!("🍺 All connections finished before graceful timeout");
GateDrainOutcome::Drained
},
},
None => {
self.inner.all_done.notified().await;
GateDrainOutcome::Drained
}
}
}
}
pub struct Permit {
gate: Gate,
}
impl Permit {
pub async fn wait_graceful_shutdown(&self) {
self.gate.wait_graceful_shutdown().await
}
pub async fn wait_forced_shutdown(&self) {
self.gate.wait_forced_shutdown().await
}
pub fn is_shutting_down(&self) -> bool {
self.gate.is_shutting_down()
}
}
impl Drop for Permit {
fn drop(&mut self) {
let count = self.gate.inner.count.fetch_sub(1, Ordering::AcqRel) - 1;
if count == 0 && self.gate.inner.graceful.is_notified() {
self.gate.inner.all_done.notify_waiters();
}
if let Some(max_count) = self.gate.inner.max_count
&& count < max_count
{
self.gate.inner.released.notify_waiters();
}
}
}
pub fn create_gate(
token: CancellationToken,
graceful_timeout: Option<Duration>,
max_count: Option<usize>,
acquire_timeout: Duration,
) -> Gate {
let gate = Gate::new(max_count, acquire_timeout);
let shutdown_gate = gate.clone();
tokio::spawn(async move {
token.cancelled().await;
shutdown_gate.graceful_shutdown(graceful_timeout);
});
gate
}
#[inline]
pub fn default_acquire_timeout() -> Duration {
Duration::from_millis(200)
}
#[derive(Debug)]
struct NotifyOnce {
tx: watch::Sender<bool>,
rx: watch::Receiver<bool>,
}
impl Default for NotifyOnce {
fn default() -> Self {
let (tx, rx) = watch::channel(false);
Self { tx, rx }
}
}
impl NotifyOnce {
fn notify_waiters(&self) {
self.tx.send_replace(true);
}
fn is_notified(&self) -> bool {
*self.rx.borrow()
}
async fn notified(&self) {
let mut rx = self.rx.clone();
loop {
if *rx.borrow_and_update() {
return;
}
if rx.changed().await.is_err() {
return;
}
}
}
}
#[cfg(test)]
#[path = "../tests/gate/unit.rs"]
mod tests;