1use crate::client::{Client, ClientConfig};
35use crate::host_identity::HostExpectation;
36use crate::private::PrivateChild;
37use rightkit_child_process::{
38 Generation, HealthOutcome, HealthPolicy, HealthProbe, HealthTracker, RestartOutcome,
39 RestartPermit, RestartPolicy, RestartTracker,
40};
41use serde_json::json;
42use std::io;
43use std::sync::mpsc::{self, RecvTimeoutError};
44use std::sync::{Arc, Mutex};
45use std::time::{Duration, Instant};
46
47pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_secs(1);
49pub const DEFAULT_STOP_GRACE: Duration = Duration::from_secs(5);
51
52const MIN_POLL: Duration = Duration::from_millis(10);
53
54#[derive(Debug, Clone, Copy)]
56pub struct SupervisorConfig {
57 restart: RestartPolicy,
58 health: HealthPolicy,
59 poll_interval: Duration,
60 stop_grace: Duration,
61}
62
63impl SupervisorConfig {
64 pub fn new(restart: RestartPolicy, health: HealthPolicy) -> Self {
67 Self {
68 restart,
69 health,
70 poll_interval: DEFAULT_POLL_INTERVAL,
71 stop_grace: DEFAULT_STOP_GRACE,
72 }
73 }
74
75 #[must_use]
78 pub fn poll_interval(mut self, interval: Duration) -> Self {
79 self.poll_interval = interval.max(MIN_POLL);
80 self
81 }
82
83 #[must_use]
86 pub fn stop_grace(mut self, grace: Duration) -> Self {
87 self.stop_grace = grace;
88 self
89 }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum SupervisorState {
95 Starting,
97 Ready,
99 WaitingRestart,
101 AwaitingDemand,
103 Exhausted,
105 Stopped,
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub struct SupervisorStatus {
112 pub state: SupervisorState,
113 pub launches: u64,
115 pub pid: Option<u32>,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
121pub enum SupervisorEvent {
122 Started {
123 launch: u64,
124 pid: u32,
125 },
126 Ready {
128 launch: u64,
129 },
130 Exited {
132 launch: u64,
133 code: Option<i32>,
134 success: bool,
135 },
136 Unhealthy {
138 launch: u64,
139 consecutive_failures: u32,
140 },
141 StartupTimedOut {
143 launch: u64,
144 },
145 LaunchFailed {
147 error: String,
148 },
149 RestartScheduled {
150 delay: Duration,
151 },
152 AwaitingDemand,
153 Exhausted,
155 Stopped,
157}
158
159pub type SupervisorEventFn = Arc<dyn Fn(&SupervisorEvent) + Send + Sync>;
161
162enum Cmd {
163 Stop,
164 RestartNow,
165}
166
167pub struct SupervisorHandle {
169 tx: mpsc::Sender<Cmd>,
170 status: Arc<Mutex<SupervisorStatus>>,
171 thread: Option<std::thread::JoinHandle<()>>,
172}
173
174impl SupervisorHandle {
175 pub fn status(&self) -> SupervisorStatus {
177 *self.status.lock().unwrap_or_else(|e| e.into_inner())
178 }
179
180 pub fn restart_now(&self) {
185 let _ = self.tx.send(Cmd::RestartNow);
186 }
187
188 pub fn stop(mut self) {
191 self.stop_inner();
192 }
193
194 fn stop_inner(&mut self) {
195 let _ = self.tx.send(Cmd::Stop);
196 if let Some(thread) = self.thread.take() {
197 let _ = thread.join();
198 }
199 }
200}
201
202impl Drop for SupervisorHandle {
203 fn drop(&mut self) {
204 self.stop_inner();
205 }
206}
207
208pub fn supervise<L, P>(
213 config: SupervisorConfig,
214 launch: L,
215 probe: P,
216 on_event: Option<SupervisorEventFn>,
217) -> io::Result<SupervisorHandle>
218where
219 L: FnMut() -> io::Result<PrivateChild> + Send + 'static,
220 P: FnMut(&PrivateChild) -> bool + Send + 'static,
221{
222 let (tx, rx) = mpsc::channel();
223 let status = Arc::new(Mutex::new(SupervisorStatus {
224 state: SupervisorState::Starting,
225 launches: 0,
226 pid: None,
227 }));
228 let core = Core {
229 cfg: config,
230 launch,
231 probe,
232 tracker: RestartTracker::new(config.restart),
233 origin: Instant::now(),
234 launches: 0,
235 status: status.clone(),
236 on_event,
237 phase: Phase::Idle(SupervisorState::Stopped),
238 };
239 let thread = std::thread::Builder::new()
240 .name("rk-supervisor".into())
241 .spawn(move || core.run(rx))?;
242 Ok(SupervisorHandle {
243 tx,
244 status,
245 thread: Some(thread),
246 })
247}
248
249pub fn ping_probe(
254 config: ClientConfig,
255 expect: Option<HostExpectation>,
256 timeout: Duration,
257) -> impl FnMut(&PrivateChild) -> bool + Send + 'static {
258 move |child| {
259 let config = config
260 .clone()
261 .secret(child.secret().clone())
262 .deadline(Some(timeout));
263 let client = match &expect {
264 Some(expect) => Client::connect_expecting(&config, expect).ok(),
265 None => Client::connect_config(&config).ok(),
266 };
267 match client {
268 Some(client) => matches!(
269 client.call("ping", json!({})),
270 Ok(value) if value["pong"] == true
271 ),
272 None => false,
273 }
274 }
275}
276
277struct Running {
278 child: PrivateChild,
279 generation: Generation,
280 health: HealthTracker,
281 ready: bool,
282 launch: u64,
283}
284
285enum Phase {
286 Running(Box<Running>),
287 Waiting(RestartPermit, Instant),
289 Idle(SupervisorState),
291}
292
293struct Core<L, P> {
294 cfg: SupervisorConfig,
295 launch: L,
296 probe: P,
297 tracker: RestartTracker,
298 origin: Instant,
299 launches: u64,
300 status: Arc<Mutex<SupervisorStatus>>,
301 on_event: Option<SupervisorEventFn>,
302 phase: Phase,
303}
304
305impl<L, P> Core<L, P>
306where
307 L: FnMut() -> io::Result<PrivateChild>,
308 P: FnMut(&PrivateChild) -> bool,
309{
310 fn at(&self) -> Duration {
311 self.origin.elapsed()
312 }
313
314 fn emit(&self, event: SupervisorEvent) {
315 if let Some(f) = &self.on_event {
316 f(&event);
317 }
318 }
319
320 fn set_status(&self, state: SupervisorState, pid: Option<u32>) {
321 let mut s = self.status.lock().unwrap_or_else(|e| e.into_inner());
322 s.state = state;
323 s.launches = self.launches;
324 s.pid = pid;
325 }
326
327 fn run(mut self, rx: mpsc::Receiver<Cmd>) {
328 self.start();
329 loop {
330 let wait = match &self.phase {
331 Phase::Waiting(_, due) => due
332 .saturating_duration_since(Instant::now())
333 .min(self.cfg.poll_interval),
334 _ => self.cfg.poll_interval,
335 };
336 match rx.recv_timeout(wait) {
337 Ok(Cmd::RestartNow) => self.restart_now(),
338 Ok(Cmd::Stop) | Err(RecvTimeoutError::Disconnected) => {
339 self.shutdown();
340 return;
341 }
342 Err(RecvTimeoutError::Timeout) => self.tick(),
343 }
344 }
345 }
346
347 fn start(&mut self) {
349 match (self.launch)() {
350 Ok(child) => {
351 let at = self.at();
352 let generation = self.tracker.started(at);
353 self.launches += 1;
354 let pid = child.child().id();
355 self.set_status(SupervisorState::Starting, Some(pid));
356 self.emit(SupervisorEvent::Started {
357 launch: self.launches,
358 pid,
359 });
360 self.phase = Phase::Running(Box::new(Running {
361 child,
362 generation,
363 health: HealthTracker::new(self.cfg.health, at),
364 ready: false,
365 launch: self.launches,
366 }));
367 }
368 Err(error) => {
369 self.emit(SupervisorEvent::LaunchFailed {
370 error: error.to_string(),
371 });
372 let outcome = self.tracker.request_restart(self.at());
373 self.apply(outcome);
374 }
375 }
376 }
377
378 fn apply(&mut self, outcome: RestartOutcome) {
379 match outcome {
380 RestartOutcome::Restart(permit) => {
381 let delay = permit.delay();
382 self.emit(SupervisorEvent::RestartScheduled { delay });
383 self.set_status(SupervisorState::WaitingRestart, None);
384 self.phase = Phase::Waiting(permit, Instant::now() + delay);
385 }
386 RestartOutcome::AwaitingDemand => {
387 self.emit(SupervisorEvent::AwaitingDemand);
388 self.set_status(SupervisorState::AwaitingDemand, None);
389 self.phase = Phase::Idle(SupervisorState::AwaitingDemand);
390 }
391 RestartOutcome::Exhausted => {
392 self.emit(SupervisorEvent::Exhausted);
393 self.set_status(SupervisorState::Exhausted, None);
394 self.phase = Phase::Idle(SupervisorState::Exhausted);
395 }
396 RestartOutcome::Stopped | RestartOutcome::IgnoredStale => {
399 self.emit(SupervisorEvent::Stopped);
400 self.set_status(SupervisorState::Stopped, None);
401 self.phase = Phase::Idle(SupervisorState::Stopped);
402 }
403 }
404 }
405
406 fn tick(&mut self) {
407 let placeholder = Phase::Idle(SupervisorState::Stopped);
408 match std::mem::replace(&mut self.phase, placeholder) {
409 Phase::Running(running) => self.tick_running(*running),
410 Phase::Waiting(permit, due) => {
411 if Instant::now() >= due && self.tracker.permit_valid(&permit, self.at()) {
412 self.start();
413 } else {
414 self.phase = Phase::Waiting(permit, due);
415 }
416 }
417 idle @ Phase::Idle(_) => self.phase = idle,
418 }
419 }
420
421 fn tick_running(&mut self, mut running: Running) {
422 match running.child.child_mut().try_wait() {
423 Ok(Some(status)) => {
424 self.emit(SupervisorEvent::Exited {
425 launch: running.launch,
426 code: status.code(),
427 success: status.success(),
428 });
429 drop(running.child);
431 let outcome = self.tracker.exited(running.generation, self.at());
432 self.apply(outcome);
433 return;
434 }
435 Ok(None) => {}
436 Err(_) => {
437 self.stop_child(running.child);
439 let outcome = self.tracker.exited(running.generation, self.at());
440 self.apply(outcome);
441 return;
442 }
443 }
444 let answered = (self.probe)(&running.child);
445 let at = self.at();
446 let outcome = running.health.observe(
447 at,
448 if answered {
449 HealthProbe::Success
450 } else {
451 HealthProbe::Failure
452 },
453 );
454 if answered && !running.ready {
455 running.ready = true;
456 self.tracker.ready(running.generation);
457 self.set_status(SupervisorState::Ready, Some(running.child.child().id()));
458 self.emit(SupervisorEvent::Ready {
459 launch: running.launch,
460 });
461 }
462 let failure = match outcome {
463 HealthOutcome::StartupTimedOut => Some(SupervisorEvent::StartupTimedOut {
464 launch: running.launch,
465 }),
466 HealthOutcome::Unhealthy {
468 consecutive_failures,
469 } if running.ready => Some(SupervisorEvent::Unhealthy {
470 launch: running.launch,
471 consecutive_failures,
472 }),
473 _ => None,
474 };
475 match failure {
476 None => self.phase = Phase::Running(Box::new(running)),
477 Some(event) => {
478 self.emit(event);
479 self.stop_child(running.child);
480 let outcome = self.tracker.exited(running.generation, self.at());
481 self.apply(outcome);
482 }
483 }
484 }
485
486 fn stop_child(&self, mut child: PrivateChild) {
488 child.close_stdin();
489 let _ = child.child_mut().wait_then_kill(self.cfg.stop_grace);
490 }
491
492 fn restart_now(&mut self) {
493 let placeholder = Phase::Idle(SupervisorState::Stopped);
494 match std::mem::replace(&mut self.phase, placeholder) {
495 Phase::Running(running) => {
496 self.stop_child(running.child);
497 let at = self.at();
498 let mut outcome = self.tracker.exited(running.generation, at);
499 if outcome == RestartOutcome::AwaitingDemand {
500 outcome = self.tracker.request_restart(at);
502 }
503 self.apply(outcome);
504 }
505 waiting @ Phase::Waiting(..) => self.phase = waiting,
507 Phase::Idle(SupervisorState::AwaitingDemand) => {
508 match self.tracker.request_restart(self.at()) {
509 RestartOutcome::AwaitingDemand => self.start(),
511 other => self.apply(other),
512 }
513 }
514 Phase::Idle(_) => {
515 self.tracker.reset_budget();
516 self.start();
517 }
518 }
519 }
520
521 fn shutdown(&mut self) {
522 self.tracker.shutdown();
523 if let Phase::Running(running) =
524 std::mem::replace(&mut self.phase, Phase::Idle(SupervisorState::Stopped))
525 {
526 self.stop_child(running.child);
527 }
528 self.set_status(SupervisorState::Stopped, None);
529 self.emit(SupervisorEvent::Stopped);
530 }
531}