Skip to main content

ractor/
rpc.rs

1// Copyright (c) Sean Lawlor
2//
3// This source code is licensed under both the MIT license found in the
4// LICENSE-MIT file in the root directory of this source tree.
5
6//! Remote procedure calls (RPC) are helpful communication primitives to communicate with actors
7//!
8//! There are generally 2 kinds of RPCs, `cast` and `call`, and their definition comes from the
9//! standard [Erlang `gen_server`](https://www.erlang.org/doc/man/gen_server.html#cast-2).
10//! The tl;dr is that `cast` is an send without waiting on a reply while `call` is expecting
11//! a reply from the actor being communicated with.
12//!
13//! ## Examples
14//!
15//! ```rust
16//! use ractor::call;
17//! use ractor::call_t;
18//! use ractor::cast;
19//! use ractor::concurrency::Duration;
20//! use ractor::Actor;
21//! use ractor::ActorProcessingErr;
22//! use ractor::ActorRef;
23//! use ractor::RpcReplyPort;
24//!
25//! struct ExampleActor;
26//!
27//! enum ExampleMessage {
28//!     Cast,
29//!     Call(RpcReplyPort<String>),
30//! }
31//!
32//! #[cfg(feature = "cluster")]
33//! impl ractor::Message for ExampleMessage {}
34//!
35//! #[cfg_attr(feature = "async-trait", ractor::async_trait)]
36//! impl Actor for ExampleActor {
37//!     type Msg = ExampleMessage;
38//!     type State = ();
39//!     type Arguments = ();
40//!
41//!     async fn pre_start(
42//!         &self,
43//!         _myself: ActorRef<Self::Msg>,
44//!         _args: Self::Arguments,
45//!     ) -> Result<Self::State, ActorProcessingErr> {
46//!         println!("Starting");
47//!         Ok(())
48//!     }
49//!
50//!     async fn handle(
51//!         &self,
52//!         _myself: ActorRef<Self::Msg>,
53//!         message: Self::Msg,
54//!         _state: &mut Self::State,
55//!     ) -> Result<(), ActorProcessingErr> {
56//!         match message {
57//!             ExampleMessage::Cast => println!("Cast message"),
58//!             ExampleMessage::Call(reply) => {
59//!                 println!("Call message");
60//!                 let _ = reply.send("a reply".to_string());
61//!             }
62//!         }
63//!         Ok(())
64//!     }
65//! }
66//!
67//! #[tokio::main]
68//! async fn main() {
69//!     let (actor, handle) = Actor::spawn(None, ExampleActor, ())
70//!         .await
71//!         .expect("Failed to startup dummy actor");
72//!
73//!     // send a 1-way message (equivalent patterns)
74//!     actor
75//!         .cast(ExampleMessage::Cast)
76//!         .expect("Failed to send message");
77//!     cast!(actor, ExampleMessage::Cast).expect("Failed to send message");
78//!
79//!     // Send a message to the actor, with an associated reply channel,
80//!     // and wait for the reply from the actor (optionally up to a timeout)
81//!     let _result = actor
82//!         .call(ExampleMessage::Call, Some(Duration::from_millis(100)))
83//!         .await
84//!         .expect("Failed to call actor");
85//!     let _result = call!(actor, ExampleMessage::Call).expect("Failed to call actor");
86//!     let _result =
87//!         call_t!(actor, ExampleMessage::Call, 100).expect("Failed to call actor with timeout");
88//!
89//!     // wait for actor exit
90//!     actor.stop(None);
91//!     handle.await.unwrap();
92//! }
93//! ```
94
95use crate::concurrency::Duration;
96use crate::concurrency::JoinHandle;
97use crate::concurrency::{self};
98use crate::ActorCell;
99use crate::ActorRef;
100use crate::DerivedActorRef;
101use crate::Message;
102use crate::MessagingErr;
103use crate::RpcReplyPort;
104
105pub mod call_result;
106pub use call_result::CallResult;
107#[cfg(test)]
108mod tests;
109
110fn internal_cast<F, TMessage>(sender: F, msg: TMessage) -> Result<(), MessagingErr<TMessage>>
111where
112    F: Fn(TMessage) -> Result<(), MessagingErr<TMessage>>,
113    TMessage: Message,
114{
115    sender(msg)
116}
117
118fn internal_call<F, TMessage, TReply, TMsgBuilder>(
119    sender: F,
120    msg_builder: TMsgBuilder,
121    timeout_option: Option<Duration>,
122) -> impl std::future::Future<Output = Result<CallResult<TReply>, MessagingErr<TMessage>>> + Send
123where
124    F: Fn(TMessage) -> Result<(), MessagingErr<TMessage>>,
125    TMessage: Message,
126    TMsgBuilder: FnOnce(RpcReplyPort<TReply>) -> TMessage,
127    TReply: Send + 'static,
128{
129    let (tx, rx) = concurrency::oneshot();
130    let port: RpcReplyPort<TReply> = match timeout_option {
131        Some(duration) => (tx, duration).into(),
132        None => tx.into(),
133    };
134    let sent = sender(msg_builder(port));
135
136    // wait for the reply
137    async move {
138        sent?;
139        Ok(if let Some(duration) = timeout_option {
140            match crate::concurrency::timeout(duration, rx).await {
141                Ok(Ok(result)) => CallResult::Success(result),
142                Ok(Err(_send_err)) => CallResult::SenderError,
143                Err(_timeout_err) => CallResult::Timeout,
144            }
145        } else {
146            match rx.await {
147                Ok(result) => CallResult::Success(result),
148                Err(_send_err) => CallResult::SenderError,
149            }
150        })
151    }
152}
153
154/// Sends an asynchronous request to the specified actor, ignoring if the
155/// actor is alive or healthy and simply returns immediately
156///
157/// * `actor` - A reference to the [ActorCell] to communicate with
158/// * `msg` - The message to send to the actor
159///
160/// Returns [Ok(())] upon successful send, [Err(MessagingErr)] otherwise
161pub fn cast<TMessage>(actor: &ActorCell, msg: TMessage) -> Result<(), MessagingErr<TMessage>>
162where
163    TMessage: Message,
164{
165    internal_cast(|m| actor.send_message::<TMessage>(m), msg)
166}
167
168/// Sends an asynchronous request to the specified actor, building a one-time
169/// use reply channel and awaiting the result with the specified timeout
170///
171/// * `actor` - A reference to the [ActorCell] to communicate with
172/// * `msg_builder` - The [FnOnce] to construct the message
173/// * `timeout_option` - An optional [Duration] which represents the amount of
174///   time until the operation times out
175///
176/// Returns [Ok(CallResult)] upon successful initial sending with the reply from
177/// the [crate::Actor], [Err(MessagingErr)] if the initial send operation failed
178pub async fn call<TMessage, TReply, TMsgBuilder>(
179    actor: &ActorCell,
180    msg_builder: TMsgBuilder,
181    timeout_option: Option<Duration>,
182) -> Result<CallResult<TReply>, MessagingErr<TMessage>>
183where
184    TMessage: Message,
185    TMsgBuilder: FnOnce(RpcReplyPort<TReply>) -> TMessage,
186    TReply: Send + 'static,
187{
188    internal_call(|m| actor.send_message(m), msg_builder, timeout_option).await
189}
190
191/// Sends an asynchronous request to the specified actors, building a one-time
192/// use reply channel for each actor and awaiting the results with the
193/// specified timeout
194///
195/// * `actors` - A reference to the group of [ActorCell]s to communicate with
196/// * `msg_builder` - The [FnOnce] to construct the message
197/// * `timeout_option` - An optional [Duration] which represents the amount of
198///   time until the operation times out
199///
200/// Returns [Ok(`Vec<CallResult<TReply>>>`)] upon successful initial sending with the reply from
201/// the [crate::Actor]s, [Err(MessagingErr)] if the initial send operation failed
202pub async fn multi_call<TMessage, TReply, TMsgBuilder>(
203    actors: &[ActorRef<TMessage>],
204    msg_builder: TMsgBuilder,
205    timeout_option: Option<Duration>,
206) -> Result<Vec<CallResult<TReply>>, MessagingErr<TMessage>>
207where
208    TMessage: Message,
209    TReply: Send + 'static,
210    TMsgBuilder: Fn(RpcReplyPort<TReply>) -> TMessage,
211{
212    let mut rx_ports = Vec::with_capacity(actors.len());
213    // send to all actors
214    for actor in actors {
215        let (tx, rx) = concurrency::oneshot();
216        let port: RpcReplyPort<TReply> = match timeout_option {
217            Some(duration) => (tx, duration).into(),
218            None => tx.into(),
219        };
220        actor.cast(msg_builder(port))?;
221        rx_ports.push(rx);
222    }
223
224    let mut results = Vec::new();
225    let mut join_set = crate::concurrency::JoinSet::new();
226    for (i, rx) in rx_ports.into_iter().enumerate() {
227        if let Some(duration) = timeout_option {
228            join_set.spawn(async move {
229                (
230                    i,
231                    match crate::concurrency::timeout(duration, rx).await {
232                        Ok(Ok(result)) => CallResult::Success(result),
233                        Ok(Err(_send_err)) => CallResult::SenderError,
234                        Err(_) => CallResult::Timeout,
235                    },
236                )
237            });
238        } else {
239            join_set.spawn(async move {
240                (
241                    i,
242                    match rx.await {
243                        Ok(result) => CallResult::Success(result),
244                        Err(_send_err) => CallResult::SenderError,
245                    },
246                )
247            });
248        }
249    }
250
251    // we threaded the index in order to maintain ordering from the originally called
252    // actors.
253    results.resize_with(join_set.len(), || CallResult::Timeout);
254    while let Some(result) = join_set.join_next().await {
255        match result {
256            Ok((i, r)) => results[i] = r,
257            _ => return Err(MessagingErr::ChannelClosed),
258        }
259    }
260
261    // wait for the replies
262    Ok(results)
263}
264
265/// Send a message asynchronously to another actor, waiting in a new task for the reply
266/// and then forwarding the reply to a followup-actor. If this [CallResult] from the first
267/// actor is not success, the forward is not sent.
268///
269/// * `actor` - A reference to the [ActorCell] to communicate with
270/// * `msg_builder` - The [FnOnce] to construct the message
271/// * `response_forward` - The [ActorCell] to forward the message to
272/// * `forward_mapping` - The [FnOnce] which maps the response from the `actor` [ActorCell]'s reply message
273///   type to the `response_forward` [ActorCell]'s message type
274/// * `timeout_option` - An optional [Duration] which represents the amount of
275///   time until the operation times out
276///
277/// Returns: A [JoinHandle<CallResult<()>>] which can be awaited to see if the
278/// forward was successful or ignored
279#[allow(clippy::type_complexity)]
280pub fn call_and_forward<TMessage, TForwardMessage, TReply, TMsgBuilder, FwdMapFn>(
281    actor: &ActorCell,
282    msg_builder: TMsgBuilder,
283    response_forward: ActorCell,
284    forward_mapping: FwdMapFn,
285    timeout_option: Option<Duration>,
286) -> Result<JoinHandle<CallResult<Result<(), MessagingErr<TForwardMessage>>>>, MessagingErr<TMessage>>
287where
288    TMessage: Message,
289    TReply: Send + 'static,
290    TMsgBuilder: FnOnce(RpcReplyPort<TReply>) -> TMessage,
291    TForwardMessage: Message,
292    FwdMapFn: FnOnce(TReply) -> TForwardMessage + Send + 'static,
293{
294    let (tx, rx) = concurrency::oneshot();
295    let port: RpcReplyPort<TReply> = match timeout_option {
296        Some(duration) => (tx, duration).into(),
297        None => tx.into(),
298    };
299    actor.send_message::<TMessage>(msg_builder(port))?;
300
301    // wait for the reply
302    Ok(crate::concurrency::spawn(async move {
303        if let Some(duration) = timeout_option {
304            match crate::concurrency::timeout(duration, rx).await {
305                Ok(Ok(result)) => CallResult::Success(result),
306                Ok(Err(_send_err)) => CallResult::SenderError,
307                Err(_timeout_err) => CallResult::Timeout,
308            }
309        } else {
310            match rx.await {
311                Ok(result) => CallResult::Success(result),
312                Err(_send_err) => CallResult::SenderError,
313            }
314        }
315        .map(|msg| response_forward.send_message::<TForwardMessage>(forward_mapping(msg)))
316    }))
317}
318
319impl<TMessage> ActorRef<TMessage>
320where
321    TMessage: Message,
322{
323    /// Alias of [cast]
324    pub fn cast(&self, msg: TMessage) -> Result<(), MessagingErr<TMessage>> {
325        cast::<TMessage>(&self.inner, msg)
326    }
327
328    /// Alias of [call]
329    pub async fn call<TReply, TMsgBuilder>(
330        &self,
331        msg_builder: TMsgBuilder,
332        timeout_option: Option<Duration>,
333    ) -> Result<CallResult<TReply>, MessagingErr<TMessage>>
334    where
335        TMsgBuilder: FnOnce(RpcReplyPort<TReply>) -> TMessage,
336        TReply: Send + 'static,
337    {
338        call::<TMessage, TReply, TMsgBuilder>(&self.inner, msg_builder, timeout_option).await
339    }
340
341    /// Alias of [call_and_forward]
342    #[allow(clippy::type_complexity)]
343    pub fn call_and_forward<TReply, TForwardMessage, TMsgBuilder, TFwdMessageBuilder>(
344        &self,
345        msg_builder: TMsgBuilder,
346        response_forward: &ActorRef<TForwardMessage>,
347        forward_mapping: TFwdMessageBuilder,
348        timeout_option: Option<Duration>,
349    ) -> Result<
350        crate::concurrency::JoinHandle<CallResult<Result<(), MessagingErr<TForwardMessage>>>>,
351        MessagingErr<TMessage>,
352    >
353    where
354        TReply: Send + 'static,
355        TMsgBuilder: FnOnce(RpcReplyPort<TReply>) -> TMessage,
356        TForwardMessage: Message,
357        TFwdMessageBuilder: FnOnce(TReply) -> TForwardMessage + Send + 'static,
358    {
359        call_and_forward::<TMessage, TForwardMessage, TReply, TMsgBuilder, TFwdMessageBuilder>(
360            &self.inner,
361            msg_builder,
362            response_forward.inner.clone(),
363            forward_mapping,
364            timeout_option,
365        )
366    }
367}
368
369impl<TMessage> DerivedActorRef<TMessage>
370where
371    TMessage: Message,
372{
373    /// Alias of [cast]
374    pub fn cast(&self, msg: TMessage) -> Result<(), MessagingErr<TMessage>> {
375        internal_cast(|m| self.send_message(m), msg)
376    }
377
378    /// Alias of [call]
379    pub async fn call<TReply, TMsgBuilder>(
380        &self,
381        msg_builder: TMsgBuilder,
382        timeout_option: Option<Duration>,
383    ) -> Result<CallResult<TReply>, MessagingErr<TMessage>>
384    where
385        TMsgBuilder: FnOnce(RpcReplyPort<TReply>) -> TMessage,
386        TReply: Send + 'static,
387    {
388        internal_call(|m| self.send_message(m), msg_builder, timeout_option).await
389    }
390}