Skip to main content

behavior_actors/
shutdown.rs

1//! Typed graceful-shutdown composition.
2//!
3//! Shutdown is a Bombay policy expressed as an ordinary behavior transition,
4//! not an additional actor-model effect. An interpreter may construct the
5//! shutdown lane, but ingress closure and mailbox ordering remain interpreter
6//! concerns.
7
8use crate::protocol::ShutdownRequested;
9use behavior::Step;
10use behavior::{
11    Actions, Address, Behavior, BirthMode, EventLayer, NoSends, SendEffects, SendLayer,
12};
13
14/// Internal event sum of a behavior that supports graceful shutdown.
15pub type ShutdownEvent<E> = EventLayer<ShutdownRequested, E>;
16
17/// Stop normally when the shutdown lane is received.
18pub struct StopOnShutdown<B> {
19    inner: B,
20}
21
22impl<B> StopOnShutdown<B> {
23    /// Wrap `inner` so the first typed shutdown request stops it normally.
24    /// This wrapper owns every shutdown request it adds. It therefore composes
25    /// over any behavior without requiring the inner event algebra to already
26    /// accept shutdown. When shutdown wrappers are nested, the outermost
27    /// wrapper owns the request.
28    #[must_use]
29    pub const fn new(inner: B) -> Self {
30        Self { inner }
31    }
32
33    /// Recover the original wrapped behavior after this wrapper is consumed.
34    #[must_use]
35    pub fn into_inner(self) -> B {
36        self.inner
37    }
38}
39
40impl<B: Behavior + behavior::BehaviorBase> behavior::BehaviorBase for StopOnShutdown<B> {
41    type Base = B::Base;
42
43    fn base(&self) -> &Self::Base {
44        self.inner.base()
45    }
46}
47
48impl<B: crate::StashStatus> crate::StashStatus for StopOnShutdown<B> {
49    fn stashed_messages(&self) -> usize {
50        self.inner.stashed_messages()
51    }
52}
53
54/// A final shutdown fold. Its sends and fresh creations are retained, while
55/// its become verdict is replaced with `Stop(Normal)`.
56pub type ShutdownReaction<B> = fn(
57    &mut B,
58    ShutdownRequested,
59) -> Actions<
60    behavior::BehaviorAddr<B>,
61    <B as Behavior>::Ph,
62    <B as Behavior>::Sends,
63    <B as Behavior>::Birth,
64>;
65
66/// Run one explicit final fold and then stop normally.
67///
68/// The finalization reaction is infallible because it receives mutable access
69/// to `B`: a fallible reaction could change `B` and then reject the same
70/// shutdown fact, violating transition atomicity. Ordinary delegated `B`
71/// transitions retain `B::Error`.
72///
73/// ```compile_fail,E0308
74/// # struct App;
75/// # impl behavior::Protocol for App { type Addr = behavior::MailAddr; type Msg = (); }
76/// # impl behavior::Behavior for App {
77/// #   type Protocol = Self; type Event = behavior::User<behavior::MailAddr, ()>; type Sends = Vec<behavior::Never>;
78/// #   type Ph = behavior::Never; type Error = behavior::Never; type Birth = behavior::NoBirths;
79/// #   fn transition(&mut self, _: behavior::ActiveTurn, _: Self::Event) -> behavior::BehaviorActed<Self> { Ok(behavior::Actions::cont()) }
80/// # }
81/// fn fallible(_: &mut App, _: behavior_actors::ShutdownRequested) -> behavior::BehaviorActed<App> {
82///     Ok(behavior::Actions::cont())
83/// }
84/// let _ = behavior_actors::FinalizeOnShutdown::new(App, fallible);
85/// ```
86pub struct FinalizeOnShutdown<B: Behavior> {
87    inner: B,
88    finalize: ShutdownReaction<B>,
89}
90
91impl<B: Behavior> FinalizeOnShutdown<B> {
92    /// Wrap `inner` with one pure finalization fold on typed shutdown.
93    ///
94    /// The final fold's sends and creations are preserved and the wrapper then
95    /// stops normally regardless of the fold's continuation verdict.
96    /// Like [`StopOnShutdown`], this wrapper owns the lane it adds; an outer
97    /// shutdown wrapper therefore takes precedence over this reaction.
98    #[must_use]
99    pub const fn new(inner: B, finalize: ShutdownReaction<B>) -> Self {
100        Self { inner, finalize }
101    }
102}
103
104impl<B: Behavior + behavior::BehaviorBase> behavior::BehaviorBase for FinalizeOnShutdown<B> {
105    type Base = B::Base;
106
107    fn base(&self) -> &Self::Base {
108        self.inner.base()
109    }
110}
111
112impl<B> crate::StashStatus for FinalizeOnShutdown<B>
113where
114    B: Behavior + crate::StashStatus,
115{
116    fn stashed_messages(&self) -> usize {
117        self.inner.stashed_messages()
118    }
119}
120
121macro_rules! impl_shutdown_behavior {
122    ($wrapper:ident, $shutdown:expr) => {
123        impl<B, A, Ph, Sends, Br> Behavior for $wrapper<B>
124        where
125            A: Address,
126            Sends: SendEffects + behavior::SendsFor<B::Event>,
127            Br: BirthMode,
128            B: Behavior<Ph = Ph, Sends = Sends, Birth = Br>,
129            B::Protocol: behavior::Protocol<Addr = A>,
130        {
131            type Protocol = B::Protocol;
132            type Event = ShutdownEvent<B::Event>;
133            type Sends = SendLayer<NoSends, Sends>;
134            type Ph = Ph;
135            type Error = B::Error;
136            type Birth = Br;
137
138            fn init(
139                &mut self,
140                _: behavior::InitializationTurn,
141            ) -> Result<Actions<A, Ph, Self::Sends, Br>, B::Error> {
142                behavior::initialize(&mut self.inner)
143                    .map(|actions| actions.map_sends(|inner| SendLayer::new(NoSends, inner)))
144            }
145
146            fn transition(
147                &mut self,
148                _: behavior::ActiveTurn,
149                event: Self::Event,
150            ) -> Result<Actions<A, Ph, Self::Sends, Br>, B::Error> {
151                match event {
152                    EventLayer::Inner(event) => {
153                        behavior::delegate_transition(&mut self.inner, event).map(|actions| {
154                            actions.map_sends(|inner| SendLayer::new(NoSends, inner))
155                        })
156                    }
157                    EventLayer::Owned(request) => $shutdown(self, request),
158                }
159            }
160        }
161    };
162}
163
164impl_shutdown_behavior!(StopOnShutdown, |_this: &mut StopOnShutdown<B>, _request| {
165    Ok(Actions::stop())
166});
167
168impl_shutdown_behavior!(
169    FinalizeOnShutdown,
170    |this: &mut FinalizeOnShutdown<B>, request| {
171        let actions = (this.finalize)(&mut this.inner, request);
172        Ok(actions
173            .map_become(|_| Step::Stop(behavior::Stopped))
174            .map_sends(|inner| SendLayer::new(NoSends, inner)))
175    }
176);