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
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub enum LockState {
169 Locked,
170 Unlocked,
171 Unknown,
172}
173
174impl LockState {
175 pub fn starts_locked(self) -> bool {
178 !matches!(self, LockState::Unlocked)
179 }
180}
181
182pub type LockStateFn = Arc<dyn Fn() -> LockState + Send + Sync>;
184
185enum Cmd {
186 Stop,
187 RestartNow(Option<LockState>),
188 ReturnChild(mpsc::Sender<Option<PrivateChild>>),
189}
190
191pub struct SupervisorHandle {
193 tx: mpsc::Sender<Cmd>,
194 status: Arc<Mutex<SupervisorStatus>>,
195 thread: Option<std::thread::JoinHandle<()>>,
196}
197
198impl SupervisorHandle {
199 pub fn status(&self) -> SupervisorStatus {
201 *self.status.lock().unwrap_or_else(|e| e.into_inner())
202 }
203
204 pub fn restart_now(&self) {
209 let _ = self.tx.send(Cmd::RestartNow(None));
210 }
211
212 pub fn restart_now_with_lock(&self, state: LockState) {
216 let _ = self.tx.send(Cmd::RestartNow(Some(state)));
217 }
218
219 pub fn stop(mut self) {
222 self.stop_inner();
223 }
224
225 pub fn stop_returning_child(mut self) -> Option<PrivateChild> {
230 let (tx, rx) = mpsc::channel();
231 if self.tx.send(Cmd::ReturnChild(tx)).is_err() {
232 return None;
233 }
234 if let Some(thread) = self.thread.take() {
235 let _ = thread.join();
236 }
237 rx.recv().ok().flatten()
238 }
239
240 fn stop_inner(&mut self) {
241 let _ = self.tx.send(Cmd::Stop);
242 if let Some(thread) = self.thread.take() {
243 let _ = thread.join();
244 }
245 }
246}
247
248impl Drop for SupervisorHandle {
249 fn drop(&mut self) {
250 self.stop_inner();
251 }
252}
253
254pub fn supervise<L, P>(
259 config: SupervisorConfig,
260 mut launch: L,
261 probe: P,
262 on_event: Option<SupervisorEventFn>,
263) -> io::Result<SupervisorHandle>
264where
265 L: FnMut() -> io::Result<PrivateChild> + Send + 'static,
266 P: FnMut(&PrivateChild) -> bool + Send + 'static,
267{
268 spawn_core(config, move |_locked| launch(), probe, None, None, on_event)
269}
270
271pub fn supervise_with_lock_state<L, P>(
278 config: SupervisorConfig,
279 launch: L,
280 probe: P,
281 lock_state: Option<LockStateFn>,
282 on_event: Option<SupervisorEventFn>,
283) -> io::Result<SupervisorHandle>
284where
285 L: FnMut(bool) -> io::Result<PrivateChild> + Send + 'static,
286 P: FnMut(&PrivateChild) -> bool + Send + 'static,
287{
288 spawn_core(config, launch, probe, None, lock_state, on_event)
289}
290
291pub fn supervise_adopted<L, P>(
297 config: SupervisorConfig,
298 child: PrivateChild,
299 launch: L,
300 probe: P,
301 lock_state: Option<LockStateFn>,
302 on_event: Option<SupervisorEventFn>,
303) -> io::Result<SupervisorHandle>
304where
305 L: FnMut(bool) -> io::Result<PrivateChild> + Send + 'static,
306 P: FnMut(&PrivateChild) -> bool + Send + 'static,
307{
308 spawn_core(config, launch, probe, Some(child), lock_state, on_event)
309}
310
311fn spawn_core<L, P>(
312 config: SupervisorConfig,
313 launch: L,
314 probe: P,
315 adopted: Option<PrivateChild>,
316 lock_state: Option<LockStateFn>,
317 on_event: Option<SupervisorEventFn>,
318) -> io::Result<SupervisorHandle>
319where
320 L: FnMut(bool) -> io::Result<PrivateChild> + Send + 'static,
321 P: FnMut(&PrivateChild) -> bool + Send + 'static,
322{
323 let (tx, rx) = mpsc::channel();
324 let status = Arc::new(Mutex::new(SupervisorStatus {
325 state: SupervisorState::Starting,
326 launches: 0,
327 pid: None,
328 }));
329 let core = Core {
330 cfg: config,
331 launch,
332 probe,
333 tracker: RestartTracker::new(config.restart),
334 origin: Instant::now(),
335 launches: 0,
336 status: status.clone(),
337 on_event,
338 phase: Phase::Idle(SupervisorState::Stopped),
339 adopted,
340 lock_state,
341 pending_locked: None,
342 };
343 let thread = std::thread::Builder::new()
344 .name("rk-supervisor".into())
345 .spawn(move || core.run(rx))?;
346 Ok(SupervisorHandle {
347 tx,
348 status,
349 thread: Some(thread),
350 })
351}
352
353pub fn ping_probe(
358 config: ClientConfig,
359 expect: Option<HostExpectation>,
360 timeout: Duration,
361) -> impl FnMut(&PrivateChild) -> bool + Send + 'static {
362 move |child| {
363 let config = config
364 .clone()
365 .secret(child.secret().clone())
366 .deadline(Some(timeout));
367 let client = match &expect {
368 Some(expect) => Client::connect_expecting(&config, expect).ok(),
369 None => Client::connect_config(&config).ok(),
370 };
371 match client {
372 Some(client) => matches!(
373 client.call("ping", json!({})),
374 Ok(value) if value["pong"] == true
375 ),
376 None => false,
377 }
378 }
379}
380
381struct Running {
382 child: PrivateChild,
383 generation: Generation,
384 health: HealthTracker,
385 ready: bool,
386 launch: u64,
387}
388
389enum Phase {
390 Running(Box<Running>),
391 Waiting(RestartPermit, Instant),
393 Idle(SupervisorState),
395}
396
397struct Core<L, P> {
398 cfg: SupervisorConfig,
399 launch: L,
400 probe: P,
401 tracker: RestartTracker,
402 origin: Instant,
403 launches: u64,
404 status: Arc<Mutex<SupervisorStatus>>,
405 on_event: Option<SupervisorEventFn>,
406 phase: Phase,
407 adopted: Option<PrivateChild>,
408 lock_state: Option<LockStateFn>,
409 pending_locked: Option<bool>,
411}
412
413impl<L, P> Core<L, P>
414where
415 L: FnMut(bool) -> io::Result<PrivateChild>,
416 P: FnMut(&PrivateChild) -> bool,
417{
418 fn at(&self) -> Duration {
419 self.origin.elapsed()
420 }
421
422 fn emit(&self, event: SupervisorEvent) {
423 if let Some(f) = &self.on_event {
424 f(&event);
425 }
426 }
427
428 fn set_status(&self, state: SupervisorState, pid: Option<u32>) {
429 let mut s = self.status.lock().unwrap_or_else(|e| e.into_inner());
430 s.state = state;
431 s.launches = self.launches;
432 s.pid = pid;
433 }
434
435 fn run(mut self, rx: mpsc::Receiver<Cmd>) {
436 match self.adopted.take() {
437 Some(child) => self.adopt(child),
438 None => self.start(),
439 }
440 loop {
441 let wait = match &self.phase {
442 Phase::Waiting(_, due) => due
443 .saturating_duration_since(Instant::now())
444 .min(self.cfg.poll_interval),
445 _ => self.cfg.poll_interval,
446 };
447 match rx.recv_timeout(wait) {
448 Ok(Cmd::RestartNow(state)) => self.restart_now(state),
449 Ok(Cmd::ReturnChild(reply)) => {
450 self.return_child(reply);
451 return;
452 }
453 Ok(Cmd::Stop) | Err(RecvTimeoutError::Disconnected) => {
454 self.shutdown();
455 return;
456 }
457 Err(RecvTimeoutError::Timeout) => self.tick(),
458 }
459 }
460 }
461
462 fn adopt(&mut self, child: PrivateChild) {
465 let at = self.at();
466 let generation = self.tracker.started(at);
467 self.tracker.ready(generation);
468 self.launches += 1;
469 let pid = child.child().id();
470 let mut health = HealthTracker::new(self.cfg.health, at);
471 let _ = health.observe(at, HealthProbe::Success);
472 self.set_status(SupervisorState::Ready, Some(pid));
473 self.emit(SupervisorEvent::Started {
474 launch: self.launches,
475 pid,
476 });
477 self.emit(SupervisorEvent::Ready {
478 launch: self.launches,
479 });
480 self.phase = Phase::Running(Box::new(Running {
481 child,
482 generation,
483 health,
484 ready: true,
485 launch: self.launches,
486 }));
487 }
488
489 fn next_locked(&mut self) -> bool {
492 if let Some(locked) = self.pending_locked.take() {
493 return locked;
494 }
495 match &self.lock_state {
496 Some(f) => f().starts_locked(),
497 None => false,
498 }
499 }
500
501 fn start(&mut self) {
503 let locked = self.next_locked();
504 match (self.launch)(locked) {
505 Ok(child) => {
506 let at = self.at();
507 let generation = self.tracker.started(at);
508 self.launches += 1;
509 let pid = child.child().id();
510 self.set_status(SupervisorState::Starting, Some(pid));
511 self.emit(SupervisorEvent::Started {
512 launch: self.launches,
513 pid,
514 });
515 self.phase = Phase::Running(Box::new(Running {
516 child,
517 generation,
518 health: HealthTracker::new(self.cfg.health, at),
519 ready: false,
520 launch: self.launches,
521 }));
522 }
523 Err(error) => {
524 self.emit(SupervisorEvent::LaunchFailed {
525 error: error.to_string(),
526 });
527 let outcome = self.tracker.request_restart(self.at());
528 self.apply(outcome);
529 }
530 }
531 }
532
533 fn apply(&mut self, outcome: RestartOutcome) {
534 match outcome {
535 RestartOutcome::Restart(permit) => {
536 let delay = permit.delay();
537 self.emit(SupervisorEvent::RestartScheduled { delay });
538 self.set_status(SupervisorState::WaitingRestart, None);
539 self.phase = Phase::Waiting(permit, Instant::now() + delay);
540 }
541 RestartOutcome::AwaitingDemand => {
542 self.emit(SupervisorEvent::AwaitingDemand);
543 self.set_status(SupervisorState::AwaitingDemand, None);
544 self.phase = Phase::Idle(SupervisorState::AwaitingDemand);
545 }
546 RestartOutcome::Exhausted => {
547 self.emit(SupervisorEvent::Exhausted);
548 self.set_status(SupervisorState::Exhausted, None);
549 self.phase = Phase::Idle(SupervisorState::Exhausted);
550 }
551 RestartOutcome::Stopped | RestartOutcome::IgnoredStale => {
554 self.emit(SupervisorEvent::Stopped);
555 self.set_status(SupervisorState::Stopped, None);
556 self.phase = Phase::Idle(SupervisorState::Stopped);
557 }
558 }
559 }
560
561 fn tick(&mut self) {
562 let placeholder = Phase::Idle(SupervisorState::Stopped);
563 match std::mem::replace(&mut self.phase, placeholder) {
564 Phase::Running(running) => self.tick_running(*running),
565 Phase::Waiting(permit, due) => {
566 if Instant::now() >= due && self.tracker.permit_valid(&permit, self.at()) {
567 self.start();
568 } else {
569 self.phase = Phase::Waiting(permit, due);
570 }
571 }
572 idle @ Phase::Idle(_) => self.phase = idle,
573 }
574 }
575
576 fn tick_running(&mut self, mut running: Running) {
577 match running.child.child_mut().try_wait() {
578 Ok(Some(status)) => {
579 self.emit(SupervisorEvent::Exited {
580 launch: running.launch,
581 code: status.code(),
582 success: status.success(),
583 });
584 drop(running.child);
586 let outcome = self.tracker.exited(running.generation, self.at());
587 self.apply(outcome);
588 return;
589 }
590 Ok(None) => {}
591 Err(_) => {
592 self.stop_child(running.child);
594 let outcome = self.tracker.exited(running.generation, self.at());
595 self.apply(outcome);
596 return;
597 }
598 }
599 let answered = (self.probe)(&running.child);
600 let at = self.at();
601 let outcome = running.health.observe(
602 at,
603 if answered {
604 HealthProbe::Success
605 } else {
606 HealthProbe::Failure
607 },
608 );
609 if answered && !running.ready {
610 running.ready = true;
611 self.tracker.ready(running.generation);
612 self.set_status(SupervisorState::Ready, Some(running.child.child().id()));
613 self.emit(SupervisorEvent::Ready {
614 launch: running.launch,
615 });
616 }
617 let failure = match outcome {
618 HealthOutcome::StartupTimedOut => Some(SupervisorEvent::StartupTimedOut {
619 launch: running.launch,
620 }),
621 HealthOutcome::Unhealthy {
623 consecutive_failures,
624 } if running.ready => Some(SupervisorEvent::Unhealthy {
625 launch: running.launch,
626 consecutive_failures,
627 }),
628 _ => None,
629 };
630 match failure {
631 None => self.phase = Phase::Running(Box::new(running)),
632 Some(event) => {
633 self.emit(event);
634 self.stop_child(running.child);
635 let outcome = self.tracker.exited(running.generation, self.at());
636 self.apply(outcome);
637 }
638 }
639 }
640
641 fn stop_child(&self, mut child: PrivateChild) {
643 child.close_stdin();
644 let _ = child.child_mut().wait_then_kill(self.cfg.stop_grace);
645 }
646
647 fn restart_now(&mut self, state: Option<LockState>) {
648 if let Some(state) = state {
649 self.pending_locked = Some(state.starts_locked());
650 }
651 let placeholder = Phase::Idle(SupervisorState::Stopped);
652 match std::mem::replace(&mut self.phase, placeholder) {
653 Phase::Running(running) => {
654 self.stop_child(running.child);
655 let at = self.at();
656 let mut outcome = self.tracker.exited(running.generation, at);
657 if outcome == RestartOutcome::AwaitingDemand {
658 outcome = self.tracker.request_restart(at);
660 }
661 self.apply(outcome);
662 }
663 waiting @ Phase::Waiting(..) => self.phase = waiting,
665 Phase::Idle(SupervisorState::AwaitingDemand) => {
666 match self.tracker.request_restart(self.at()) {
667 RestartOutcome::AwaitingDemand => self.start(),
669 other => self.apply(other),
670 }
671 }
672 Phase::Idle(_) => {
673 self.tracker.reset_budget();
674 self.start();
675 }
676 }
677 }
678
679 fn return_child(&mut self, reply: mpsc::Sender<Option<PrivateChild>>) {
682 self.tracker.shutdown();
683 let child = match std::mem::replace(&mut self.phase, Phase::Idle(SupervisorState::Stopped))
684 {
685 Phase::Running(running) => Some(running.child),
686 _ => None,
687 };
688 self.set_status(SupervisorState::Stopped, None);
689 self.emit(SupervisorEvent::Stopped);
690 let _ = reply.send(child);
691 }
692
693 fn shutdown(&mut self) {
694 self.tracker.shutdown();
695 if let Phase::Running(running) =
696 std::mem::replace(&mut self.phase, Phase::Idle(SupervisorState::Stopped))
697 {
698 self.stop_child(running.child);
699 }
700 self.set_status(SupervisorState::Stopped, None);
701 self.emit(SupervisorEvent::Stopped);
702 }
703}