rtc
only.Expand description
Remote trait calling.
This module allows calling of methods on an object located on a remote endpoint via a trait.
By tagging a trait with the remote attribute, server, client and request receiver types are generated for that trait. The client type contains an automatically generated implementation of the trait. Each call is encoded into a request and send to the server. The server accepts requests from the client and calls the requested trait method on an object implementing that trait located on the server. It then transmits the result back to the client.
§Client type
Assuming that the trait is called Trait
, the client will be called TraitClient
.
The client type implements the trait and is remote sendable over a remote channel or any other means to a remote endpoint. All methods called on the client will be forwarded to the server and executed there.
The client type also implements the Client trait which provides a notification when the connection to the server has been lost.
If the trait takes the receiver only by reference (&self
) the client is clonable.
To force the client to be clonable, even if it takes the receiver by mutable reference (&mut self
),
specify the clone
argument to the remote attribute.
§Server types
Assuming the trait is called Trait
, the server names will all start with TraitServer
.
Depending on whether the trait takes the receiver by value (self
), by reference (&self
) or
by mutable reference (&mut self
) different server types are generated:
TraitServer
is always generated,TraitServerRefMut
andTraitServerSharedMut
are generated when the receiver is never taken by value,TraitServerRef
andTraitServerShared
are generated when the receiver is never taken by value and mutable reference.
The purpose of these server types is as follows:
- server implementations with
Send
+Sync
requirement on the target object (recommended):TraitServer
implements Server and takes the target object by value. It will consume the target value when a trait method taking the receiver by value is invoked.TraitServerShared
implements ServerShared and takes an Arc to the target value. It can execute client requests in parallel. The generatedServerShared::serve
implementation returns a future that implementsSend
.TraitServerSharedMut
implements ServerSharedMut and takes an Arc to a local RwLock holding the target object. It can execute const client requests in parallel and mutable requests sequentially. The generatedServerSharedMut::serve
implementation returns a future that implementsSend
.
- server implementations with no
Send
+Sync
requirement on the target object:TraitServerRef
implements ServerRef and takes a reference to the target value.TraitServerRefMut
implements ServerRefMut and takes a mutable reference to the target value.
If unsure, you probably want to use TraitServerSharedMut
, even when the target object will
only be accessed by a single client.
§Request receiver type
Assuming the trait is called Trait
, the request receiver will be called TraitReqReceiver
.
The request receiver is also a server. However, instead of invoking the trait methods on a target object, it allows you to process each request as a message and send the result via a oneshot reply channel.
See ReqReceiver for details.
§Usage
Tag your trait with the remote attribute.
Call new()
on a server type to create a server and corresponding client instance for a
target object, which must implement the trait.
Send the client to a remote endpoint and then call serve()
on the server instance to
start processing requests by the client.
§Error handling
Since a remote trait call can fail due to connection problems, the return type of all trait functions must always be of the Result type. The error type must be able to convert from CallError and thus absorb the remote calling error.
There is no timeout imposed on a remote call, but the underlying chmux connection pings the remote endpoint by default. If the underlying connection fails, all remote calls will automatically fail. You can wrap remote calls using tokio::time::timeout if you need to use per-call timeouts.
§Cancellation
If the client drops the future of a call while it is executing or the connection is interrupted
the trait function on the server is automatically cancelled at the next await
point.
You can apply the #[no_cancel]
attribute to a method to always run it to completion.
§Forward and backward compatibility
All request arguments are packed into an enum case named after the function.
Each argument corresponds to a field with the same name.
Thus it is always safe to add new arguments at the end and apply the #[serde(default)]
attribute to them.
Arguments that are passed by the client but are unknown to the server will be silently discarded.
Also, new functions can be added to the trait without breaking backward compatibility. Calling a non-existent function (for example when the client is newer than the server) will result in a error, but the server will continue serving. It is thus safe to just attempt to call a server function to see if it is available.
§Alternatives
If you just need to expose a function remotely using remote functions is simpler.
§Example
This is a short example only; a fully worked example with client and server split into their own crates is available in the examples directory. This can also be used as a template to get started quickly.
In the following example a trait Counter
is defined and marked as remotely callable.
It is implemented on the CounterObj
struct.
The server creates a CounterObj
and obtains a CounterServerSharedMut
and CounterClient
for it.
The CounterClient
is then sent to the client, which receives it and calls
trait methods on it.
use std::sync::Arc;
use tokio::sync::RwLock;
use remoc::prelude::*;
use remoc::rtc::CallError;
// Custom error type that can convert from CallError.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum IncreaseError {
Overflow,
Call(CallError),
}
impl From<CallError> for IncreaseError {
fn from(err: CallError) -> Self {
Self::Call(err)
}
}
// Trait defining remote service.
#[rtc::remote]
pub trait Counter {
async fn value(&self) -> Result<u32, CallError>;
async fn watch(&mut self) -> Result<rch::watch::Receiver<u32>, CallError>;
#[no_cancel]
async fn increase(&mut self, #[serde(default)] by: u32)
-> Result<(), IncreaseError>;
}
// Server implementation object.
pub struct CounterObj {
value: u32,
watchers: Vec<rch::watch::Sender<u32>>,
}
impl CounterObj {
pub fn new() -> Self {
Self { value: 0, watchers: Vec::new() }
}
}
// Server implementation of trait methods.
#[rtc::async_trait]
impl Counter for CounterObj {
async fn value(&self) -> Result<u32, CallError> {
Ok(self.value)
}
async fn watch(&mut self) -> Result<rch::watch::Receiver<u32>, CallError> {
let (tx, rx) = rch::watch::channel(self.value);
self.watchers.push(tx);
Ok(rx)
}
async fn increase(&mut self, by: u32) -> Result<(), IncreaseError> {
match self.value.checked_add(by) {
Some(new_value) => self.value = new_value,
None => return Err(IncreaseError::Overflow),
}
for watch in &self.watchers {
let _ = watch.send(self.value);
}
Ok(())
}
}
// This would be run on the client.
async fn client(mut rx: rch::base::Receiver<CounterClient>) {
let mut remote_counter = rx.recv().await.unwrap().unwrap();
let mut watch_rx = remote_counter.watch().await.unwrap();
assert_eq!(remote_counter.value().await.unwrap(), 0);
remote_counter.increase(20).await.unwrap();
assert_eq!(remote_counter.value().await.unwrap(), 20);
remote_counter.increase(45).await.unwrap();
assert_eq!(remote_counter.value().await.unwrap(), 65);
assert_eq!(*watch_rx.borrow().unwrap(), 65);
}
// This would be run on the server.
async fn server(mut tx: rch::base::Sender<CounterClient>) {
let mut counter_obj = Arc::new(RwLock::new(CounterObj::new()));
let (server, client) = CounterServerSharedMut::new(counter_obj, 1);
tx.send(client).await.unwrap();
server.serve(true).await.unwrap();
}
Structs§
- Closed
- A future that completes when the server or client has been dropped or the connection between them has been lost.
Enums§
- Call
Error - Call a method on a remotable trait failed.
- OnReq
Receive Error - Determines what should happen on the server-side if receiving an RTC request fails.
- Serve
Error - RTC serving failed.
Traits§
- Client
- Client of a remotable trait.
- ReqReceiver
- A receiver of requests made by the client of a remotable trait.
- Server
- A server of a remotable trait taking the target object by value.
- Server
Base - Base trait shared between all server variants of a remotable trait.
- Server
Ref - A server of a remotable trait taking the target object by reference.
- Server
RefMut - A server of a remotable trait taking the target object by mutable reference.
- Server
Shared - A server of a remotable trait taking the target object by shared reference.
- Server
Shared Mut - A server of a remotable trait taking the target object by shared mutable reference.
Attribute Macros§
- async_
trait - Attribute that must be applied on all implementations of a trait marked with the remote attribute.
- remote
- Denotes a trait as remotely callable and generate a client and servers for it.