Skip to main content

behavior/
receive_timeout.rs

1//! Pure receive-inactivity composition. Relative scheduling is a request to
2//! the emitting actor's interpreter and never observes a clock here.
3
4use std::time::Duration;
5
6use crate::Step;
7use crate::behavior::{
8    Actions, Address, Behavior, BirthMode, SendAlgebra, SendProduct, ServiceSends, UserEvent,
9};
10use crate::protocol::{
11    ChildEvent, ChildStopped, PeerEvent, PeerStopped, ScheduleAfter, ShutdownEvent,
12    ShutdownRequested, TimeEvent, TimerElapsed, TimerGeneration, TimerId, WorkerEvent,
13    WorkerStopped,
14};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum ReceiveTimeoutEvent<E> {
18    Inner(E),
19    Elapsed(TimerElapsed),
20}
21
22impl<E> TimeEvent for ReceiveTimeoutEvent<E> {
23    fn time_reached(event: TimerElapsed) -> Option<Self> {
24        Some(Self::Elapsed(event))
25    }
26}
27
28impl<E: UserEvent> UserEvent for ReceiveTimeoutEvent<E> {
29    type Addr = E::Addr;
30    type Message = E::Message;
31
32    fn user(from: Self::Addr, message: Self::Message) -> Self {
33        Self::Inner(E::user(from, message))
34    }
35
36    fn into_user(self) -> Result<crate::User<Self::Addr, Self::Message>, Self> {
37        match self {
38            Self::Inner(event) => event.into_user().map_err(Self::Inner),
39            elapsed @ Self::Elapsed(_) => Err(elapsed),
40        }
41    }
42}
43
44impl<E: PeerEvent<A>, A: Address> PeerEvent<A> for ReceiveTimeoutEvent<E> {
45    fn peer_stopped(event: PeerStopped<A>) -> Option<Self> {
46        E::peer_stopped(event).map(Self::Inner)
47    }
48}
49
50impl<E: ChildEvent<A>, A: Address> ChildEvent<A> for ReceiveTimeoutEvent<E> {
51    fn child_stopped(event: ChildStopped<A>) -> Option<Self> {
52        E::child_stopped(event).map(Self::Inner)
53    }
54}
55
56impl<E: WorkerEvent<A>, A: Address> WorkerEvent<A> for ReceiveTimeoutEvent<E> {
57    fn worker_stopped(event: WorkerStopped<A>) -> Option<Self> {
58        E::worker_stopped(event).map(Self::Inner)
59    }
60}
61
62impl<E: ShutdownEvent> ShutdownEvent for ReceiveTimeoutEvent<E> {
63    fn shutdown_requested(event: ShutdownRequested) -> Option<Self> {
64        E::shutdown_requested(event).map(Self::Inner)
65    }
66}
67
68/// A controlled receive-timeout failure.
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub enum ReceiveTimeoutError<E> {
71    /// The inner fold or timeout reaction failed.
72    Inner(E),
73    /// Advancing the timer generation would make a stale delivery live again.
74    ///
75    /// This is detected after the successful continuing inner user fold: the
76    /// inner state mutation has occurred, but its returned sends and creations
77    /// are not emitted because the composed transition fails. Bombay behavior
78    /// folds are not transactional and wrappers cannot roll back inner state.
79    GenerationExhausted,
80}
81
82pub type ReceiveTimeoutReaction<B> = fn(
83    &mut B,
84) -> Result<
85    Actions<
86        <B as Behavior>::Addr,
87        <B as Behavior>::Ph,
88        <B as Behavior>::Sends,
89        <B as Behavior>::Birth,
90    >,
91    <B as Behavior>::Error,
92>;
93
94pub type ReceiveTimeoutSends<B> = SendProduct<<B as Behavior>::Sends, ServiceSends<ScheduleAfter>>;
95
96pub type ReceiveTimeoutActions<B> = Actions<
97    <B as Behavior>::Addr,
98    <B as Behavior>::Ph,
99    ReceiveTimeoutSends<B>,
100    <B as Behavior>::Birth,
101>;
102
103/// A pure one-notification-per-idle-period receive timeout.
104///
105/// Only successful user communications are activity. Timer, peer, child,
106/// worker, and shutdown service events compose through this wrapper but never
107/// rearm it. A matching timeout consumes the live generation before invoking
108/// the reaction; if that reaction continues, the timeout remains unarmed until
109/// another successful continuing user communication.
110pub struct ReceiveTimeout<B: Behavior> {
111    inner: B,
112    id: TimerId,
113    after: Duration,
114    live: Option<TimerGeneration>,
115    last_issued: Option<TimerGeneration>,
116    on_elapsed: ReceiveTimeoutReaction<B>,
117}
118
119impl<B: Behavior> ReceiveTimeout<B> {
120    #[must_use]
121    pub fn new(
122        inner: B,
123        id: TimerId,
124        after: Duration,
125        on_elapsed: ReceiveTimeoutReaction<B>,
126    ) -> Self {
127        Self {
128            inner,
129            id,
130            after,
131            live: None,
132            last_issued: None,
133            on_elapsed,
134        }
135    }
136
137    #[must_use]
138    pub fn inner(&self) -> &B {
139        &self.inner
140    }
141
142    fn schedule(&mut self) -> Result<ServiceSends<ScheduleAfter>, ReceiveTimeoutError<B::Error>> {
143        let generation = match self.last_issued {
144            None => TimerGeneration(0),
145            Some(TimerGeneration(generation)) => TimerGeneration(
146                generation
147                    .checked_add(1)
148                    .ok_or(ReceiveTimeoutError::GenerationExhausted)?,
149            ),
150        };
151        self.last_issued = Some(generation);
152        self.live = Some(generation);
153        Ok(ServiceSends::one(ScheduleAfter {
154            id: self.id,
155            generation,
156            after: self.after,
157        }))
158    }
159
160    fn wrap(
161        actions: Actions<B::Addr, B::Ph, B::Sends, B::Birth>,
162        own: ServiceSends<ScheduleAfter>,
163    ) -> ReceiveTimeoutActions<B> {
164        Actions {
165            sends: SendProduct {
166                inner: actions.sends,
167                own,
168            },
169            creates: actions.creates,
170            become_: actions.become_,
171        }
172    }
173
174    fn terminal(actions: &Actions<B::Addr, B::Ph, B::Sends, B::Birth>) -> bool {
175        matches!(actions.become_, Step::Stop(_))
176    }
177}
178
179impl<B, A, Ph, Sends, Br> Behavior for ReceiveTimeout<B>
180where
181    A: Address + Send,
182    Sends: SendAlgebra,
183    Br: BirthMode,
184    B: Behavior<Addr = A, Ph = Ph, Sends = Sends, Birth = Br> + Send,
185    B::Event: TimeEvent + Send,
186    B::Msg: Send,
187{
188    type Addr = A;
189    type Msg = B::Msg;
190    type Event = ReceiveTimeoutEvent<B::Event>;
191    type Sends = ReceiveTimeoutSends<B>;
192    type Ph = Ph;
193    type Error = ReceiveTimeoutError<B::Error>;
194    type Birth = Br;
195
196    async fn init(&mut self) -> Result<ReceiveTimeoutActions<B>, Self::Error> {
197        let actions = self
198            .inner
199            .init()
200            .await
201            .map_err(ReceiveTimeoutError::Inner)?;
202        let own = if Self::terminal(&actions) {
203            self.live = None;
204            ServiceSends::empty()
205        } else {
206            self.schedule()?
207        };
208        Ok(Self::wrap(actions, own))
209    }
210
211    async fn step(&mut self, event: Self::Event) -> Result<ReceiveTimeoutActions<B>, Self::Error> {
212        match event {
213            ReceiveTimeoutEvent::Elapsed(elapsed)
214                if elapsed.id == self.id && self.live == Some(elapsed.generation) =>
215            {
216                self.live = None;
217                let actions =
218                    (self.on_elapsed)(&mut self.inner).map_err(ReceiveTimeoutError::Inner)?;
219                Ok(Self::wrap(actions, ServiceSends::empty()))
220            }
221            ReceiveTimeoutEvent::Elapsed(elapsed) if elapsed.id == self.id => Ok(Actions::cont()),
222            ReceiveTimeoutEvent::Elapsed(elapsed) => {
223                let Some(inner) = B::Event::time_reached(elapsed) else {
224                    return Ok(Actions::cont());
225                };
226                let actions = self
227                    .inner
228                    .step(inner)
229                    .await
230                    .map_err(ReceiveTimeoutError::Inner)?;
231                if Self::terminal(&actions) {
232                    self.live = None;
233                }
234                Ok(Self::wrap(actions, ServiceSends::empty()))
235            }
236            ReceiveTimeoutEvent::Inner(event) => match event.into_user() {
237                Ok(user) => {
238                    let event = B::Event::user(user.from, user.message);
239                    let actions = self
240                        .inner
241                        .step(event)
242                        .await
243                        .map_err(ReceiveTimeoutError::Inner)?;
244                    let own = if Self::terminal(&actions) {
245                        self.live = None;
246                        ServiceSends::empty()
247                    } else {
248                        self.schedule()?
249                    };
250                    Ok(Self::wrap(actions, own))
251                }
252                Err(service) => {
253                    let actions = self
254                        .inner
255                        .step(service)
256                        .await
257                        .map_err(ReceiveTimeoutError::Inner)?;
258                    if Self::terminal(&actions) {
259                        self.live = None;
260                    }
261                    Ok(Self::wrap(actions, ServiceSends::empty()))
262                }
263            },
264        }
265    }
266}
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271    use crate::{Acted, Base, Delivery, MailAddr, Never, NoBirths, State, User};
272
273    struct Count(u8);
274
275    impl State for Count {
276        type Addr = MailAddr;
277        type Msg = ();
278
279        fn handle(
280            &mut self,
281            _from: MailAddr,
282            (): (),
283        ) -> Acted<MailAddr, Never, Vec<Delivery<MailAddr, Never>>, NoBirths, Never> {
284            self.0 += 1;
285            Ok(Actions::cont())
286        }
287    }
288
289    type Inner = Base<Count>;
290
291    fn elapsed(
292        _inner: &mut Inner,
293    ) -> Acted<MailAddr, Never, Vec<Delivery<MailAddr, Never>>, NoBirths, Never> {
294        Ok(Actions::cont())
295    }
296
297    #[tokio::test]
298    async fn exhaustion_follows_the_successful_inner_fold_without_emitting_its_actions() {
299        let mut timeout = ReceiveTimeout::new(
300            Base::new(Count(0)),
301            TimerId(0),
302            Duration::from_secs(1),
303            elapsed,
304        );
305        timeout.init().await.unwrap();
306        timeout.last_issued = Some(TimerGeneration(u64::MAX));
307
308        let result = timeout
309            .step(ReceiveTimeoutEvent::Inner(User::user(MailAddr(1), ())))
310            .await;
311
312        assert!(matches!(
313            result,
314            Err(ReceiveTimeoutError::GenerationExhausted)
315        ));
316        assert_eq!(timeout.inner().state().0, 1);
317        assert_eq!(timeout.live, Some(TimerGeneration(0)));
318    }
319}