use crate::client::{Client, ClientConfig};
use crate::host_identity::HostExpectation;
use crate::private::PrivateChild;
use rightkit_child_process::{
Generation, HealthOutcome, HealthPolicy, HealthProbe, HealthTracker, RestartOutcome,
RestartPermit, RestartPolicy, RestartTracker,
};
use serde_json::json;
use std::io;
use std::sync::mpsc::{self, RecvTimeoutError};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_secs(1);
pub const DEFAULT_STOP_GRACE: Duration = Duration::from_secs(5);
const MIN_POLL: Duration = Duration::from_millis(10);
#[derive(Debug, Clone, Copy)]
pub struct SupervisorConfig {
restart: RestartPolicy,
health: HealthPolicy,
poll_interval: Duration,
stop_grace: Duration,
}
impl SupervisorConfig {
pub fn new(restart: RestartPolicy, health: HealthPolicy) -> Self {
Self {
restart,
health,
poll_interval: DEFAULT_POLL_INTERVAL,
stop_grace: DEFAULT_STOP_GRACE,
}
}
#[must_use]
pub fn poll_interval(mut self, interval: Duration) -> Self {
self.poll_interval = interval.max(MIN_POLL);
self
}
#[must_use]
pub fn stop_grace(mut self, grace: Duration) -> Self {
self.stop_grace = grace;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SupervisorState {
Starting,
Ready,
WaitingRestart,
AwaitingDemand,
Exhausted,
Stopped,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SupervisorStatus {
pub state: SupervisorState,
pub launches: u64,
pub pid: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SupervisorEvent {
Started {
launch: u64,
pid: u32,
},
Ready {
launch: u64,
},
Exited {
launch: u64,
code: Option<i32>,
success: bool,
},
Unhealthy {
launch: u64,
consecutive_failures: u32,
},
StartupTimedOut {
launch: u64,
},
LaunchFailed {
error: String,
},
RestartScheduled {
delay: Duration,
},
AwaitingDemand,
Exhausted,
Stopped,
}
pub type SupervisorEventFn = Arc<dyn Fn(&SupervisorEvent) + Send + Sync>;
enum Cmd {
Stop,
RestartNow,
}
pub struct SupervisorHandle {
tx: mpsc::Sender<Cmd>,
status: Arc<Mutex<SupervisorStatus>>,
thread: Option<std::thread::JoinHandle<()>>,
}
impl SupervisorHandle {
pub fn status(&self) -> SupervisorStatus {
*self.status.lock().unwrap_or_else(|e| e.into_inner())
}
pub fn restart_now(&self) {
let _ = self.tx.send(Cmd::RestartNow);
}
pub fn stop(mut self) {
self.stop_inner();
}
fn stop_inner(&mut self) {
let _ = self.tx.send(Cmd::Stop);
if let Some(thread) = self.thread.take() {
let _ = thread.join();
}
}
}
impl Drop for SupervisorHandle {
fn drop(&mut self) {
self.stop_inner();
}
}
pub fn supervise<L, P>(
config: SupervisorConfig,
launch: L,
probe: P,
on_event: Option<SupervisorEventFn>,
) -> io::Result<SupervisorHandle>
where
L: FnMut() -> io::Result<PrivateChild> + Send + 'static,
P: FnMut(&PrivateChild) -> bool + Send + 'static,
{
let (tx, rx) = mpsc::channel();
let status = Arc::new(Mutex::new(SupervisorStatus {
state: SupervisorState::Starting,
launches: 0,
pid: None,
}));
let core = Core {
cfg: config,
launch,
probe,
tracker: RestartTracker::new(config.restart),
origin: Instant::now(),
launches: 0,
status: status.clone(),
on_event,
phase: Phase::Idle(SupervisorState::Stopped),
};
let thread = std::thread::Builder::new()
.name("rk-supervisor".into())
.spawn(move || core.run(rx))?;
Ok(SupervisorHandle {
tx,
status,
thread: Some(thread),
})
}
pub fn ping_probe(
config: ClientConfig,
expect: Option<HostExpectation>,
timeout: Duration,
) -> impl FnMut(&PrivateChild) -> bool + Send + 'static {
move |child| {
let config = config
.clone()
.secret(child.secret().clone())
.deadline(Some(timeout));
let client = match &expect {
Some(expect) => Client::connect_expecting(&config, expect).ok(),
None => Client::connect_config(&config).ok(),
};
match client {
Some(client) => matches!(
client.call("ping", json!({})),
Ok(value) if value["pong"] == true
),
None => false,
}
}
}
struct Running {
child: PrivateChild,
generation: Generation,
health: HealthTracker,
ready: bool,
launch: u64,
}
enum Phase {
Running(Box<Running>),
Waiting(RestartPermit, Instant),
Idle(SupervisorState),
}
struct Core<L, P> {
cfg: SupervisorConfig,
launch: L,
probe: P,
tracker: RestartTracker,
origin: Instant,
launches: u64,
status: Arc<Mutex<SupervisorStatus>>,
on_event: Option<SupervisorEventFn>,
phase: Phase,
}
impl<L, P> Core<L, P>
where
L: FnMut() -> io::Result<PrivateChild>,
P: FnMut(&PrivateChild) -> bool,
{
fn at(&self) -> Duration {
self.origin.elapsed()
}
fn emit(&self, event: SupervisorEvent) {
if let Some(f) = &self.on_event {
f(&event);
}
}
fn set_status(&self, state: SupervisorState, pid: Option<u32>) {
let mut s = self.status.lock().unwrap_or_else(|e| e.into_inner());
s.state = state;
s.launches = self.launches;
s.pid = pid;
}
fn run(mut self, rx: mpsc::Receiver<Cmd>) {
self.start();
loop {
let wait = match &self.phase {
Phase::Waiting(_, due) => due
.saturating_duration_since(Instant::now())
.min(self.cfg.poll_interval),
_ => self.cfg.poll_interval,
};
match rx.recv_timeout(wait) {
Ok(Cmd::RestartNow) => self.restart_now(),
Ok(Cmd::Stop) | Err(RecvTimeoutError::Disconnected) => {
self.shutdown();
return;
}
Err(RecvTimeoutError::Timeout) => self.tick(),
}
}
}
fn start(&mut self) {
match (self.launch)() {
Ok(child) => {
let at = self.at();
let generation = self.tracker.started(at);
self.launches += 1;
let pid = child.child().id();
self.set_status(SupervisorState::Starting, Some(pid));
self.emit(SupervisorEvent::Started {
launch: self.launches,
pid,
});
self.phase = Phase::Running(Box::new(Running {
child,
generation,
health: HealthTracker::new(self.cfg.health, at),
ready: false,
launch: self.launches,
}));
}
Err(error) => {
self.emit(SupervisorEvent::LaunchFailed {
error: error.to_string(),
});
let outcome = self.tracker.request_restart(self.at());
self.apply(outcome);
}
}
}
fn apply(&mut self, outcome: RestartOutcome) {
match outcome {
RestartOutcome::Restart(permit) => {
let delay = permit.delay();
self.emit(SupervisorEvent::RestartScheduled { delay });
self.set_status(SupervisorState::WaitingRestart, None);
self.phase = Phase::Waiting(permit, Instant::now() + delay);
}
RestartOutcome::AwaitingDemand => {
self.emit(SupervisorEvent::AwaitingDemand);
self.set_status(SupervisorState::AwaitingDemand, None);
self.phase = Phase::Idle(SupervisorState::AwaitingDemand);
}
RestartOutcome::Exhausted => {
self.emit(SupervisorEvent::Exhausted);
self.set_status(SupervisorState::Exhausted, None);
self.phase = Phase::Idle(SupervisorState::Exhausted);
}
RestartOutcome::Stopped | RestartOutcome::IgnoredStale => {
self.emit(SupervisorEvent::Stopped);
self.set_status(SupervisorState::Stopped, None);
self.phase = Phase::Idle(SupervisorState::Stopped);
}
}
}
fn tick(&mut self) {
let placeholder = Phase::Idle(SupervisorState::Stopped);
match std::mem::replace(&mut self.phase, placeholder) {
Phase::Running(running) => self.tick_running(*running),
Phase::Waiting(permit, due) => {
if Instant::now() >= due && self.tracker.permit_valid(&permit, self.at()) {
self.start();
} else {
self.phase = Phase::Waiting(permit, due);
}
}
idle @ Phase::Idle(_) => self.phase = idle,
}
}
fn tick_running(&mut self, mut running: Running) {
match running.child.child_mut().try_wait() {
Ok(Some(status)) => {
self.emit(SupervisorEvent::Exited {
launch: running.launch,
code: status.code(),
success: status.success(),
});
drop(running.child);
let outcome = self.tracker.exited(running.generation, self.at());
self.apply(outcome);
return;
}
Ok(None) => {}
Err(_) => {
self.stop_child(running.child);
let outcome = self.tracker.exited(running.generation, self.at());
self.apply(outcome);
return;
}
}
let answered = (self.probe)(&running.child);
let at = self.at();
let outcome = running.health.observe(
at,
if answered {
HealthProbe::Success
} else {
HealthProbe::Failure
},
);
if answered && !running.ready {
running.ready = true;
self.tracker.ready(running.generation);
self.set_status(SupervisorState::Ready, Some(running.child.child().id()));
self.emit(SupervisorEvent::Ready {
launch: running.launch,
});
}
let failure = match outcome {
HealthOutcome::StartupTimedOut => Some(SupervisorEvent::StartupTimedOut {
launch: running.launch,
}),
HealthOutcome::Unhealthy {
consecutive_failures,
} if running.ready => Some(SupervisorEvent::Unhealthy {
launch: running.launch,
consecutive_failures,
}),
_ => None,
};
match failure {
None => self.phase = Phase::Running(Box::new(running)),
Some(event) => {
self.emit(event);
self.stop_child(running.child);
let outcome = self.tracker.exited(running.generation, self.at());
self.apply(outcome);
}
}
}
fn stop_child(&self, mut child: PrivateChild) {
child.close_stdin();
let _ = child.child_mut().wait_then_kill(self.cfg.stop_grace);
}
fn restart_now(&mut self) {
let placeholder = Phase::Idle(SupervisorState::Stopped);
match std::mem::replace(&mut self.phase, placeholder) {
Phase::Running(running) => {
self.stop_child(running.child);
let at = self.at();
let mut outcome = self.tracker.exited(running.generation, at);
if outcome == RestartOutcome::AwaitingDemand {
outcome = self.tracker.request_restart(at);
}
self.apply(outcome);
}
waiting @ Phase::Waiting(..) => self.phase = waiting,
Phase::Idle(SupervisorState::AwaitingDemand) => {
match self.tracker.request_restart(self.at()) {
RestartOutcome::AwaitingDemand => self.start(),
other => self.apply(other),
}
}
Phase::Idle(_) => {
self.tracker.reset_budget();
self.start();
}
}
}
fn shutdown(&mut self) {
self.tracker.shutdown();
if let Phase::Running(running) =
std::mem::replace(&mut self.phase, Phase::Idle(SupervisorState::Stopped))
{
self.stop_child(running.child);
}
self.set_status(SupervisorState::Stopped, None);
self.emit(SupervisorEvent::Stopped);
}
}