use std::sync::{Arc, Mutex};
use tokio::sync::watch;
use tokio::time::Instant;
#[derive(Clone, Copy, Default)]
struct State {
draining: bool,
active: usize,
drain_started: Option<Instant>,
}
pub struct Lifecycle {
state: Mutex<State>,
changes: watch::Sender<()>,
observations: Option<Arc<crate::observation::Observations>>,
}
impl Default for Lifecycle {
fn default() -> Self {
Self {
state: Mutex::new(State::default()),
changes: watch::channel(()).0,
observations: None,
}
}
}
impl Lifecycle {
pub fn observed(observations: Arc<crate::observation::Observations>) -> Self {
Self {
observations: Some(observations),
..Self::default()
}
}
pub fn snapshot(&self) -> LifecycleSnapshot {
let state = self.state.lock().unwrap_or_else(|e| e.into_inner());
LifecycleSnapshot {
active: state.active,
draining: state.draining,
draining_for: state.drain_started.map(|started| started.elapsed()),
}
}
pub(crate) fn record(&self, event: crate::observation::Event) {
if let Some(observations) = &self.observations {
observations.record(event);
}
}
pub fn begin(self: &Arc<Self>) -> Option<RequestGuard> {
let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
if state.draining {
return None;
}
state.active += 1;
Some(RequestGuard(Arc::clone(self)))
}
pub fn start_drain(&self) -> bool {
let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
if state.draining {
return false;
}
state.draining = true;
state.drain_started = Some(Instant::now());
self.record(crate::observation::Event::DrainStarted);
if state.active == 0 {
self.record(crate::observation::Event::DrainCompleted);
}
self.changes.send_replace(());
true
}
pub fn accepting_calls(&self) -> bool {
!self
.state
.lock()
.unwrap_or_else(|e| e.into_inner())
.draining
}
pub async fn draining(&self) {
let mut changes = self.changes.subscribe();
loop {
changes.borrow_and_update();
if self
.state
.lock()
.unwrap_or_else(|e| e.into_inner())
.draining
{
return;
}
changes.changed().await.expect("self keeps sender alive");
}
}
pub async fn unless_draining<T>(
&self,
operation: impl std::future::Future<Output = T>,
) -> Option<T> {
tokio::select! {
biased;
() = self.draining() => None,
value = operation => Some(value),
}
}
pub async fn drain(&self) {
self.start_drain();
let mut changes = self.changes.subscribe();
loop {
changes.borrow_and_update();
let active = self.state.lock().unwrap_or_else(|e| e.into_inner()).active;
if active == 0 {
return;
}
changes.changed().await.expect("self keeps sender alive");
}
}
pub async fn drain_until(&self, deadline: Instant) -> DrainOutcome {
if tokio::time::timeout_at(deadline, self.drain())
.await
.is_ok()
{
return DrainOutcome::Complete;
}
let active = self.state.lock().unwrap_or_else(|e| e.into_inner()).active;
if active == 0 {
DrainOutcome::Complete
} else {
self.record(crate::observation::Event::DrainTimedOut);
DrainOutcome::TimedOut { active }
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct LifecycleSnapshot {
pub active: usize,
pub draining: bool,
pub draining_for: Option<std::time::Duration>,
}
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DrainOutcome {
Complete,
TimedOut {
active: usize,
},
}
#[must_use = "keep the guard alive until the operation and its reply finish"]
pub struct RequestGuard(Arc<Lifecycle>);
impl Drop for RequestGuard {
fn drop(&mut self) {
let mut state = self.0.state.lock().unwrap_or_else(|e| e.into_inner());
state.active -= 1;
if state.draining && state.active == 0 {
self.0.record(crate::observation::Event::DrainCompleted);
self.0.changes.send_replace(());
}
}
}