Skip to main content

rightkit_daemon/
supervisor.rs

1//! A supervisor loop for a daemon-pipe child (issue #295).
2//!
3//! A [`PrivateChild`] is an engine the app started itself and talks to over the daemon pipe. The
4//! `rightkit-job-queue` engine supervisor needs a sidecar stdio `hello` frame, which a daemon
5//! service does not speak; this one watches such a child with the two `rightkit-child-process`
6//! trackers instead:
7//!
8//! * **poll**: every [`SupervisorConfig::poll_interval`] the loop asks whether the child has
9//!   exited (`try_wait`, no reaping surprises: the supervisor owns the child);
10//! * **probe**: a caller-supplied closure says whether the child answers (the usual one is
11//!   [`ping_probe`]: connect with the child's own secret, verify the host, call `ping`). Each
12//!   result is fed to a [`HealthTracker`];
13//! * **relaunch**: an exit, an unhealthy child or a startup that never became healthy is stopped
14//!   (stdin closed first, then the tree is killed after [`SupervisorConfig::stop_grace`]) and the
15//!   [`RestartTracker`] decides: relaunch after its backoff, wait for demand, or give up
16//!   ([`SupervisorEvent::Exhausted`]).
17//!
18//! Decisions that are easy to get wrong, and what was chosen:
19//!
20//! * While a generation has never answered a probe, failures are expected (it is starting), so
21//!   only [`HealthOutcome::StartupTimedOut`] acts; `Unhealthy` acts only after the first success.
22//! * The first successful probe calls [`RestartTracker::ready`], so a child that comes up and later
23//!   crashes starts again from the base backoff (per the restart policy's own rules).
24//! * A failed launch (the closure returned an error) is charged to the restart budget like a
25//!   crash, so a binary that cannot start is not retried at poll speed forever.
26//! * [`SupervisorHandle::restart_now`] on a running child counts as an exit of that generation.
27//!   After the policy gave up ([`SupervisorState::Exhausted`]) it forgets the budget and starts
28//!   again: an explicit user start.
29//! * The probe runs on the supervisor thread and may block as long as its own deadline; give it a
30//!   short one ([`ping_probe`] takes the bound as an argument).
31//! * The child is created, probed and stopped on the supervisor thread only; the launch closure is
32//!   the one thing that crosses to it.
33
34use 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
47/// Default [`SupervisorConfig::poll_interval`].
48pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_secs(1);
49/// Default [`SupervisorConfig::stop_grace`].
50pub const DEFAULT_STOP_GRACE: Duration = Duration::from_secs(5);
51
52const MIN_POLL: Duration = Duration::from_millis(10);
53
54/// How a child is watched and restarted.
55#[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    /// `restart` decides when and whether a stopped child starts again; `health` decides when
65    /// probe results make a child unhealthy or a start a failure.
66    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    /// Time between checks (exit poll plus one probe). Default [`DEFAULT_POLL_INTERVAL`]; raised
76    /// to at least 10 ms. A restart's backoff is honoured to its own precision, not this one's.
77    #[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    /// How long a child gets to exit by itself after its stdin was closed before its process tree
84    /// is killed. Default [`DEFAULT_STOP_GRACE`].
85    #[must_use]
86    pub fn stop_grace(mut self, grace: Duration) -> Self {
87        self.stop_grace = grace;
88        self
89    }
90}
91
92/// What the supervisor is doing now.
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum SupervisorState {
95    /// A child is running and has not yet answered a probe.
96    Starting,
97    /// A child is running and has answered a probe.
98    Ready,
99    /// The child is gone and the next start is waiting out its backoff.
100    WaitingRestart,
101    /// The child is gone and the policy waits for [`SupervisorHandle::restart_now`].
102    AwaitingDemand,
103    /// The restart budget is spent; nothing runs until [`SupervisorHandle::restart_now`].
104    Exhausted,
105    /// The policy never restarts and the child is gone, or the supervisor has stopped.
106    Stopped,
107}
108
109/// A snapshot of the supervisor, from [`SupervisorHandle::status`].
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub struct SupervisorStatus {
112    pub state: SupervisorState,
113    /// Children started so far (the running one included).
114    pub launches: u64,
115    /// The running child's process id, when there is one.
116    pub pid: Option<u32>,
117}
118
119/// What happened, content-free. `launch` is the 1-based count of children started.
120#[derive(Debug, Clone, PartialEq, Eq)]
121pub enum SupervisorEvent {
122    Started {
123        launch: u64,
124        pid: u32,
125    },
126    /// The child answered its first probe.
127    Ready {
128        launch: u64,
129    },
130    /// The child exited by itself.
131    Exited {
132        launch: u64,
133        code: Option<i32>,
134        success: bool,
135    },
136    /// A child that had been healthy failed `consecutive_failures` probes in a row; it is stopped.
137    Unhealthy {
138        launch: u64,
139        consecutive_failures: u32,
140    },
141    /// The child never answered a probe within the policy's startup timeout; it is stopped.
142    StartupTimedOut {
143        launch: u64,
144    },
145    /// The launch closure failed (never started a child).
146    LaunchFailed {
147        error: String,
148    },
149    RestartScheduled {
150        delay: Duration,
151    },
152    AwaitingDemand,
153    /// The restart budget is spent.
154    Exhausted,
155    /// The supervisor will not start another child (a policy that never restarts, or `stop`).
156    Stopped,
157}
158
159/// Callback for [`SupervisorEvent`]s. Runs on the supervisor thread; keep it short.
160pub type SupervisorEventFn = Arc<dyn Fn(&SupervisorEvent) + Send + Sync>;
161
162/// The app's lock state, as input to a restart (the lock mirror itself stays the app's: a
163/// replacement core starts locked exactly when the app says the lock blocked its data).
164/// [`Unknown`](Self::Unknown) is the state of an unlock (or lock) request that got no answer
165/// because the child died first: it starts the replacement locked, so an unanswered unlock
166/// can never restart a child unlocked.
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub enum LockState {
169    Locked,
170    Unlocked,
171    Unknown,
172}
173
174impl LockState {
175    /// The `locked` start flag a launch gets for this state. Only a positively answered
176    /// [`Unlocked`](Self::Unlocked) starts a replacement unlocked.
177    pub fn starts_locked(self) -> bool {
178        !matches!(self, LockState::Unlocked)
179    }
180}
181
182/// Supplies the current [`LockState`] at each launch. Runs on the supervisor thread.
183pub 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
191/// Control of a running supervisor. Dropping it stops the supervisor and its child.
192pub struct SupervisorHandle {
193    tx: mpsc::Sender<Cmd>,
194    status: Arc<Mutex<SupervisorStatus>>,
195    thread: Option<std::thread::JoinHandle<()>>,
196}
197
198impl SupervisorHandle {
199    /// The state now.
200    pub fn status(&self) -> SupervisorStatus {
201        *self.status.lock().unwrap_or_else(|e| e.into_inner())
202    }
203
204    /// Ask for a start: a running child is stopped and counted as an exit (then the policy
205    /// decides, so a policy that waits for demand starts at once); a child waiting for demand
206    /// starts; an exhausted or stopped policy forgets its budget and starts. A pending backoff is
207    /// left to run. Returns before the work is done.
208    pub fn restart_now(&self) {
209        let _ = self.tx.send(Cmd::RestartNow(None));
210    }
211
212    /// [`restart_now`](Self::restart_now) with the lock state the next launch starts from,
213    /// overriding the [`LockStateFn`] for that one launch. [`LockState::Unknown`] (an unlock
214    /// that was never answered) starts the replacement locked.
215    pub fn restart_now_with_lock(&self, state: LockState) {
216        let _ = self.tx.send(Cmd::RestartNow(Some(state)));
217    }
218
219    /// Stop the child (stdin closed, then killed after the grace) and end the supervisor. Waits
220    /// for the thread to finish.
221    pub fn stop(mut self) {
222        self.stop_inner();
223    }
224
225    /// End the supervisor without stopping the child, handing the running child back once
226    /// (`None` when no child is running) so the caller can shut it down in its own order.
227    /// No restart follows: the supervisor thread has ended when this returns. Consumes the
228    /// handle, so the child can be taken only once.
229    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
254/// Start supervising. `launch` makes a child (normally [`PrivateChild::spawn`] of an
255/// `OwnedCommand`, with the per-run suite made fresh each time if the child needs one); `probe`
256/// says whether the running child answers (see [`ping_probe`]); `on_event` hears what happens.
257/// The first child is started at once, on the supervisor thread.
258pub 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
271/// [`supervise`] for a child whose replacement must start from the app's lock state: every
272/// launch receives `locked`, resolved at launch time from a
273/// [`restart_now_with_lock`](SupervisorHandle::restart_now_with_lock) override first, then
274/// `lock_state`, then `false`. [`LockState::Unknown`] resolves to locked, so an unlock the
275/// dying child never answered cannot restart it unlocked. The lock mirror that produces
276/// the state stays the app's input.
277pub 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
291/// Supervise a child the caller already spawned (and usually already verified: it counts
292/// as launch 1, already ready, so its first crash restarts from the base backoff). The
293/// child is transferred untouched — its stdin stays open and it is not probed before the
294/// first poll. Replacements come from `launch`, with the lock flag resolved as in
295/// [`supervise_with_lock_state`].
296pub 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
353/// A probe for [`supervise`]: connect to the child's service with the child's own secret, verify
354/// the host when `expect` is given, and call `ping`. True only for a `{"pong": true}` answer.
355/// `timeout` bounds one probe (connect, hello and the call). Use [`HostExpectation`] whenever the
356/// secret is sent: a secret goes to whatever answered at the endpoint.
357pub 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    /// The next start is waiting for `due`.
392    Waiting(RestartPermit, Instant),
393    /// Nothing runs: `AwaitingDemand`, `Exhausted` or `Stopped`.
394    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    /// One-launch lock override from `restart_now_with_lock`.
410    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    /// Take over an already-spawned child as launch 1, already ready (the caller spawned
463    /// and verified it). The child itself is not touched: no stdin close, no probe yet.
464    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    /// The `locked` flag for the next launch: explicit restart override, then the app's
490    /// lock-state input, then unlocked. `Unknown` resolves locked (see [`LockState`]).
491    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    /// Launch a child now. A launch failure is charged to the budget like a crash.
502    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            // `IgnoredStale` cannot happen for a generation this loop owns; if it ever did,
552            // stopping loudly beats a silent loop.
553            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                // The child has been reaped by `try_wait`; dropping the wrapper closes its stdin.
585                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                // The state of the child cannot be read: do not guess it is fine.
593                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            // Failures before the first success are a child still starting, not an unhealthy one.
622            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    /// Stdin closed (the polite cue), then the whole tree is killed after the grace.
642    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                    // This is the demand.
659                    outcome = self.tracker.request_restart(at);
660                }
661                self.apply(outcome);
662            }
663            // A start is already on its way.
664            waiting @ Phase::Waiting(..) => self.phase = waiting,
665            Phase::Idle(SupervisorState::AwaitingDemand) => {
666                match self.tracker.request_restart(self.at()) {
667                    // Nothing ever ran, so the tracker was not waiting: start directly.
668                    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    /// Hand the running child back untouched and end supervision: no stdin close, no
680    /// kill, no restart. The caller owns the child's shutdown from here.
681    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}