use std::time::{Duration, Instant};
pub(crate) const FIRST_DELAY: Duration = Duration::from_secs(1);
pub(crate) const MAX_DELAY: Duration = Duration::from_secs(30);
pub(crate) const HEALTHY_AFTER: Duration = Duration::from_secs(60);
pub(crate) const CRASHES_BEFORE_FALLBACK: usize = 3;
pub(crate) const EXIT_NO_COMPOSITOR: i32 = 2;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Exit {
Clean,
NoCompositor,
Crashed,
}
impl Exit {
pub(crate) fn from_code(code: Option<i32>) -> Exit {
match code {
Some(0) => Exit::Clean,
Some(EXIT_NO_COMPOSITOR) => Exit::NoCompositor,
_ => Exit::Crashed,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Binary {
Build,
LastGood,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Next {
Stop,
Restart { after: Duration, binary: Binary },
}
#[derive(Debug)]
pub(crate) struct Policy {
pub(crate) binary: Binary,
build_started: Option<Instant>,
crashes: usize,
delay: Duration,
}
impl Policy {
pub(crate) fn new(binary: Binary) -> Policy {
Policy {
binary,
build_started: None,
crashes: 0,
delay: FIRST_DELAY,
}
}
pub(crate) fn new_build(&mut self) {
*self = Policy::new(Binary::Build);
}
pub(crate) fn started(&mut self, now: Instant) {
if self.binary == Binary::Build && self.build_started.is_none() {
self.build_started = Some(now);
}
}
pub(crate) fn healthy(&mut self) {
self.delay = FIRST_DELAY;
}
pub(crate) fn exited(&mut self, now: Instant, exit: Exit, has_last_good: bool) -> Next {
if exit != Exit::Crashed {
return Next::Stop;
}
let after = self.delay;
self.delay = (self.delay * 2).min(MAX_DELAY);
if self.binary == Binary::Build
&& self
.build_started
.is_some_and(|started| now.duration_since(started) <= HEALTHY_AFTER)
{
self.crashes += 1;
if self.crashes >= CRASHES_BEFORE_FALLBACK && has_last_good {
self.binary = Binary::LastGood;
self.delay = FIRST_DELAY;
return Next::Restart {
after: FIRST_DELAY,
binary: Binary::LastGood,
};
}
}
Next::Restart {
after,
binary: self.binary,
}
}
}