Skip to main content

memfault_ssf/
envelope.rs

1//
2// Copyright (c) Memfault, Inc.
3// See License.txt for details
4//! Provide a `struct Envelope<S>` that can be used to wrap messages of any type
5//! M, as long as:
6//! -  S is a service
7//! -  S can handle the type M.
8//!
9//! Because the type `Envelope<S>` is only generic on the service, it enables
10//! grouping together multiple messages of different types.
11//!
12//! This is the magic that makes it possible to deliver messages of multiple
13//! unrelated types (they are not one enum) to services.
14//!
15//! The implementation relies on dynamic dispatch to an internal hidden type
16//! that supports calling `envelope->handle(service)` (an inversion of
17//! responsibility).
18//!
19//! Additionally we add an `AsyncEnvelope<S>`, which provides all the same
20//! mechanisms, but exposes async versions of the send methods.
21
22use std::{
23    any::TypeId,
24    sync::mpsc::{channel, Receiver, Sender},
25    time::Instant,
26};
27
28use futures::future::LocalBoxFuture;
29use tokio::sync::oneshot;
30
31use crate::{AsyncHandler, DeliveryStats, Handler, Message, Service};
32
33/// Wrap a message that can be handled synchronously by `S`.
34pub struct Envelope<S> {
35    message: Box<dyn EnvelopeT<S>>,
36}
37
38impl<S: Service> Envelope<S> {
39    pub fn wrap<M>(message: M) -> Self
40    where
41        M: Message,
42        S: Handler<M>,
43    {
44        Self::wrap_with_reply(message).0
45    }
46
47    pub fn wrap_with_reply<M>(message: M) -> (Self, Receiver<M::Reply>)
48    where
49        M: Message,
50        S: Handler<M>,
51    {
52        let (ack_sender, ack_receiver) = channel();
53        (
54            Envelope {
55                message: Box::new(EnvelopeTImpl {
56                    timestamp: Instant::now(),
57                    message: Some(message),
58                    ack_sender,
59                }),
60            },
61            ack_receiver,
62        )
63    }
64
65    pub fn deliver_to(&mut self, service: &mut S) -> Result<DeliveryStats, &'static str> {
66        self.message.handle(service)
67    }
68
69    pub fn message_type_id(&self) -> Option<TypeId> {
70        self.message.type_id()
71    }
72}
73
74trait EnvelopeT<S: Service>: Send {
75    fn type_id(&self) -> Option<TypeId>;
76    fn handle(&mut self, service: &mut S) -> Result<DeliveryStats, &'static str>;
77}
78struct EnvelopeTImpl<M>
79where
80    M: Message,
81{
82    message: Option<M>,
83    ack_sender: Sender<M::Reply>,
84    timestamp: Instant,
85}
86impl<S: Service + Handler<M>, M: Message> EnvelopeT<S> for EnvelopeTImpl<M> {
87    fn type_id(&self) -> Option<TypeId> {
88        self.message.as_ref().map(|m| m.type_id())
89    }
90
91    fn handle(&mut self, service: &mut S) -> Result<DeliveryStats, &'static str> {
92        if let Some(message) = self.message.take() {
93            let processing_at = Instant::now();
94            let r = service.deliver(message);
95
96            let queued = processing_at - self.timestamp;
97            let processing = Instant::now() - processing_at;
98
99            let _error = self.ack_sender.send(r);
100            Ok(DeliveryStats { queued, processing })
101        } else {
102            Err("Attempt to deliver multiple times")
103        }
104    }
105}
106
107/// Wrap a message that can only be handled asynchronously by `S`.
108pub struct AsyncEnvelope<S> {
109    message: Box<dyn AsyncEnvelopeT<S>>,
110}
111
112impl<S: Service> AsyncEnvelope<S> {
113    pub fn wrap<M>(message: M) -> Self
114    where
115        M: Message,
116        S: AsyncHandler<M>,
117    {
118        Self::wrap_with_reply(message).0
119    }
120
121    pub fn wrap_with_reply<M>(message: M) -> (Self, oneshot::Receiver<M::Reply>)
122    where
123        M: Message,
124        S: AsyncHandler<M>,
125    {
126        let (ack_sender, ack_receiver) = oneshot::channel();
127        (
128            AsyncEnvelope {
129                message: Box::new(AsyncEnvelopeTImpl {
130                    timestamp: Instant::now(),
131                    message: Some(message),
132                    ack_sender: Some(ack_sender),
133                }),
134            },
135            ack_receiver,
136        )
137    }
138
139    pub fn deliver_to<'a>(
140        &'a mut self,
141        service: &'a mut S,
142    ) -> LocalBoxFuture<'a, Result<DeliveryStats, &'static str>> {
143        self.message.handle(service)
144    }
145
146    pub fn message_type_id(&self) -> Option<TypeId> {
147        self.message.type_id()
148    }
149}
150
151trait AsyncEnvelopeT<S: Service>: Send {
152    fn type_id(&self) -> Option<TypeId>;
153    fn handle<'a>(
154        &'a mut self,
155        service: &'a mut S,
156    ) -> LocalBoxFuture<'a, Result<DeliveryStats, &'static str>>;
157}
158struct AsyncEnvelopeTImpl<M>
159where
160    M: Message,
161{
162    message: Option<M>,
163    ack_sender: Option<oneshot::Sender<M::Reply>>,
164    timestamp: Instant,
165}
166impl<S: Service + AsyncHandler<M>, M: Message> AsyncEnvelopeT<S> for AsyncEnvelopeTImpl<M> {
167    fn type_id(&self) -> Option<TypeId> {
168        self.message.as_ref().map(|m| m.type_id())
169    }
170
171    fn handle<'a>(
172        &'a mut self,
173        service: &'a mut S,
174    ) -> LocalBoxFuture<'a, Result<DeliveryStats, &'static str>> {
175        Box::pin(async move {
176            if let Some(message) = self.message.take() {
177                let processing_at = Instant::now();
178                let r = service.deliver_async(message).await;
179
180                let queued = processing_at - self.timestamp;
181                let processing = Instant::now() - processing_at;
182
183                if let Some(ack_sender) = self.ack_sender.take() {
184                    let _error = ack_sender.send(r);
185                }
186                Ok(DeliveryStats { queued, processing })
187            } else {
188                Err("Attempt to deliver multiple times")
189            }
190        })
191    }
192}
193
194/// Either a synchronous or asynchronous envelope, sent through a single
195/// `BoundedTaskMailbox` channel and dispatched accordingly by `async_run`.
196pub enum TaskEnvelope<S> {
197    Sync(Envelope<S>),
198    Async(AsyncEnvelope<S>),
199}
200
201impl<S: Service> TaskEnvelope<S> {
202    pub fn message_type_id(&self) -> Option<TypeId> {
203        match self {
204            TaskEnvelope::Sync(envelope) => envelope.message_type_id(),
205            TaskEnvelope::Async(envelope) => envelope.message_type_id(),
206        }
207    }
208}
209
210impl<S: Service> From<Envelope<S>> for TaskEnvelope<S> {
211    fn from(envelope: Envelope<S>) -> Self {
212        TaskEnvelope::Sync(envelope)
213    }
214}
215
216impl<S: Service> From<AsyncEnvelope<S>> for TaskEnvelope<S> {
217    fn from(envelope: AsyncEnvelope<S>) -> Self {
218        TaskEnvelope::Async(envelope)
219    }
220}