Skip to main content

memfault_ssf/
service_thread.rs

1//
2// Copyright (c) Memfault, Inc.
3// See License.txt for details
4use std::{
5    any::TypeId,
6    sync::mpsc::{channel, Receiver, RecvError, Sender, TryRecvError},
7    thread::spawn,
8};
9
10use log::{error, warn};
11use tokio::runtime::Builder;
12use tokio::sync::mpsc as tokio_mpsc;
13
14use crate::{
15    BoundedMailbox, BoundedTaskMailbox, Envelope, Mailbox, Service, ShutdownServiceMessage,
16    StatsAggregator, TaskService,
17};
18
19/// Run a service inside a dedicated thread using a mpsc::channel to send/receive messages
20pub struct ServiceThread<S: Service> {
21    pub join_handle: ServiceJoinHandle,
22    pub mailbox: Mailbox<S>,
23}
24
25impl<S: Service + Send + 'static> ServiceThread<S> {
26    pub fn spawn_with(service: S) -> Self {
27        let (mailbox, receiver) = Mailbox::create();
28        let (handle_tx, handle_rx) = channel();
29        let join_handle = ServiceJoinHandle::new(handle_rx);
30
31        spawn(move || run(service, receiver, handle_tx));
32
33        ServiceThread {
34            join_handle,
35            mailbox,
36        }
37    }
38
39    pub fn mbox(&self) -> Mailbox<S> {
40        self.mailbox.clone()
41    }
42}
43
44impl<S: Service + 'static> ServiceThread<S> {
45    pub fn spawn_with_init_fn<F: FnOnce() -> S + Send + 'static>(init_fn: F) -> Self {
46        let (mailbox, receiver) = Mailbox::create();
47        let (handle_tx, handle_rx) = channel();
48        let join_handle = ServiceJoinHandle::new(handle_rx);
49
50        spawn(move || {
51            let service = init_fn();
52            run(service, receiver, handle_tx)
53        });
54
55        ServiceThread {
56            join_handle,
57            mailbox,
58        }
59    }
60}
61
62pub struct BoundedServiceThread<S: Service> {
63    pub join_handle: ServiceJoinHandle,
64    pub mailbox: BoundedMailbox<S>,
65}
66
67impl<S: Service + Send + 'static> BoundedServiceThread<S> {
68    pub fn spawn_with(service: S, channel_size: usize) -> Self {
69        let (mailbox, receiver) = BoundedMailbox::create(channel_size);
70        let (handle_tx, handle_rx) = channel();
71        let join_handle = ServiceJoinHandle::new(handle_rx);
72
73        spawn(move || run(service, receiver, handle_tx));
74
75        BoundedServiceThread {
76            join_handle,
77            mailbox,
78        }
79    }
80
81    pub fn mbox(&self) -> BoundedMailbox<S> {
82        self.mailbox.clone()
83    }
84}
85
86impl<S: Service + 'static> BoundedServiceThread<S> {
87    pub fn spawn_with_init_fn<F: FnOnce() -> S + Send + 'static>(
88        init_fn: F,
89        channel_size: usize,
90    ) -> Self {
91        let (mailbox, receiver) = BoundedMailbox::create(channel_size);
92        let (handle_tx, handle_rx) = channel();
93        let join_handle = ServiceJoinHandle::new(handle_rx);
94
95        spawn(move || {
96            let service = init_fn();
97            run(service, receiver, handle_tx)
98        });
99
100        BoundedServiceThread {
101            join_handle,
102            mailbox,
103        }
104    }
105}
106
107fn run<S: Service>(
108    mut service: S,
109    receiver: Receiver<Envelope<S>>,
110    join_handle_tx: Sender<Result<StatsAggregator, &'static str>>,
111) {
112    let mut stats_aggregator = StatsAggregator::new();
113    for mut envelope in receiver {
114        let type_id = envelope.message_type_id();
115        match envelope.deliver_to(&mut service) {
116            Err(_e) => {
117                // Delivery failed - probably "attempt to deliver twice" - should never happen.
118                if let Err(e) = join_handle_tx.send(Err("Message delivery failed")) {
119                    error!("ssf delivery failed: {e}");
120                }
121                return;
122            }
123            Ok(stats) => {
124                stats_aggregator.add(&stats);
125            }
126        }
127        if type_id == Some(TypeId::of::<ShutdownServiceMessage>()) {
128            break;
129        }
130    }
131
132    // drop service, and send message indicating the the service thread is closed
133    drop(service);
134    if let Err(e) = join_handle_tx.send(Ok(stats_aggregator)) {
135        error!("ssf delivery failed: {e}");
136    }
137}
138
139pub struct BoundedTaskServiceThread<S: TaskService> {
140    pub join_handle: ServiceJoinHandle,
141    pub mailbox: BoundedTaskMailbox<S>,
142}
143
144impl<S: TaskService + Send + 'static> BoundedTaskServiceThread<S> {
145    pub fn spawn_with(service: S, channel_size: usize) -> Self {
146        let (mailbox, receiver) = BoundedTaskMailbox::create(channel_size);
147        let (handle_tx, handle_rx) = channel();
148        let join_handle = ServiceJoinHandle::new(handle_rx);
149
150        spawn(move || {
151            let runtime = match Builder::new_current_thread()
152                .enable_io()
153                .enable_time()
154                .build()
155            {
156                Ok(runtime) => runtime,
157                Err(e) => {
158                    error!("Failed to build task service runtime: {}", e);
159                    if let Err(send_err) =
160                        handle_tx.send(Err("Failed to start task service runtime"))
161                    {
162                        error!(
163                            "Failed to send task service failure notification: {}",
164                            send_err
165                        );
166                    }
167                    return;
168                }
169            };
170            runtime.block_on(async_run(service, receiver, handle_tx));
171        });
172
173        BoundedTaskServiceThread {
174            join_handle,
175            mailbox,
176        }
177    }
178
179    pub fn mbox(&self) -> BoundedTaskMailbox<S> {
180        self.mailbox.clone()
181    }
182}
183
184impl<S: TaskService + 'static> BoundedTaskServiceThread<S> {
185    pub fn spawn_with_init_fn<I>(init_fn: I, channel_size: usize) -> Self
186    where
187        I: FnOnce() -> S + Send + 'static,
188    {
189        let (mailbox, receiver) = BoundedTaskMailbox::create(channel_size);
190        let (handle_tx, handle_rx) = channel();
191        let join_handle = ServiceJoinHandle::new(handle_rx);
192
193        spawn(move || {
194            let service = init_fn();
195            let runtime = Builder::new_current_thread()
196                .enable_io()
197                .enable_time()
198                .build();
199            match runtime {
200                Ok(runtime) => runtime.block_on(async_run(service, receiver, handle_tx)),
201                Err(e) => error!("Failed to spawn service: {}", e),
202            }
203        });
204
205        BoundedTaskServiceThread {
206            join_handle,
207            mailbox,
208        }
209    }
210}
211
212async fn async_run<S>(
213    mut service: S,
214    mut receiver: tokio_mpsc::Receiver<Envelope<S>>,
215    join_handle_tx: Sender<Result<StatsAggregator, &'static str>>,
216) where
217    S: TaskService,
218{
219    let mut stats_aggregator = StatsAggregator::new();
220
221    if let Err(e) = service.init().await {
222        error!("Failed to initialize task: {}", e);
223        return;
224    }
225
226    loop {
227        tokio::select! {
228            Some(mut envelope) = receiver.recv() => {
229                let type_id = envelope.message_type_id();
230                match envelope.deliver_to(&mut service) {
231                    Err(_e) => {
232                        // Delivery failed - probably "attempt to deliver twice" - should never happen.
233                        if let Err(e) = join_handle_tx.send(Err("Message delivery failed")) {
234                            error!("ssf delivery failed: {e}");
235                        }
236                        return;
237                    }
238                    Ok(stats) => {
239                        stats_aggregator.add(&stats);
240                    }
241                }
242                if type_id == Some(TypeId::of::<ShutdownServiceMessage>()) {
243                    break;
244                }
245            },
246            result = service.run_task() => {
247                if let Err(e) = result {
248                    warn!("Service task failed: {}", e);
249                }
250            }
251        };
252    }
253
254    // drop service, and send message indicating the the service thread is closed
255    drop(service);
256    if let Err(e) = join_handle_tx.send(Ok(stats_aggregator)) {
257        error!("ssf delivery failed: {e}");
258    }
259}
260
261pub struct ServiceJoinHandle {
262    rx: Receiver<Result<StatsAggregator, &'static str>>,
263}
264
265impl ServiceJoinHandle {
266    pub fn new(rx: Receiver<Result<StatsAggregator, &'static str>>) -> Self {
267        Self { rx }
268    }
269
270    pub fn join(&mut self) -> Result<StatsAggregator, ServiceJoinHandleError> {
271        self.rx
272            .recv()?
273            .map_err(ServiceJoinHandleError::ServiceFailed)
274    }
275
276    pub fn try_join(&mut self) -> Result<StatsAggregator, ServiceJoinHandleError> {
277        self.rx
278            .try_recv()?
279            .map_err(ServiceJoinHandleError::ServiceFailed)
280    }
281}
282
283#[derive(Debug, PartialEq, Eq)]
284pub enum ServiceJoinHandleError {
285    ServiceStopped,
286    ServiceRunning,
287    ServiceFailed(&'static str),
288}
289
290impl From<RecvError> for ServiceJoinHandleError {
291    fn from(_value: RecvError) -> Self {
292        // recv() can only fail if the sender is dropped
293        Self::ServiceStopped
294    }
295}
296
297impl From<TryRecvError> for ServiceJoinHandleError {
298    fn from(value: TryRecvError) -> Self {
299        match value {
300            TryRecvError::Empty => Self::ServiceRunning,
301            TryRecvError::Disconnected => Self::ServiceStopped,
302        }
303    }
304}
305
306#[cfg(test)]
307mod test {
308    use super::*;
309
310    #[test]
311    fn test_join_handle_error_conversion() {
312        assert_eq!(
313            ServiceJoinHandleError::from(RecvError),
314            ServiceJoinHandleError::ServiceStopped
315        );
316        assert_eq!(
317            ServiceJoinHandleError::from(TryRecvError::Empty),
318            ServiceJoinHandleError::ServiceRunning
319        );
320        assert_eq!(
321            ServiceJoinHandleError::from(TryRecvError::Disconnected),
322            ServiceJoinHandleError::ServiceStopped
323        );
324    }
325
326    #[test]
327    fn test_try_join() {
328        let (tx, rx) = channel();
329        let mut join_handle = ServiceJoinHandle::new(rx);
330
331        assert!(matches!(
332            join_handle.try_join(),
333            Err(ServiceJoinHandleError::ServiceRunning)
334        ));
335
336        tx.send(Ok(StatsAggregator::new())).unwrap();
337        assert!(join_handle.try_join().is_ok());
338    }
339
340    #[test]
341    fn test_join() {
342        let (tx, rx) = channel();
343        let mut join_handle = ServiceJoinHandle::new(rx);
344
345        tx.send(Ok(StatsAggregator::new())).unwrap();
346
347        assert!(join_handle.join().is_ok());
348    }
349
350    #[test]
351    fn test_join_dropped() {
352        let (tx, rx) = channel();
353        let mut join_handle = ServiceJoinHandle::new(rx);
354
355        drop(tx);
356        assert!(matches!(
357            join_handle.join(),
358            Err(ServiceJoinHandleError::ServiceStopped)
359        ));
360    }
361}