Skip to main content

behavior/
supervising.rs

1//! Pure supervision. Stable child identity is a proxy actor; replacement is a
2//! message to that proxy, which creates a fresh worker incarnation.
3
4use std::time::Duration;
5
6use tokio::time::Instant;
7
8use crate::behavior::{
9    Actions, Address, Behavior, Births, Create, Delivery, Recipient, SendAlgebra, SendProduct,
10    ServiceSends, User, UserEvent,
11};
12use crate::deadlined::{TimeEvent, TimeReached};
13use crate::shutdown::{ShutdownEvent, ShutdownRequested};
14use crate::verdict::{Never, Step};
15use crate::watching::{PeerEvent, PeerStopped};
16use crate::{Crash, Exit};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Strategy {
20    OneForOne,
21    OneForAll,
22    RestForOne,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum RestartPolicy {
27    Permanent,
28    Transient,
29    Temporary,
30}
31
32#[must_use]
33pub const fn restart_one() -> Strategy {
34    Strategy::OneForOne
35}
36
37#[must_use]
38pub const fn restart_all() -> Strategy {
39    Strategy::OneForAll
40}
41
42#[must_use]
43pub const fn restart_rest() -> Strategy {
44    Strategy::RestForOne
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct ChildStopped<A: Address> {
49    pub nonce: A::Nonce,
50    pub outcome: Result<Exit<A>, Crash>,
51    pub at: Instant,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct ObserveChild<A: Address> {
56    pub nonce: A::Nonce,
57}
58
59/// A proxy's request for its interpreter to report a worker termination to
60/// the proxy's parent. The interpreter supplies the emitting proxy's child
61/// nonce when constructing [`WorkerStopped`].
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub struct ReportWorkerStopped<A: Address> {
64    pub outcome: Result<Exit<A>, Crash>,
65    pub at: Instant,
66}
67
68/// A worker termination reported by a still-live supervised proxy.
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct WorkerStopped<A: Address> {
71    pub proxy: A::Nonce,
72    pub outcome: Result<Exit<A>, Crash>,
73    pub at: Instant,
74}
75
76/// Construction of the worker-report lane through a composed event type.
77pub trait WorkerEvent<A: Address>: Sized {
78    fn worker_stopped(event: WorkerStopped<A>) -> Option<Self>;
79}
80
81#[derive(Clone, PartialEq, Eq)]
82pub enum SupervisionEvent<E, A: Address> {
83    Inner(E),
84    ChildStopped(ChildStopped<A>),
85    WorkerStopped(WorkerStopped<A>),
86}
87
88pub trait ChildEvent<A: Address>: Sized {
89    fn child_stopped(event: ChildStopped<A>) -> Option<Self>;
90}
91
92impl<E, A: Address> ChildEvent<A> for SupervisionEvent<E, A> {
93    fn child_stopped(event: ChildStopped<A>) -> Option<Self> {
94        Some(Self::ChildStopped(event))
95    }
96}
97
98impl<E, A: Address> WorkerEvent<A> for SupervisionEvent<E, A> {
99    fn worker_stopped(event: WorkerStopped<A>) -> Option<Self> {
100        Some(Self::WorkerStopped(event))
101    }
102}
103
104impl<E: UserEvent, A: Address> UserEvent for SupervisionEvent<E, A> {
105    type Addr = E::Addr;
106    type Message = E::Message;
107
108    fn user(from: Self::Addr, message: Self::Message) -> Self {
109        Self::Inner(E::user(from, message))
110    }
111
112    fn into_user(self) -> Result<User<Self::Addr, Self::Message>, Self> {
113        match self {
114            Self::Inner(event) => event.into_user().map_err(Self::Inner),
115            stopped @ (Self::ChildStopped(_) | Self::WorkerStopped(_)) => Err(stopped),
116        }
117    }
118}
119
120impl<E: TimeEvent, A: Address> TimeEvent for SupervisionEvent<E, A> {
121    fn time_reached(event: TimeReached) -> Option<Self> {
122        E::time_reached(event).map(Self::Inner)
123    }
124}
125
126impl<E: PeerEvent<A>, A: Address> PeerEvent<A> for SupervisionEvent<E, A> {
127    fn peer_stopped(event: PeerStopped<A>) -> Option<Self> {
128        E::peer_stopped(event).map(Self::Inner)
129    }
130}
131
132impl<E: ShutdownEvent, A: Address> ShutdownEvent for SupervisionEvent<E, A> {
133    fn shutdown_requested(event: ShutdownRequested) -> Option<Self> {
134        E::shutdown_requested(event).map(Self::Inner)
135    }
136}
137
138/// Commands accepted by a stable proxy.
139///
140/// A replacement requested while the current worker is alive is held until
141/// that worker's observed termination. Creation therefore never overlaps two
142/// worker incarnations beneath the same proxy.
143#[derive(Debug)]
144pub enum ProxyCommand<C: Behavior> {
145    /// Forward an application message to the current worker, if one is alive.
146    Forward(C::Msg),
147    /// Supply the worker behavior for the next fresh incarnation.
148    Replace(C),
149}
150
151pub type SupervisorSends<A, Sends, C> = SendProduct<
152    Sends,
153    SendProduct<ServiceSends<ObserveChild<A>>, Vec<Delivery<A, ProxyCommand<C>>>>,
154>;
155
156pub type SupervisorActions<B, C> = Actions<
157    <B as Behavior>::Addr,
158    <B as Behavior>::Ph,
159    SupervisorSends<<B as Behavior>::Addr, <B as Behavior>::Sends, C>,
160    Births<Proxy<C>>,
161>;
162
163/// The stable actor. Every replacement is an ordinary fresh birth beneath it.
164///
165/// Each emitted worker birth is paired with an [`ObserveChild`] request. A
166/// matching [`ChildStopped`] leaves the proxy alive and emits a
167/// [`ReportWorkerStopped`] carrying the outcome unchanged. Stale child-stop
168/// observations are inert.
169pub struct Proxy<C: Behavior<Ph = Never>> {
170    worker: Option<C>,
171    generation: u64,
172    worker_alive: bool,
173    pending: Option<C>,
174}
175
176impl<C: Behavior<Ph = Never>> Proxy<C> {
177    #[must_use]
178    pub fn new(worker: C) -> Self {
179        Self {
180            worker: Some(worker),
181            generation: 0,
182            worker_alive: false,
183            pending: None,
184        }
185    }
186}
187
188impl<C> Behavior for Proxy<C>
189where
190    C: Behavior<Ph = Never> + Send,
191    C::Addr: Send,
192    <C::Addr as Address>::Nonce: From<u64> + Send,
193    C::Msg: Send,
194    C: Send,
195{
196    type Addr = C::Addr;
197    type Msg = ProxyCommand<C>;
198    type Event = SupervisionEvent<User<C::Addr, ProxyCommand<C>>, C::Addr>;
199    type Sends = SendProduct<
200        Vec<Delivery<C::Addr, C::Msg>>,
201        SendProduct<
202            ServiceSends<ObserveChild<C::Addr>>,
203            ServiceSends<ReportWorkerStopped<C::Addr>>,
204        >,
205    >;
206    type Ph = Never;
207    type Error = Never;
208    type Birth = Births<C>;
209    type Effect = Actions<C::Addr, Never, Self::Sends, Births<C>>;
210    type Done = Exit<C::Addr>;
211
212    async fn init(&mut self) -> Result<Self::Effect, Never> {
213        let child = self.worker.take().expect("a proxy initializes once");
214        self.worker_alive = true;
215        Ok(Actions {
216            sends: SendProduct {
217                inner: Vec::new(),
218                own: SendProduct {
219                    inner: ServiceSends::one(ObserveChild {
220                        nonce: <C::Addr as Address>::Nonce::from(self.generation),
221                    }),
222                    own: ServiceSends::empty(),
223                },
224            },
225            creates: vec![Create {
226                nonce: <C::Addr as Address>::Nonce::from(self.generation),
227                child,
228            }],
229            become_: Step::Continue,
230        })
231    }
232
233    async fn step(&mut self, event: Self::Event) -> Result<Self::Effect, Never> {
234        let SupervisionEvent::Inner(event) = event else {
235            return match event {
236                SupervisionEvent::ChildStopped(event)
237                    if event.nonce == <C::Addr as Address>::Nonce::from(self.generation) =>
238                {
239                    self.worker_alive = false;
240                    let report = ReportWorkerStopped {
241                        outcome: event.outcome,
242                        at: event.at,
243                    };
244                    let creates = self.pending.take().map_or_else(Vec::new, |child| {
245                        self.generation = self
246                            .generation
247                            .checked_add(1)
248                            .expect("proxy generation exhausted");
249                        self.worker_alive = true;
250                        vec![Create {
251                            nonce: <C::Addr as Address>::Nonce::from(self.generation),
252                            child,
253                        }]
254                    });
255                    let observes = creates
256                        .iter()
257                        .map(|create| ObserveChild {
258                            nonce: create.nonce,
259                        })
260                        .collect();
261                    Ok(Actions {
262                        sends: SendProduct {
263                            inner: Vec::new(),
264                            own: SendProduct {
265                                inner: ServiceSends::new(observes),
266                                own: ServiceSends::one(report),
267                            },
268                        },
269                        creates,
270                        become_: Step::Continue,
271                    })
272                }
273                SupervisionEvent::ChildStopped(_) | SupervisionEvent::WorkerStopped(_) => {
274                    Ok(Actions::cont())
275                }
276                SupervisionEvent::Inner(_) => unreachable!(),
277            };
278        };
279        match event.message {
280            ProxyCommand::Forward(message) => Ok(Actions {
281                sends: SendProduct {
282                    inner: self
283                        .worker_alive
284                        .then(|| {
285                            Delivery::new(
286                                Recipient::child(<C::Addr as Address>::Nonce::from(
287                                    self.generation,
288                                )),
289                                message,
290                            )
291                        })
292                        .into_iter()
293                        .collect(),
294                    own: SendProduct {
295                        inner: ServiceSends::empty(),
296                        own: ServiceSends::empty(),
297                    },
298                },
299                creates: Vec::new(),
300                become_: Step::Continue,
301            }),
302            ProxyCommand::Replace(child) => {
303                if self.worker_alive {
304                    self.pending = Some(child);
305                    return Ok(Actions::cont());
306                }
307                self.generation = self
308                    .generation
309                    .checked_add(1)
310                    .expect("proxy generation exhausted");
311                self.worker_alive = true;
312                let nonce = <C::Addr as Address>::Nonce::from(self.generation);
313                Ok(Actions {
314                    sends: SendProduct {
315                        inner: Vec::new(),
316                        own: SendProduct {
317                            inner: ServiceSends::one(ObserveChild { nonce }),
318                            own: ServiceSends::empty(),
319                        },
320                    },
321                    creates: vec![Create { nonce, child }],
322                    become_: Step::Continue,
323                })
324            }
325        }
326    }
327}
328
329struct Slot {
330    alive: bool,
331    sequence: u64,
332}
333
334pub struct Supervising<B: Behavior, C: Behavior<Ph = Never, Addr = B::Addr>> {
335    inner: B,
336    slots: Vec<(<B::Addr as Address>::Nonce, Slot)>,
337    configured_count: usize,
338    next_sequence: u64,
339    build: fn(usize) -> C,
340    strategy: Strategy,
341    policy: RestartPolicy,
342    max_restarts: u32,
343    window: Duration,
344    restarts: Vec<Instant>,
345}
346
347impl<B, C> Supervising<B, C>
348where
349    B: Behavior<Birth = Births<C>>,
350    C: Behavior<Ph = Never, Addr = B::Addr>,
351{
352    #[allow(clippy::too_many_arguments, reason = "hidden by Spec")]
353    /// Construct the concrete supervisor behavior hidden by `Spec`.
354    ///
355    /// # Panics
356    /// Panics only if a fleet index cannot be represented by `u64`.
357    #[must_use]
358    pub fn new(
359        inner: B,
360        nonces: fn(usize) -> <B::Addr as Address>::Nonce,
361        count: usize,
362        build: fn(usize) -> C,
363        strategy: Strategy,
364        policy: RestartPolicy,
365        max_restarts: u32,
366        window: Duration,
367    ) -> Self {
368        let slots = (0..count)
369            .map(|index| {
370                (
371                    nonces(index),
372                    Slot {
373                        alive: true,
374                        sequence: u64::try_from(index).expect("fleet index fits u64"),
375                    },
376                )
377            })
378            .collect();
379        Self {
380            inner,
381            slots,
382            configured_count: count,
383            next_sequence: u64::try_from(count).expect("fleet size fits u64"),
384            build,
385            strategy,
386            policy,
387            max_restarts,
388            window,
389            restarts: Vec::new(),
390        }
391    }
392
393    #[must_use]
394    pub fn with_strategy(mut self, strategy: Strategy) -> Self {
395        self.strategy = strategy;
396        self
397    }
398
399    #[must_use]
400    pub fn with_policy(mut self, policy: RestartPolicy) -> Self {
401        self.policy = policy;
402        self
403    }
404
405    #[must_use]
406    pub fn with_budget(mut self, max: u32, window: Duration) -> Self {
407        self.max_restarts = max;
408        self.window = window;
409        self
410    }
411
412    fn position(&self, nonce: <B::Addr as Address>::Nonce) -> Option<usize> {
413        self.slots.iter().position(|(known, _)| *known == nonce)
414    }
415
416    #[must_use]
417    /// Report whether a known supervised proxy is alive.
418    ///
419    /// # Panics
420    /// Panics when `nonce` is not part of this supervisor topology.
421    pub fn is_alive(&self, nonce: <B::Addr as Address>::Nonce) -> bool {
422        self.slots[self.position(nonce).expect("unknown supervised nonce")]
423            .1
424            .alive
425    }
426
427    #[must_use]
428    pub fn child_count(&self) -> usize {
429        self.slots.len()
430    }
431
432    #[must_use]
433    pub fn restarts_in_window(&self) -> usize {
434        self.restarts.len()
435    }
436
437    fn replacements(
438        &mut self,
439        event: &WorkerStopped<B::Addr>,
440    ) -> Vec<Delivery<B::Addr, ProxyCommand<C>>> {
441        let dead = self
442            .position(event.proxy)
443            .expect("unknown supervised nonce");
444        let eligible = match self.policy {
445            RestartPolicy::Permanent => true,
446            RestartPolicy::Transient => {
447                !matches!(&event.outcome, Ok(Exit::Normal | Exit::Collected))
448            }
449            RestartPolicy::Temporary => false,
450        };
451        if !eligible {
452            self.slots[dead].1.alive = false;
453            return Vec::new();
454        }
455        if self.window != Duration::MAX {
456            self.restarts.retain(|stamp| {
457                event
458                    .at
459                    .checked_duration_since(*stamp)
460                    .is_none_or(|age| age <= self.window)
461            });
462        }
463        let sequence = self.slots[dead].1.sequence;
464        let candidates: Vec<usize> = match self.strategy {
465            Strategy::OneForOne => vec![dead],
466            Strategy::OneForAll => self
467                .slots
468                .iter()
469                .enumerate()
470                .filter_map(|(index, (_, slot))| slot.alive.then_some(index))
471                .collect(),
472            Strategy::RestForOne => self
473                .slots
474                .iter()
475                .enumerate()
476                .filter_map(|(index, (_, slot))| {
477                    (slot.alive && slot.sequence >= sequence).then_some(index)
478                })
479                .collect(),
480        };
481        if self.restarts.len() + candidates.len() > self.max_restarts as usize {
482            self.slots[dead].1.alive = false;
483            return Vec::new();
484        }
485        self.restarts
486            .resize(self.restarts.len() + candidates.len(), event.at);
487        candidates
488            .into_iter()
489            .map(|index| {
490                self.slots[index].1.alive = true;
491                Delivery::new(
492                    Recipient::child(self.slots[index].0),
493                    ProxyCommand::Replace((self.build)(index)),
494                )
495            })
496            .collect()
497    }
498
499    fn wrap(
500        &mut self,
501        actions: Actions<B::Addr, B::Ph, B::Sends, Births<C>>,
502    ) -> SupervisorActions<B, C> {
503        let born: Vec<_> = actions.creates.iter().map(|create| create.nonce).collect();
504        for create in &actions.creates {
505            assert!(
506                self.position(create.nonce).is_none(),
507                "a child birth nonce must be fresh"
508            );
509            self.slots.push((
510                create.nonce,
511                Slot {
512                    alive: true,
513                    sequence: self.next_sequence,
514                },
515            ));
516            self.next_sequence = self
517                .next_sequence
518                .checked_add(1)
519                .expect("birth sequence exhausted");
520        }
521        Actions {
522            sends: SendProduct {
523                inner: actions.sends,
524                own: SendProduct {
525                    inner: ServiceSends::new(
526                        born.into_iter()
527                            .map(|nonce| ObserveChild { nonce })
528                            .collect(),
529                    ),
530                    own: Vec::new(),
531                },
532            },
533            creates: actions
534                .creates
535                .into_iter()
536                .map(|create| Create {
537                    nonce: create.nonce,
538                    child: Proxy::new(create.child),
539                })
540                .collect(),
541            become_: actions.become_,
542        }
543    }
544}
545
546impl<B, C, A, Ph, Sends> Behavior for Supervising<B, C>
547where
548    A: Address + Send,
549    Sends: SendAlgebra,
550    B: Behavior<
551            Addr = A,
552            Ph = Ph,
553            Sends = Sends,
554            Birth = Births<C>,
555            Effect = Actions<A, Ph, Sends, Births<C>>,
556            Done = Exit<A>,
557        > + Send,
558    B::Event: ChildEvent<B::Addr> + Send,
559    A::Nonce: From<u64> + Send,
560    B::Msg: Send,
561    C: Behavior<Ph = Never, Addr = B::Addr> + Send,
562{
563    type Addr = A;
564    type Msg = B::Msg;
565    type Event = SupervisionEvent<B::Event, B::Addr>;
566    type Sends = SupervisorSends<A, Sends, C>;
567    type Ph = Ph;
568    type Error = B::Error;
569    type Birth = Births<Proxy<C>>;
570    type Effect = Actions<A, Ph, Self::Sends, Births<Proxy<C>>>;
571    type Done = Exit<A>;
572
573    async fn init(&mut self) -> Result<Self::Effect, B::Error> {
574        let actions = self.inner.init().await?;
575        let mut actions = self.wrap(actions);
576        actions
577            .creates
578            .extend(self.slots[..self.configured_count].iter().enumerate().map(
579                |(index, (nonce, _))| Create {
580                    nonce: *nonce,
581                    child: Proxy::new((self.build)(index)),
582                },
583            ));
584        actions.sends.own.inner.extend(
585            self.slots[..self.configured_count]
586                .iter()
587                .map(|(nonce, _)| ObserveChild { nonce: *nonce }),
588        );
589        Ok(actions)
590    }
591
592    async fn step(&mut self, event: Self::Event) -> Result<Self::Effect, B::Error> {
593        match event {
594            SupervisionEvent::WorkerStopped(event) => Ok(Actions {
595                sends: SendProduct {
596                    inner: B::Sends::empty(),
597                    own: SendProduct {
598                        inner: ServiceSends::empty(),
599                        own: self.replacements(&event),
600                    },
601                },
602                creates: Vec::new(),
603                become_: Step::Continue,
604            }),
605            SupervisionEvent::ChildStopped(event) => {
606                let dead = self
607                    .position(event.nonce)
608                    .expect("unknown supervised nonce");
609                self.slots[dead].1.alive = false;
610                Ok(Actions::cont())
611            }
612            SupervisionEvent::Inner(event) => {
613                let actions = self.inner.step(event).await?;
614                Ok(self.wrap(actions))
615            }
616        }
617    }
618}