use std::time::Duration;
use tokio::sync::watch;
use tokio::time::Instant;
pub(super) const INPUT_STALL_TIMEOUT: Duration = Duration::from_secs(60);
pub(super) struct InputStall<'a> {
state: &'a watch::Sender<bool>,
deadline: Instant,
paused_at: Option<Instant>,
}
impl<'a> InputStall<'a> {
pub(super) fn new(state: &'a watch::Sender<bool>, timeout: Duration) -> Self {
Self {
state,
deadline: Instant::now() + timeout,
paused_at: None,
}
}
pub(super) fn set_paused(&mut self, paused: bool) {
if paused {
self.paused_at.get_or_insert_with(Instant::now);
} else if let Some(started) = self.paused_at.take() {
self.deadline += started.elapsed();
}
}
pub(super) async fn watch(&self) {
if self.paused_at.is_none() && !*self.state.borrow() {
tokio::time::sleep_until(self.deadline).await;
self.state.send_replace(true);
tracing::warn!(
"agent relay: input stalled; rejecting new connections until delivery resumes"
);
}
std::future::pending::<()>().await;
}
}
impl Drop for InputStall<'_> {
fn drop(&mut self) {
self.state.send_if_modified(|stalled| {
if *stalled {
*stalled = false;
tracing::info!("agent relay: input resumed; reopening client admission");
true
} else {
false
}
});
}
}