use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
pub const DEFAULT_QUIT_DRAIN_BOUND: Duration = Duration::from_secs(15);
pub const MAX_QUIT_DRAIN_BOUND: Duration = Duration::from_secs(120);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QuitStep {
ExitNow,
Drain,
AlreadyDraining,
}
#[derive(Debug)]
pub struct QuitGate {
bound: Option<Duration>,
draining: AtomicBool,
epoch: AtomicU64,
}
impl QuitGate {
pub fn new(bound: Option<Duration>) -> Self {
Self {
bound: bound.map(|b| b.min(MAX_QUIT_DRAIN_BOUND)),
draining: AtomicBool::new(false),
epoch: AtomicU64::new(0),
}
}
pub fn enabled(&self) -> bool {
self.bound.is_some()
}
pub fn bound(&self) -> Option<Duration> {
self.bound
}
pub fn draining(&self) -> bool {
self.draining.load(Ordering::SeqCst)
}
pub fn request(&self, exiting: &AtomicBool, renderer_alive: bool) -> QuitStep {
if exiting.load(Ordering::SeqCst) || !self.enabled() || !renderer_alive {
return QuitStep::ExitNow;
}
if self.draining.swap(true, Ordering::SeqCst) {
QuitStep::AlreadyDraining
} else {
QuitStep::Drain
}
}
pub fn finish(&self) {
self.epoch.fetch_add(1, Ordering::SeqCst);
self.draining.store(false, Ordering::SeqCst);
}
pub fn arm_bound(self: &Arc<Self>, on_expiry: impl FnOnce() + Send + 'static) {
let Some(bound) = self.bound else {
return;
};
let epoch = self.epoch.fetch_add(1, Ordering::SeqCst) + 1;
let gate = Arc::clone(self);
let spawned = std::thread::Builder::new()
.name("rk-shell-quit-bound".into())
.spawn(move || {
std::thread::sleep(bound);
if gate.epoch.load(Ordering::SeqCst) == epoch && gate.draining() {
gate.draining.store(false, Ordering::SeqCst);
on_expiry();
}
});
if spawned.is_err() {
self.draining.store(false, Ordering::SeqCst);
}
}
}