#[cfg(feature = "js")]
use wasm_bindgen_test::wasm_bindgen_test;
use std::{
error::Error,
fmt,
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
};
use futures::{FutureExt, future::BoxFuture};
use serde::{Deserialize, Deserializer, Serialize};
use remoc::rtc::{
CallDecision, ChainedMonitor, ClientMonitor, DispatchDecision, DispatchGuard, MonitorableClient,
MonitorableReqReceiver, MonitorableServer, RecvDecision, Req, ReqReceiver, ReqReceiverMonitor, ServeError,
Server, ServerMonitor, ServerShared,
};
use crate::loop_channel;
#[remoc::rtc::remote]
pub trait Counter {
async fn value(self) -> Result<u32, remoc::rtc::CallError>;
async fn value_ref(&self) -> Result<u32, remoc::rtc::CallError>;
async fn increase(&mut self, by: u32) -> Result<(), remoc::rtc::CallError>;
}
pub struct CounterObj {
value: u32,
}
impl Counter for CounterObj {
async fn value(self) -> Result<u32, remoc::rtc::CallError> {
Ok(self.value)
}
async fn value_ref(&self) -> Result<u32, remoc::rtc::CallError> {
Ok(self.value)
}
async fn increase(&mut self, by: u32) -> Result<(), remoc::rtc::CallError> {
self.value += by;
Ok(())
}
}
struct CountingMonitor {
count: Arc<AtomicUsize>,
}
impl<V, R, M> ServerMonitor<V, R, M> for CountingMonitor
where
Req<V, R, M>: Sync,
V: remoc::rtc::ReqEnum,
R: remoc::rtc::ReqEnum,
M: remoc::rtc::ReqEnum,
{
fn pre_dispatch<'a>(
&mut self, req: &'a Result<Option<Req<V, R, M>>, remoc::rch::mpsc::RecvError>,
) -> BoxFuture<'a, DispatchDecision> {
let count = self.count.clone();
async move {
futures::future::ready(()).await;
if matches!(req, Ok(Some(_))) {
count.fetch_add(1, Ordering::SeqCst);
}
DispatchDecision::Pass
}
.boxed()
}
}
#[derive(Debug)]
struct RateLimited;
impl fmt::Display for RateLimited {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "too many requests")
}
}
impl Error for RateLimited {}
struct RateLimitMonitor {
remaining: usize,
}
impl<V, R, M> ServerMonitor<V, R, M> for RateLimitMonitor
where
V: remoc::rtc::ReqEnum,
R: remoc::rtc::ReqEnum,
M: remoc::rtc::ReqEnum,
{
fn pre_dispatch<'a>(
&mut self, req: &'a Result<Option<Req<V, R, M>>, remoc::rch::mpsc::RecvError>,
) -> BoxFuture<'a, DispatchDecision> {
let allow = if matches!(req, Ok(Some(_))) {
if self.remaining == 0 {
false
} else {
self.remaining -= 1;
true
}
} else {
true
};
async move { if allow { DispatchDecision::Pass } else { DispatchDecision::Error(Box::new(RateLimited)) } }
.boxed()
}
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn count() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<CounterClient>().await;
let count = Arc::new(AtomicUsize::new(0));
println!("Creating counter server with counting monitor");
let (mut server, client) = CounterServer::new(CounterObj { value: 0 }, 1);
server.set_monitor(CountingMonitor { count: count.clone() });
a_tx.send(client).await.unwrap();
let client_task = async move {
let mut client = b_rx.recv().await.unwrap().unwrap();
client.increase(20).await.unwrap();
assert_eq!(client.value_ref().await.unwrap(), 20);
client.increase(45).await.unwrap();
assert_eq!(client.value().await.unwrap(), 65);
};
let (_, (obj, res)) = tokio::join!(client_task, server.serve());
res.unwrap();
assert!(obj.is_none());
assert_eq!(count.load(Ordering::SeqCst), 4);
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn rate_limit() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<CounterClient>().await;
println!("Creating counter server with rate-limiting monitor");
let (mut server, client) = CounterServer::new(CounterObj { value: 0 }, 1);
server.set_monitor(RateLimitMonitor { remaining: 1 });
a_tx.send(client).await.unwrap();
let client_task = async move {
let mut client = b_rx.recv().await.unwrap().unwrap();
client.increase(20).await.unwrap();
assert!(client.increase(45).await.is_err());
};
let (_, (obj, res)) = tokio::join!(client_task, server.serve());
assert!(matches!(res, Err(ServeError::Monitor(_))));
assert!(obj.is_some());
}
#[remoc::rtc::remote]
pub trait Worker {
async fn work(&self) -> Result<(), remoc::rtc::CallError>;
}
pub struct WorkerObj;
impl Worker for WorkerObj {
async fn work(&self) -> Result<(), remoc::rtc::CallError> {
remoc::exec::time::sleep(std::time::Duration::from_millis(100)).await;
Ok(())
}
}
struct InFlightGuard {
in_flight: Arc<AtomicUsize>,
}
impl InFlightGuard {
fn new(in_flight: Arc<AtomicUsize>, max: Arc<AtomicUsize>) -> Self {
let now = in_flight.fetch_add(1, Ordering::SeqCst) + 1;
max.fetch_max(now, Ordering::SeqCst);
Self { in_flight }
}
}
impl Drop for InFlightGuard {
fn drop(&mut self) {
self.in_flight.fetch_sub(1, Ordering::SeqCst);
}
}
impl DispatchGuard for InFlightGuard {}
struct InFlightMonitor {
in_flight: Arc<AtomicUsize>,
max: Arc<AtomicUsize>,
}
impl<V, R, M> ServerMonitor<V, R, M> for InFlightMonitor
where
V: remoc::rtc::ReqEnum,
R: remoc::rtc::ReqEnum,
M: remoc::rtc::ReqEnum,
{
fn pre_dispatch<'a>(
&mut self, req: &'a Result<Option<Req<V, R, M>>, remoc::rch::mpsc::RecvError>,
) -> BoxFuture<'a, DispatchDecision> {
let decision = if matches!(req, Ok(Some(_))) {
DispatchDecision::Guard(Box::new(InFlightGuard::new(self.in_flight.clone(), self.max.clone())))
} else {
DispatchDecision::Pass
};
async move { decision }.boxed()
}
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn guard_in_flight() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<WorkerClient>().await;
const N: usize = 5;
let in_flight = Arc::new(AtomicUsize::new(0));
let max = Arc::new(AtomicUsize::new(0));
println!("Creating shared worker server with in-flight guard monitor");
let (mut server, client) = WorkerServerShared::new(Arc::new(WorkerObj), 16);
server.set_monitor(InFlightMonitor { in_flight: in_flight.clone(), max: max.clone() });
a_tx.send(client).await.unwrap();
let client_task = async move {
let client = b_rx.recv().await.unwrap().unwrap();
let calls: Vec<_> = (0..N).map(|_| client.work()).collect();
for res in futures::future::join_all(calls).await {
res.unwrap();
}
};
let (_, res) = tokio::join!(client_task, server.serve(true));
res.unwrap();
assert_eq!(in_flight.load(Ordering::SeqCst), 0);
assert_eq!(max.load(Ordering::SeqCst), N);
}
#[derive(Clone, Serialize)]
pub struct FailToDecode(u32);
impl<'de> Deserialize<'de> for FailToDecode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let _ = u32::deserialize(deserializer)?;
Err(serde::de::Error::custom("intentional decode failure"))
}
}
#[remoc::rtc::remote]
pub trait Decoder {
async fn process(&self, value: FailToDecode) -> Result<(), remoc::rtc::CallError>;
async fn ping(&self) -> Result<u32, remoc::rtc::CallError>;
}
pub struct DecoderObj;
impl Decoder for DecoderObj {
async fn process(&self, _value: FailToDecode) -> Result<(), remoc::rtc::CallError> {
Ok(())
}
async fn ping(&self) -> Result<u32, remoc::rtc::CallError> {
Ok(42)
}
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn incompatible_client_trips() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<DecoderClient>().await;
println!("Creating decoder server with incompatible-client monitor (limit 3)");
let (mut server, client) = DecoderServer::new(DecoderObj, 1);
server.set_monitor(remoc::rtc::monitor::IncompatibleClientMonitor::new().limit(Some(3)).log_level(None));
a_tx.send(client).await.unwrap();
let client_task = async move {
let client = b_rx.recv().await.unwrap().unwrap();
for _ in 0..10 {
assert!(client.process(FailToDecode(0)).await.is_err());
}
};
let (_, (obj, res)) = tokio::join!(client_task, server.serve());
assert!(matches!(res, Err(ServeError::Monitor(_))));
assert!(obj.is_some());
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn incompatible_client_tolerates() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<DecoderClient>().await;
println!("Creating decoder server with incompatible-client monitor (limiting disabled)");
let (mut server, client) = DecoderServer::new(DecoderObj, 1);
server.set_monitor(remoc::rtc::monitor::IncompatibleClientMonitor::new().limit(None).log_level(None));
a_tx.send(client).await.unwrap();
let client_task = async move {
let client = b_rx.recv().await.unwrap().unwrap();
for _ in 0..5 {
assert!(client.process(FailToDecode(0)).await.is_err());
}
assert_eq!(client.ping().await.unwrap(), 42);
};
let (_, (_obj, res)) = tokio::join!(client_task, server.serve());
res.unwrap();
}
struct DecodeFailCounter {
count: Arc<AtomicUsize>,
}
impl<V, R, M> ServerMonitor<V, R, M> for DecodeFailCounter
where
V: remoc::rtc::ReqEnum,
R: remoc::rtc::ReqEnum,
M: remoc::rtc::ReqEnum,
{
fn pre_dispatch<'a>(
&mut self, req: &'a Result<Option<Req<V, R, M>>, remoc::rch::mpsc::RecvError>,
) -> BoxFuture<'a, DispatchDecision> {
let decision = match req {
Err(err) if !err.is_final() => {
self.count.fetch_add(1, Ordering::SeqCst);
DispatchDecision::Drop
}
_ => DispatchDecision::Pass,
};
async move { decision }.boxed()
}
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn incompatible_server_throttles() {
use remoc::exec::time::{Instant, sleep};
use std::time::Duration;
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<DecoderClient>().await;
let server_failures = Arc::new(AtomicUsize::new(0));
println!("Creating decoder server that tolerates and counts decode failures");
let (mut server, client) = DecoderServer::new(DecoderObj, 1);
server.set_monitor(DecodeFailCounter { count: server_failures.clone() });
a_tx.send(client).await.unwrap();
let window = Duration::from_millis(200);
let client_task = async move {
let mut client = b_rx.recv().await.unwrap().unwrap();
client.set_monitor(
remoc::rtc::monitor::IncompatibleServerMonitor::new().limit(Some(1)).window(window).log_level(None),
);
let start = Instant::now();
for _ in 0..5 {
assert!(client.process(FailToDecode(0)).await.is_err(), "decoding should have failed on the server");
}
start.elapsed()
};
let (elapsed, (_obj, res)) = tokio::join!(client_task, server.serve());
res.unwrap();
assert_eq!(server_failures.load(Ordering::SeqCst), 5);
assert!(
elapsed >= window * 2,
"calls to the failing method should have been throttled, but only took {elapsed:?}"
);
sleep(Duration::from_millis(10)).await;
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn incompatible_server_tolerates() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<DecoderClient>().await;
let server_failures = Arc::new(AtomicUsize::new(0));
println!("Creating decoder server with incompatible-server monitor (limiting disabled)");
let (mut server, client) = DecoderServer::new(DecoderObj, 1);
server.set_monitor(DecodeFailCounter { count: server_failures.clone() });
a_tx.send(client).await.unwrap();
let client_task = async move {
let mut client = b_rx.recv().await.unwrap().unwrap();
client.set_monitor(remoc::rtc::monitor::IncompatibleServerMonitor::new().limit(None).log_level(None));
for _ in 0..10 {
assert!(client.process(FailToDecode(0)).await.is_err());
}
assert_eq!(client.ping().await.unwrap(), 42);
};
let (_, (_obj, res)) = tokio::join!(client_task, server.serve());
res.unwrap();
assert_eq!(server_failures.load(Ordering::SeqCst), 10);
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn rate_limit_monitor_throttles() {
use remoc::exec::time::Instant;
use std::{num::NonZeroUsize, time::Duration};
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<CounterClient>().await;
let window = Duration::from_millis(200);
println!("Creating counter server with the rate-limit monitor (2 requests per window)");
let (mut server, client) = CounterServer::new(CounterObj { value: 0 }, 1);
server.set_monitor(
remoc::rtc::monitor::RateLimitMonitor::new(NonZeroUsize::new(2).unwrap(), window).log_level(None),
);
a_tx.send(client).await.unwrap();
let client_task = async move {
let client = b_rx.recv().await.unwrap().unwrap();
let start = Instant::now();
for i in 0..6 {
println!("request {i}");
assert_eq!(client.value_ref().await.unwrap(), 0);
println!("request {i} done");
}
start.elapsed()
};
let (elapsed, (obj, res)) = tokio::join!(client_task, server.serve());
res.unwrap();
assert!(obj.is_some());
assert!(elapsed >= window * 2, "calls should have been rate limited, but only took {elapsed:?}");
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn concurrent_limit_monitor_limits() {
use remoc::exec::time::Instant;
use std::{num::NonZeroUsize, time::Duration};
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<WorkerClient>().await;
const N: usize = 5;
println!("Creating shared worker server with the concurrent-limit monitor (limit 2)");
let (mut server, client) = WorkerServerShared::new(Arc::new(WorkerObj), 16);
server.set_monitor(
remoc::rtc::monitor::ConcurrentLimitMonitor::new(NonZeroUsize::new(2).unwrap()).log_level(None),
);
a_tx.send(client).await.unwrap();
let client_task = async move {
let client = b_rx.recv().await.unwrap().unwrap();
let start = Instant::now();
let calls: Vec<_> = (0..N).map(|_| client.work()).collect();
for res in futures::future::join_all(calls).await {
res.unwrap();
}
start.elapsed()
};
let (elapsed, res) = tokio::join!(client_task, server.serve(true));
res.unwrap();
assert!(
elapsed >= Duration::from_millis(250),
"calls should have been limited to two at a time, but only took {elapsed:?}"
);
}
struct DropMonitor;
impl<V, R, M> ServerMonitor<V, R, M> for DropMonitor
where
V: remoc::rtc::ReqEnum,
R: remoc::rtc::ReqEnum,
M: remoc::rtc::ReqEnum,
{
fn pre_dispatch<'a>(
&mut self, req: &'a Result<Option<Req<V, R, M>>, remoc::rch::mpsc::RecvError>,
) -> BoxFuture<'a, DispatchDecision> {
let is_req = matches!(req, Ok(Some(_)));
async move { if is_req { DispatchDecision::Drop } else { DispatchDecision::Pass } }.boxed()
}
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn chain_server_monitors_both_apply() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<CounterClient>().await;
let count_a = Arc::new(AtomicUsize::new(0));
let count_b = Arc::new(AtomicUsize::new(0));
println!("Creating counter server with two chained counting monitors");
let (mut server, client) = CounterServer::new(CounterObj { value: 0 }, 1);
server.set_monitor(ChainedMonitor(
CountingMonitor { count: count_a.clone() },
CountingMonitor { count: count_b.clone() },
));
a_tx.send(client).await.unwrap();
let client_task = async move {
let mut client = b_rx.recv().await.unwrap().unwrap();
client.increase(20).await.unwrap();
assert_eq!(client.value_ref().await.unwrap(), 20);
client.increase(45).await.unwrap();
assert_eq!(client.value().await.unwrap(), 65);
};
let (_, (obj, res)) = tokio::join!(client_task, server.serve());
res.unwrap();
assert!(obj.is_none());
assert_eq!(count_a.load(Ordering::SeqCst), 4);
assert_eq!(count_b.load(Ordering::SeqCst), 4);
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn chain_server_monitors_short_circuit() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<CounterClient>().await;
let count = Arc::new(AtomicUsize::new(0));
println!("Creating counter server with a dropping monitor chained before a counting monitor");
let (mut server, client) = CounterServer::new(CounterObj { value: 0 }, 1);
server.set_monitor(ChainedMonitor(DropMonitor, CountingMonitor { count: count.clone() }));
a_tx.send(client).await.unwrap();
let client_task = async move {
let mut client = b_rx.recv().await.unwrap().unwrap();
assert!(client.increase(20).await.is_err());
assert!(client.value_ref().await.is_err());
};
let (_, (obj, res)) = tokio::join!(client_task, server.serve());
res.unwrap();
assert!(obj.is_some());
assert_eq!(count.load(Ordering::SeqCst), 0);
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn chain_server_monitors_guards() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<WorkerClient>().await;
const N: usize = 5;
let in_flight_a = Arc::new(AtomicUsize::new(0));
let max_a = Arc::new(AtomicUsize::new(0));
let in_flight_b = Arc::new(AtomicUsize::new(0));
let max_b = Arc::new(AtomicUsize::new(0));
println!("Creating shared worker server with two chained in-flight guard monitors");
let (mut server, client) = WorkerServerShared::new(Arc::new(WorkerObj), 16);
server.set_monitor(ChainedMonitor(
InFlightMonitor { in_flight: in_flight_a.clone(), max: max_a.clone() },
InFlightMonitor { in_flight: in_flight_b.clone(), max: max_b.clone() },
));
a_tx.send(client).await.unwrap();
let client_task = async move {
let client = b_rx.recv().await.unwrap().unwrap();
let calls: Vec<_> = (0..N).map(|_| client.work()).collect();
for res in futures::future::join_all(calls).await {
res.unwrap();
}
};
let (_, res) = tokio::join!(client_task, server.serve(true));
res.unwrap();
assert_eq!(in_flight_a.load(Ordering::SeqCst), 0);
assert_eq!(max_a.load(Ordering::SeqCst), N);
assert_eq!(in_flight_b.load(Ordering::SeqCst), 0);
assert_eq!(max_b.load(Ordering::SeqCst), N);
}
struct CountingClientMonitor {
count: Arc<AtomicUsize>,
}
impl<V, R, M> ClientMonitor<V, R, M> for CountingClientMonitor
where
V: remoc::rtc::ReqEnum,
R: remoc::rtc::ReqEnum,
M: remoc::rtc::ReqEnum,
{
fn pre_call<'a>(&'a self, _req: &'a Req<V, R, M>) -> BoxFuture<'a, CallDecision> {
let count = self.count.clone();
async move {
count.fetch_add(1, Ordering::SeqCst);
CallDecision::Pass
}
.boxed()
}
}
struct DropClientMonitor;
impl<V, R, M> ClientMonitor<V, R, M> for DropClientMonitor
where
V: remoc::rtc::ReqEnum,
R: remoc::rtc::ReqEnum,
M: remoc::rtc::ReqEnum,
{
fn pre_call<'a>(&'a self, _req: &'a Req<V, R, M>) -> BoxFuture<'a, CallDecision> {
async move { CallDecision::Drop }.boxed()
}
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn chain_client_monitors_both_apply() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<CounterClient>().await;
let count_a = Arc::new(AtomicUsize::new(0));
let count_b = Arc::new(AtomicUsize::new(0));
println!("Creating counter server; the received client gets two chained counting monitors");
let (server, client) = CounterServer::new(CounterObj { value: 0 }, 1);
a_tx.send(client).await.unwrap();
let count_a_mon = count_a.clone();
let count_b_mon = count_b.clone();
let client_task = async move {
let mut client = b_rx.recv().await.unwrap().unwrap();
client.set_monitor(ChainedMonitor(
CountingClientMonitor { count: count_a_mon },
CountingClientMonitor { count: count_b_mon },
));
client.increase(20).await.unwrap();
assert_eq!(client.value_ref().await.unwrap(), 20);
client.increase(45).await.unwrap();
assert_eq!(client.value().await.unwrap(), 65);
};
let (_, (_obj, res)) = tokio::join!(client_task, server.serve());
res.unwrap();
assert_eq!(count_a.load(Ordering::SeqCst), 4);
assert_eq!(count_b.load(Ordering::SeqCst), 4);
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn chain_client_monitors_short_circuit() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<CounterClient>().await;
let count = Arc::new(AtomicUsize::new(0));
println!("Creating counter server; the received client drops requests before a counting monitor");
let (server, client) = CounterServer::new(CounterObj { value: 0 }, 1);
a_tx.send(client).await.unwrap();
let count_mon = count.clone();
let client_task = async move {
let mut client = b_rx.recv().await.unwrap().unwrap();
client.set_monitor(ChainedMonitor(DropClientMonitor, CountingClientMonitor { count: count_mon }));
assert!(client.increase(20).await.is_err());
assert!(client.value_ref().await.is_err());
};
let (_, (obj, res)) = tokio::join!(client_task, server.serve());
res.unwrap();
assert!(obj.is_some());
assert_eq!(count.load(Ordering::SeqCst), 0);
}
fn handle_counter_req<Codec>(value: &mut u32, req: CounterReq<Codec>)
where
Codec: remoc::codec::Codec,
{
match req {
Req::Value(CounterReqValue::Value { __reply_tx }) => {
let _ = __reply_tx.send(Ok(*value));
}
Req::Ref(CounterReqRef::ValueRef { __reply_tx }) => {
let _ = __reply_tx.send(Ok(*value));
}
Req::RefMut(CounterReqRefMut::Increase { __reply_tx, by }) => {
*value += by;
let _ = __reply_tx.send(Ok(()));
}
_ => (),
}
}
struct CountingReqMonitor {
count: Arc<AtomicUsize>,
}
impl<V, R, M> ReqReceiverMonitor<V, R, M> for CountingReqMonitor
where
V: remoc::rtc::ReqEnum,
R: remoc::rtc::ReqEnum,
M: remoc::rtc::ReqEnum,
{
fn pre_recv<'a>(
&'a mut self, req: &'a Result<Option<Req<V, R, M>>, remoc::rch::mpsc::RecvError>,
) -> BoxFuture<'a, RecvDecision> {
let count = self.count.clone();
let is_req = matches!(req, Ok(Some(_)));
async move {
if is_req {
count.fetch_add(1, Ordering::SeqCst);
}
RecvDecision::Pass
}
.boxed()
}
}
struct DropReqMonitor;
impl<V, R, M> ReqReceiverMonitor<V, R, M> for DropReqMonitor
where
V: remoc::rtc::ReqEnum,
R: remoc::rtc::ReqEnum,
M: remoc::rtc::ReqEnum,
{
fn pre_recv<'a>(
&'a mut self, req: &'a Result<Option<Req<V, R, M>>, remoc::rch::mpsc::RecvError>,
) -> BoxFuture<'a, RecvDecision> {
let is_req = matches!(req, Ok(Some(_)));
async move { if is_req { RecvDecision::Drop } else { RecvDecision::Pass } }.boxed()
}
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn req_receiver_monitor_counts() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<CounterClient>().await;
let count = Arc::new(AtomicUsize::new(0));
println!("Creating counter request receiver with counting monitor");
let (mut req_rx, client) = CounterReqReceiver::new(1);
req_rx.set_monitor(CountingReqMonitor { count: count.clone() });
a_tx.send(client).await.unwrap();
let client_task = async move {
let mut client = b_rx.recv().await.unwrap().unwrap();
client.increase(20).await.unwrap();
assert_eq!(client.value_ref().await.unwrap(), 20);
client.increase(45).await.unwrap();
assert_eq!(client.value().await.unwrap(), 65);
};
let server_task = async move {
let mut value = 0;
while let Some(req) = req_rx.recv().await.unwrap() {
handle_counter_req(&mut value, req);
}
value
};
let (_, value) = tokio::join!(client_task, server_task);
assert_eq!(count.load(Ordering::SeqCst), 4);
assert_eq!(value, 65);
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn req_receiver_monitor_drops() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<CounterClient>().await;
println!("Creating counter request receiver with dropping monitor");
let (mut req_rx, client) = CounterReqReceiver::new(1);
req_rx.set_monitor(DropReqMonitor);
a_tx.send(client).await.unwrap();
let client_task = async move {
let mut client = b_rx.recv().await.unwrap().unwrap();
assert!(client.increase(20).await.is_err());
assert!(client.value_ref().await.is_err());
};
let server_task = async move {
let mut value = 0;
while let Some(req) = req_rx.recv().await.unwrap() {
handle_counter_req(&mut value, req);
}
value
};
let (_, value) = tokio::join!(client_task, server_task);
assert_eq!(value, 0);
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn req_receiver_monitor_chain_short_circuit() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<CounterClient>().await;
let count = Arc::new(AtomicUsize::new(0));
println!("Creating counter request receiver with a dropping monitor chained before a counting one");
let (mut req_rx, client) = CounterReqReceiver::new(1);
req_rx.set_monitor(ChainedMonitor(DropReqMonitor, CountingReqMonitor { count: count.clone() }));
a_tx.send(client).await.unwrap();
let client_task = async move {
let mut client = b_rx.recv().await.unwrap().unwrap();
assert!(client.increase(20).await.is_err());
assert!(client.value_ref().await.is_err());
};
let server_task = async move {
let mut value = 0;
while let Some(req) = req_rx.recv().await.unwrap() {
handle_counter_req(&mut value, req);
}
value
};
let (_, value) = tokio::join!(client_task, server_task);
assert_eq!(count.load(Ordering::SeqCst), 0);
assert_eq!(value, 0);
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn req_receiver_monitor_stream_filtered() {
use futures::StreamExt;
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<CounterClient>().await;
let count = Arc::new(AtomicUsize::new(0));
println!("Creating counter request receiver stream with a counting then dropping monitor chain");
let (mut req_rx, client) = CounterReqReceiver::new(1);
req_rx.set_monitor(ChainedMonitor(CountingReqMonitor { count: count.clone() }, DropReqMonitor));
a_tx.send(client).await.unwrap();
let client_task = async move {
let mut client = b_rx.recv().await.unwrap().unwrap();
assert!(client.increase(20).await.is_err());
assert!(client.value_ref().await.is_err());
};
let server_task = async move {
let mut stream = req_rx.into_stream();
let mut value = 0;
while let Some(req) = stream.next().await {
handle_counter_req(&mut value, req.unwrap());
}
value
};
let (_, value) = tokio::join!(client_task, server_task);
assert_eq!(count.load(Ordering::SeqCst), 2);
assert_eq!(value, 0);
}