use std::process::{Child, ExitStatus};
use std::time::Duration;
use kernel::AxError;
use super::tail::Tail;
use super::{BacklogId, Body, Claim, Member};
const READ_BYTES_PER_MS: u64 = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PollBudget {
polls: u32,
interval_ms: u64,
}
impl PollBudget {
pub const DEFAULT: PollBudget = PollBudget {
polls: 500,
interval_ms: 20,
};
#[must_use]
pub fn new(polls: u32, interval_ms: u64) -> PollBudget {
PollBudget { polls, interval_ms }
}
pub(super) fn polls(self) -> u32 {
self.polls
}
pub(super) fn interval(self) -> Duration {
Duration::from_millis(self.interval_ms)
}
pub(super) fn read_per_poll(self) -> usize {
usize::try_from(self.interval_ms.saturating_mul(READ_BYTES_PER_MS)).unwrap_or(usize::MAX)
}
}
impl Default for PollBudget {
fn default() -> PollBudget {
PollBudget::DEFAULT
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Exit {
Ended { code: i32 },
Signalled,
Unknown { why: Unseen },
}
impl Exit {
pub(super) fn of(status: &ExitStatus) -> Exit {
match status.code() {
Some(code) => Exit::Ended { code },
None => Exit::Signalled,
}
}
pub(super) fn polled(child: &mut Child) -> Option<Exit> {
match child.try_wait() {
Ok(Some(status)) => Some(Exit::of(&status)),
Ok(None) => None,
Err(_) => Some(Exit::Unknown {
why: Unseen::WaitRefused,
}),
}
}
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Exit::Ended { .. } => "ended",
Exit::Signalled => "signalled",
Exit::Unknown { .. } => "unknown",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Unseen {
LeftTheTable,
WaitRefused,
}
impl Unseen {
#[must_use]
pub fn sentence(self) -> &'static str {
match self {
Unseen::LeftTheTable => {
"this command left the table before anything waited on it, so its \
ending was never read"
}
Unseen::WaitRefused => {
"this machine refused to report the child's state, so its ending is \
not known"
}
}
}
}
impl super::Backlog {
pub(super) fn settle(&self, id: BacklogId) -> Result<Option<Exit>, AxError> {
let mut table = self.hold()?;
let Some(Member {
body: Body::Command { child, .. },
..
}) = table.members.get_mut(&id)
else {
return Ok(Some(Exit::Unknown {
why: Unseen::LeftTheTable,
}));
};
let stopped = Exit::polled(child);
if stopped.is_some() {
table.members.remove(&id);
}
Ok(stopped)
}
pub(super) fn hand_over(&self, id: BacklogId, read: Tail) -> Result<(), AxError> {
let mut table = self.hold()?;
if let Some(Member {
body: Body::Command { claim, tail, .. },
..
}) = table.members.get_mut(&id)
&& let Claim::Window(owner) = *claim
{
*claim = Claim::Run(owner);
*tail = read;
}
Ok(())
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, reason = "test code")]
mod tests {
use super::*;
#[test]
fn every_ending_says_which_of_the_three_it_is() {
assert_eq!(Exit::Ended { code: -1 }.as_str(), "ended");
assert_eq!(Exit::Signalled.as_str(), "signalled");
assert_eq!(
Exit::Unknown {
why: Unseen::WaitRefused
}
.as_str(),
"unknown"
);
assert_ne!(
Exit::Ended { code: -1 },
Exit::Signalled,
"a program that returns minus one has not been signalled"
);
}
#[test]
fn the_default_window_is_ten_seconds_of_short_polls() {
let budget = PollBudget::DEFAULT;
assert_eq!(budget.polls(), 500);
assert_eq!(budget.interval(), Duration::from_millis(20));
}
}