use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Termination {
Terminate,
Hangup,
}
impl Termination {
#[must_use]
pub const fn grace(self) -> Duration {
match self {
Self::Terminate => Duration::from_secs(5),
Self::Hangup => Duration::from_secs(3),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Ending {
pub(crate) cause: Termination,
pub(crate) deadline: Duration,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Step {
Ask(Termination),
Ignore,
End,
}
pub(crate) fn receive(ending: &mut Option<Ending>, cause: Termination, now: Duration) -> Step {
match (*ending, cause) {
(None, _) => {
*ending = Some(Ending { cause, deadline: now + cause.grace() });
Step::Ask(cause)
}
(Some(Ending { cause: Termination::Hangup, .. }), Termination::Hangup) => Step::Ignore,
(Some(current), Termination::Hangup) => {
let deadline = current.deadline.min(now + Termination::Hangup.grace());
*ending = Some(Ending { cause, deadline });
Step::Ask(cause)
}
(Some(_), Termination::Terminate) => Step::End,
}
}
#[cfg(test)]
mod tests {
use super::*;
const SECOND: Duration = Duration::from_secs(1);
#[test]
fn the_first_signal_asks_and_starts_its_grace() {
let mut ending = None;
assert_eq!(receive(&mut ending, Termination::Terminate, SECOND), Step::Ask(Termination::Terminate));
assert_eq!(ending, Some(Ending { cause: Termination::Terminate, deadline: SECOND * 6 }));
let mut ending = None;
assert_eq!(receive(&mut ending, Termination::Hangup, SECOND), Step::Ask(Termination::Hangup));
assert_eq!(ending, Some(Ending { cause: Termination::Hangup, deadline: SECOND * 4 }));
}
#[test]
fn a_second_terminate_ends_whatever_came_first() {
for first in [Termination::Terminate, Termination::Hangup] {
let mut ending = None;
receive(&mut ending, first, Duration::ZERO);
assert_eq!(receive(&mut ending, Termination::Terminate, SECOND), Step::End, "after {first:?}");
}
}
#[test]
fn a_repeated_hangup_changes_nothing() {
let mut ending = None;
receive(&mut ending, Termination::Hangup, Duration::ZERO);
let before = ending;
assert_eq!(receive(&mut ending, Termination::Hangup, SECOND), Step::Ignore);
assert_eq!(ending, before, "the deadline stays where the first hangup put it");
}
#[test]
fn a_hangup_during_a_terminate_asks_again_with_the_shorter_grace() {
let mut ending = None;
receive(&mut ending, Termination::Terminate, Duration::ZERO);
assert_eq!(receive(&mut ending, Termination::Hangup, SECOND), Step::Ask(Termination::Hangup));
assert_eq!(ending, Some(Ending { cause: Termination::Hangup, deadline: SECOND * 4 }));
let mut ending = None;
receive(&mut ending, Termination::Terminate, Duration::ZERO);
receive(&mut ending, Termination::Hangup, SECOND * 4);
assert_eq!(ending.map(|ending| ending.deadline), Some(SECOND * 5));
}
}