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