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}