Skip to main content

behavior/supervision/adapter/
supervisor.rs

1//! Fleet coordination for supervised stable proxy actors.
2
3use std::time::Duration;
4
5use super::super::domain::{Fleet, RestartBudget};
6use super::super::policy::{
7    RestartPolicy, Strategy, SupervisionFailure, SupervisionFailureReaction,
8    retire_on_supervision_failure,
9};
10use super::super::protocol::{ProxyCommand, SupervisionEvent};
11use super::proxy::Proxy;
12use crate::behavior::{
13    Actions, Address, Behavior, Births, Create, Delivery, Recipient, SendAlgebra, ServiceSends,
14};
15use crate::next::{Never, Step};
16use crate::protocol::{
17    ChildEvent, CreationEvent, ObserveChild, WorkerCreationEvent, WorkerStopped,
18};
19use crate::{Become, Exit, SupervisionFailureReason};
20use crate::{Inner, Own, SendInput};
21
22/// Named effect lanes emitted by a supervised behavior.
23pub struct SupervisorSends<A: Address, Sends, C: Behavior<Addr = A>> {
24    pub behavior: Sends,
25    pub child_observations: ServiceSends<ObserveChild<A::Nonce>>,
26    pub replacement_commands: Vec<Delivery<A, ProxyCommand<C>>>,
27}
28
29impl<A, Sends, C> SendAlgebra for SupervisorSends<A, Sends, C>
30where
31    A: Address,
32    Sends: SendAlgebra,
33    C: Behavior<Addr = A>,
34{
35    fn empty() -> Self {
36        Self {
37            behavior: Sends::empty(),
38            child_observations: ServiceSends::empty(),
39            replacement_commands: Vec::new(),
40        }
41    }
42
43    fn append(&mut self, other: Self) {
44        self.behavior.append(other.behavior);
45        self.child_observations.append(other.child_observations);
46        self.replacement_commands.extend(other.replacement_commands);
47    }
48}
49
50impl<A, Sends, C> SendInput<ObserveChild<A::Nonce>, Own> for SupervisorSends<A, Sends, C>
51where
52    A: Address,
53    C: Behavior<Addr = A>,
54{
55    fn emit(&mut self, input: ObserveChild<A::Nonce>) {
56        self.child_observations.send(input);
57    }
58}
59
60impl<A, Sends, C> SendInput<Delivery<A, ProxyCommand<C>>, Own> for SupervisorSends<A, Sends, C>
61where
62    A: Address,
63    C: Behavior<Addr = A>,
64{
65    fn emit(&mut self, input: Delivery<A, ProxyCommand<C>>) {
66        self.replacement_commands.push(input);
67    }
68}
69
70impl<A, Sends, C, Input, Path> SendInput<Input, Inner<Path>> for SupervisorSends<A, Sends, C>
71where
72    A: Address,
73    C: Behavior<Addr = A>,
74    Sends: SendInput<Input, Path>,
75{
76    fn emit(&mut self, input: Input) {
77        <Sends as SendInput<Input, Path>>::emit(&mut self.behavior, input);
78    }
79}
80
81pub type SupervisorActions<B, C> = Actions<
82    <B as Behavior>::Addr,
83    <B as Behavior>::Ph,
84    SupervisorSends<<B as Behavior>::Addr, <B as Behavior>::Sends, C>,
85    Births<Proxy<C>>,
86>;
87
88enum ReplacementDecision<A: Address, C: Behavior<Addr = A>> {
89    Retire,
90    Replace(Vec<Delivery<A, ProxyCommand<C>>>),
91    Failed(SupervisionFailure<A>),
92}
93
94pub struct Supervisor<B: Behavior, C: Behavior<Ph = Never, Addr = B::Addr>> {
95    inner: B,
96    fleet: Fleet<<B::Addr as Address>::Nonce>,
97    build: fn(usize) -> C,
98    strategy: Strategy,
99    policy: RestartPolicy,
100    budget: RestartBudget,
101    on_failure: SupervisionFailureReaction<B>,
102}
103
104impl<B, C> Supervisor<B, C>
105where
106    B: Behavior<Birth = Births<C>>,
107    C: Behavior<Ph = Never, Addr = B::Addr>,
108{
109    #[allow(clippy::too_many_arguments, reason = "hidden by Compose")]
110    /// Construct the concrete supervisor behavior hidden by `Compose`.
111    ///
112    /// # Panics
113    /// Panics when configured child nonces are not unique. Such a topology
114    /// would violate creator-local child routing and creation freshness before
115    /// the behavior could return an initialization result.
116    #[must_use]
117    pub fn new(
118        inner: B,
119        nonces: fn(usize) -> <B::Addr as Address>::Nonce,
120        count: usize,
121        build: fn(usize) -> C,
122        strategy: Strategy,
123        policy: RestartPolicy,
124        max_restarts: u32,
125        window: Duration,
126    ) -> Self {
127        let fleet = Fleet::configured((0..count).map(nonces))
128            .unwrap_or_else(|_| panic!("configured child nonces must be fresh"));
129        Self {
130            inner,
131            fleet,
132            build,
133            strategy,
134            policy,
135            budget: RestartBudget::new(max_restarts, window),
136            on_failure: retire_on_supervision_failure::<B>,
137        }
138    }
139
140    #[must_use]
141    pub fn with_strategy(mut self, strategy: Strategy) -> Self {
142        self.strategy = strategy;
143        self
144    }
145
146    #[must_use]
147    pub fn with_policy(mut self, policy: RestartPolicy) -> Self {
148        self.policy = policy;
149        self
150    }
151
152    #[must_use]
153    pub fn with_budget(mut self, max: u32, window: Duration) -> Self {
154        self.budget = RestartBudget::new(max, window);
155        self
156    }
157
158    #[must_use]
159    /// Replace the pure reaction used for typed supervision failures.
160    pub fn with_failure_reaction(mut self, reaction: SupervisionFailureReaction<B>) -> Self {
161        self.on_failure = reaction;
162        self
163    }
164
165    #[must_use]
166    /// Report whether a known supervised proxy is alive.
167    ///
168    /// # Panics
169    /// Panics when `nonce` is not part of this supervisor topology.
170    pub fn is_alive(&self, nonce: <B::Addr as Address>::Nonce) -> bool {
171        self.fleet
172            .is_available(nonce)
173            .unwrap_or_else(|_| panic!("unknown supervised nonce"))
174    }
175
176    #[must_use]
177    pub fn child_count(&self) -> usize {
178        self.fleet.len()
179    }
180
181    #[must_use]
182    pub fn restarts_in_window(&self) -> usize {
183        self.budget.admitted()
184    }
185
186    fn replacement_decision(
187        &mut self,
188        event: &WorkerStopped<B::Addr>,
189    ) -> ReplacementDecision<B::Addr, C> {
190        let eligible = match self.policy {
191            RestartPolicy::Permanent => true,
192            RestartPolicy::Transient => {
193                !matches!(&event.outcome, Ok(Exit::Normal | Exit::Collected))
194            }
195            RestartPolicy::Temporary => false,
196        };
197        if !eligible {
198            self.fleet
199                .retire(event.proxy)
200                .unwrap_or_else(|_| panic!("unknown supervised nonce"));
201            return ReplacementDecision::Retire;
202        }
203        let candidates = self
204            .fleet
205            .replacements(event.proxy, self.strategy)
206            .unwrap_or_else(|_| panic!("unknown supervised nonce"));
207        if let Err(reason) = self.budget.admit(event.at, candidates.len()) {
208            self.fleet
209                .retire(event.proxy)
210                .unwrap_or_else(|_| panic!("unknown supervised nonce"));
211            return ReplacementDecision::Failed(SupervisionFailure::new(
212                event.proxy,
213                event.outcome,
214                SupervisionFailureReason::RestartDenied(reason),
215            ));
216        }
217        for candidate in &candidates {
218            self.fleet
219                .replacement_requested(candidate.nonce)
220                .unwrap_or_else(|_| unreachable!("candidate belongs to fleet"));
221        }
222        ReplacementDecision::Replace(
223            candidates
224                .into_iter()
225                .map(|candidate| {
226                    Delivery::new(
227                        Recipient::child(candidate.nonce),
228                        ProxyCommand::Replace((self.build)(candidate.index)),
229                    )
230                })
231                .collect(),
232        )
233    }
234
235    fn react_to_failure(
236        &mut self,
237        failure: &SupervisionFailure<B::Addr>,
238    ) -> Result<Become<B::Addr, B::Ph>, B::Error> {
239        Ok(match (self.on_failure)(&mut self.inner, failure)? {
240            Step::Continue => Step::Continue,
241            Step::Goto(never) => match never {},
242            Step::Stop(exit) => Step::Stop(exit),
243        })
244    }
245
246    fn wrap(
247        &mut self,
248        actions: Actions<B::Addr, B::Ph, B::Sends, Births<C>>,
249    ) -> SupervisorActions<B, C> {
250        let born: Vec<_> = actions.creates.iter().map(|create| create.nonce).collect();
251        for create in &actions.creates {
252            self.fleet
253                .register(create.nonce)
254                .unwrap_or_else(|_| panic!("a child birth nonce must be fresh"));
255        }
256        Actions::new(
257            SupervisorSends {
258                behavior: actions.sends,
259                child_observations: ServiceSends::new(
260                    born.into_iter().map(ObserveChild::new).collect(),
261                ),
262                replacement_commands: Vec::new(),
263            },
264            actions
265                .creates
266                .into_iter()
267                .map(|create| Create::new(create.nonce, Proxy::new(create.child), create.kind))
268                .collect(),
269            actions.become_,
270        )
271    }
272}
273
274impl<B, C, A, Ph, Sends> Behavior for Supervisor<B, C>
275where
276    A: Address,
277    Sends: SendAlgebra,
278    B: Behavior<Addr = A, Ph = Ph, Sends = Sends, Birth = Births<C>>,
279    B::Event: ChildEvent + CreationEvent + WorkerCreationEvent,
280    A::Nonce: From<u64>,
281    C: Behavior<Ph = Never, Addr = B::Addr>,
282{
283    type Addr = A;
284    type Msg = B::Msg;
285    type Event = SupervisionEvent<B::Event>;
286    type Sends = SupervisorSends<A, Sends, C>;
287    type Ph = Ph;
288    type Error = B::Error;
289    type Birth = Births<Proxy<C>>;
290
291    fn init(&mut self) -> Result<SupervisorActions<B, C>, B::Error> {
292        let actions = self.inner.init()?;
293        let mut actions = self.wrap(actions);
294        actions.creates.extend(
295            self.fleet
296                .configured_nonces()
297                .enumerate()
298                .map(|(index, nonce)| Create::birth(nonce, Proxy::new((self.build)(index)))),
299        );
300        actions
301            .sends
302            .child_observations
303            .extend(self.fleet.configured_nonces().map(ObserveChild::new));
304        Ok(actions)
305    }
306
307    fn transition(&mut self, event: Self::Event) -> Result<SupervisorActions<B, C>, B::Error> {
308        match event {
309            SupervisionEvent::WorkerStopped(event) => {
310                let decision = self.replacement_decision(&event);
311                match decision {
312                    ReplacementDecision::Retire => Ok(Actions::cont()),
313                    ReplacementDecision::Replace(replacements) => Ok(Actions::new(
314                        SupervisorSends {
315                            behavior: B::Sends::empty(),
316                            child_observations: ServiceSends::empty(),
317                            replacement_commands: replacements,
318                        },
319                        Vec::new(),
320                        Step::Continue,
321                    )),
322                    ReplacementDecision::Failed(failure) => {
323                        Ok(Actions::just(self.react_to_failure(&failure)?))
324                    }
325                }
326            }
327            SupervisionEvent::ChildStopped(event) => {
328                self.fleet
329                    .retire(event.nonce)
330                    .unwrap_or_else(|_| panic!("unknown supervised nonce"));
331                let failure = SupervisionFailure::new(
332                    event.nonce,
333                    event.outcome,
334                    SupervisionFailureReason::StableChildStopped,
335                );
336                Ok(Actions::just(self.react_to_failure(&failure)?))
337            }
338            SupervisionEvent::CreationResolved(event) => {
339                self.fleet.resolve_creation(event.nonce, event.result);
340                if let Some(event) = B::Event::creation_resolved(event) {
341                    let actions = self.inner.transition(event)?;
342                    Ok(self.wrap(actions))
343                } else {
344                    Ok(Actions::cont())
345                }
346            }
347            SupervisionEvent::WorkerCreationResolved(event) => {
348                // Worker realization does not change the stable proxy's
349                // liveness. The typed result remains distinct from a proxy
350                // terminal observation.
351                if let Some(event) = B::Event::worker_creation_resolved(event) {
352                    let actions = self.inner.transition(event)?;
353                    Ok(self.wrap(actions))
354                } else {
355                    Ok(Actions::cont())
356                }
357            }
358            SupervisionEvent::Inner(event) => {
359                let actions = self.inner.transition(event)?;
360                Ok(self.wrap(actions))
361            }
362        }
363    }
364}