#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ReactorPhase {
#[default]
Idle,
Draining,
Executing,
AwaitingIo,
Stable,
Error,
}
impl ReactorPhase {
pub fn as_str(self) -> &'static str {
match self {
Self::Idle => "idle",
Self::Draining => "draining",
Self::Executing => "executing",
Self::AwaitingIo => "awaiting_io",
Self::Stable => "stable",
Self::Error => "error",
}
}
}
#[derive(Debug, Clone)]
pub struct PhaseContext {
pub queue_empty: bool,
pub pending_io: usize,
pub steps: usize,
pub drained_any: bool,
}
impl ReactorPhase {
pub fn next(self, ctx: &PhaseContext) -> Self {
match self {
Self::Idle => Self::Draining,
Self::Draining => Self::next_after_draining(ctx),
Self::Executing => Self::next_after_executing(ctx),
Self::AwaitingIo | Self::Stable | Self::Error => Self::Idle,
}
}
fn next_after_draining(ctx: &PhaseContext) -> Self {
if ctx.pending_io > 0 {
Self::AwaitingIo
} else if !ctx.queue_empty {
Self::Executing
} else if ctx.steps > 0 {
Self::Stable
} else {
Self::Idle
}
}
fn next_after_executing(ctx: &PhaseContext) -> Self {
if ctx.pending_io > 0 {
Self::AwaitingIo
} else if ctx.queue_empty {
Self::post_execution(ctx)
} else {
Self::Executing
}
}
fn post_execution(ctx: &PhaseContext) -> Self {
if ctx.steps > 0 {
Self::Stable
} else {
Self::Idle
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ctx(queue_empty: bool, pending_io: usize, steps: usize, drained_any: bool) -> PhaseContext {
PhaseContext {
queue_empty,
pending_io,
steps,
drained_any,
}
}
#[test]
fn test_phase_idle_to_draining() {
let phase = ReactorPhase::Idle;
let c = ctx(true, 0, 0, false);
assert_eq!(phase.next(&c), ReactorPhase::Draining);
}
#[test]
fn test_phase_draining_to_executing_when_queue_nonempty() {
let phase = ReactorPhase::Draining;
let c = ctx(false, 0, 0, true);
assert_eq!(phase.next(&c), ReactorPhase::Executing);
}
#[test]
fn test_phase_draining_to_awaiting_io() {
let phase = ReactorPhase::Draining;
let c = ctx(true, 2, 5, true);
assert_eq!(phase.next(&c), ReactorPhase::AwaitingIo);
}
#[test]
fn test_phase_draining_to_stable() {
let phase = ReactorPhase::Draining;
let c = ctx(true, 0, 10, true);
assert_eq!(phase.next(&c), ReactorPhase::Stable);
}
#[test]
fn test_phase_draining_to_idle_when_no_work() {
let phase = ReactorPhase::Draining;
let c = ctx(true, 0, 0, false);
assert_eq!(phase.next(&c), ReactorPhase::Idle);
}
#[test]
fn test_phase_executing_to_awaiting_io() {
let phase = ReactorPhase::Executing;
let c = ctx(false, 1, 3, true);
assert_eq!(phase.next(&c), ReactorPhase::AwaitingIo);
}
#[test]
fn test_phase_executing_to_stable_when_queue_empty() {
let phase = ReactorPhase::Executing;
let c = ctx(true, 0, 5, true);
assert_eq!(phase.next(&c), ReactorPhase::Stable);
}
#[test]
fn test_phase_executing_continues_when_queue_nonempty() {
let phase = ReactorPhase::Executing;
let c = ctx(false, 0, 3, true);
assert_eq!(phase.next(&c), ReactorPhase::Executing);
}
#[test]
fn test_phase_awaiting_io_to_idle() {
let phase = ReactorPhase::AwaitingIo;
let c = ctx(false, 0, 5, true);
assert_eq!(phase.next(&c), ReactorPhase::Idle);
}
#[test]
fn test_phase_stable_to_idle() {
let phase = ReactorPhase::Stable;
let c = ctx(true, 0, 0, false);
assert_eq!(phase.next(&c), ReactorPhase::Idle);
}
#[test]
fn test_phase_error_to_idle() {
let phase = ReactorPhase::Error;
let c = ctx(true, 0, 0, false);
assert_eq!(phase.next(&c), ReactorPhase::Idle);
}
#[test]
fn test_phase_as_str() {
assert_eq!(ReactorPhase::Idle.as_str(), "idle");
assert_eq!(ReactorPhase::Draining.as_str(), "draining");
assert_eq!(ReactorPhase::Executing.as_str(), "executing");
assert_eq!(ReactorPhase::AwaitingIo.as_str(), "awaiting_io");
assert_eq!(ReactorPhase::Stable.as_str(), "stable");
assert_eq!(ReactorPhase::Error.as_str(), "error");
}
}