Skip to main content

syncweb_core/sync/
actor.rs

1use std::{error::Error, fmt, future::Future, panic::AssertUnwindSafe};
2
3use n0_future::FutureExt;
4use tokio::sync::{mpsc, oneshot};
5
6/// Error returned when an actor handler panics.
7#[derive(Debug)]
8#[non_exhaustive]
9pub struct ActorPanic(String);
10
11impl ActorPanic {
12    #[must_use]
13    pub const fn new(msg: String) -> Self {
14        Self(msg)
15    }
16}
17
18impl fmt::Display for ActorPanic {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        write!(f, "actor handler panicked: {}", self.0)
21    }
22}
23
24impl Error for ActorPanic {}
25
26/// Marker used to spawn a dedicated asynchronous storage actor.
27#[derive(Clone, Copy, Debug, Default)]
28#[non_exhaustive]
29pub struct Actor;
30
31/// Handle for sending requests to an [`Actor`].
32pub struct ActorHandle<M, R> {
33    sender: mpsc::UnboundedSender<(M, oneshot::Sender<std::result::Result<R, ActorPanic>>)>,
34}
35
36impl<M, R> Clone for ActorHandle<M, R> {
37    fn clone(&self) -> Self {
38        Self {
39            sender: self.sender.clone(),
40        }
41    }
42}
43
44impl<M, R> std::fmt::Debug for ActorHandle<M, R> {
45    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        formatter.debug_struct("ActorHandle").finish_non_exhaustive()
47    }
48}
49
50impl Actor {
51    /// Spawn an actor loop. Handler panics are isolated to the request and do
52    /// not unwind the task or the caller.
53    #[must_use]
54    pub fn spawn<M, R, F, Fut>(handler: F) -> ActorHandle<M, R>
55    where
56        M: Send + 'static,
57        R: Send + 'static,
58        F: Fn(M) -> Fut + Send + Sync + 'static,
59        Fut: Future<Output = R> + Send + 'static,
60    {
61        let (sender, mut receiver) =
62            mpsc::unbounded_channel::<(M, oneshot::Sender<std::result::Result<R, ActorPanic>>)>();
63        tokio::spawn(async move {
64            while let Some((message, response)) = receiver.recv().await {
65                let result = AssertUnwindSafe(handler(message)).catch_unwind().await;
66                match result {
67                    Ok(value) => {
68                        let _ = response.send(Ok(value));
69                    }
70                    Err(panic) => {
71                        let msg = panic
72                            .downcast_ref::<String>()
73                            .cloned()
74                            .or_else(|| panic.downcast_ref::<&str>().map(ToString::to_string))
75                            .unwrap_or_else(|| "unknown panic".to_string());
76                        let _ = response.send(Err(ActorPanic::new(msg)));
77                    }
78                }
79            }
80        });
81        ActorHandle { sender }
82    }
83}
84
85impl<M, R> ActorHandle<M, R>
86where
87    M: Send + 'static,
88    R: Send + 'static,
89{
90    /// Send a message and await its response.
91    /// # Errors
92    ///
93    /// Returns an error if the actor has stopped, the response channel was
94    /// dropped, or the handler panicked.
95    pub async fn request(&self, message: M) -> crate::Result<R> {
96        let (sender, receiver) = oneshot::channel();
97        self.sender
98            .send((message, sender))
99            .map_err(|error| crate::error::SyncwebError::operation("storage actor stopped", error))?;
100        match receiver.await {
101            Ok(Ok(value)) => Ok(value),
102            Ok(Err(panic)) => Err(crate::error::SyncwebError::operation("actor handler panicked", panic)),
103            Err(error) => Err(crate::error::SyncwebError::operation(
104                "storage actor response dropped",
105                error,
106            )),
107        }
108    }
109
110    #[must_use]
111    pub fn is_closed(&self) -> bool {
112        self.sender.is_closed()
113    }
114}