task_local context over context objects

- interner impls logically separate from API in orchid-base (default host interner still in base for testing)
- error reporting, logging, and a variety of other features passed down via context in extension, not yet in host to maintain library-ish profile, should consider options
- no global spawn mechanic, the host has a spawn function but extensions only get a stash for enqueuing async work in sync callbacks which is then explicitly, manually, and with strict order popped and awaited
- still deadlocks nondeterministically for some ungodly reason
This commit is contained in:
2026-01-01 14:54:29 +00:00
parent 06debb3636
commit 32d6237dc5
92 changed files with 2507 additions and 2223 deletions

View File

@@ -1,12 +1,11 @@
use std::cell::RefCell;
use std::collections::VecDeque;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::{Pin, pin};
use std::rc::Rc;
use std::task::Poll;
use std::{io, mem};
use async_fn_stream::stream;
use async_fn_stream::try_stream;
use bound::Bound;
use derive_destructure::destructure;
use futures::channel::mpsc::{self, Receiver, Sender, channel};
@@ -14,23 +13,29 @@ use futures::channel::oneshot;
use futures::future::LocalBoxFuture;
use futures::lock::{Mutex, MutexGuard};
use futures::{
AsyncRead, AsyncWrite, AsyncWriteExt, SinkExt, Stream, StreamExt, stream, stream_select,
AsyncRead, AsyncWrite, AsyncWriteExt, FutureExt, SinkExt, Stream, StreamExt, stream_select,
};
use hashbrown::HashMap;
use orchid_api_traits::{Channel, Decode, Encode, Request, UnderRoot};
use orchid_api_traits::{Decode, Encode, Request, UnderRoot};
use crate::localset::LocalSet;
#[must_use = "Receipts indicate that a required action has been performed within a function. \
Most likely this should be returned somewhere."]
pub struct Receipt<'a>(PhantomData<&'a mut ()>);
impl Receipt<'_> {
/// Only call this function from a custom implementation of [RepWriter]
pub fn _new() -> Self { Self(PhantomData) }
}
/// Write guard to outbound for the purpose of serializing a request. Only one
/// can exist at a time. Dropping this object should panic.
pub trait ReqWriter {
pub trait ReqWriter<'a> {
/// Access to the underlying channel. This may be buffered.
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite>;
/// Finalize the request, release the outbound channel, then queue for the
/// reply on the inbound channel.
fn send(self: Box<Self>) -> LocalBoxFuture<'static, Box<dyn RepReader>>;
fn send(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Box<dyn RepReader<'a> + 'a>>>;
}
/// Write guard to inbound for the purpose of deserializing a reply. While held,
@@ -40,49 +45,106 @@ pub trait ReqWriter {
/// synchronously, because the API isn't cancellation safe in general so it is a
/// programmer error in all cases to drop an object related to it without proper
/// cleanup.
pub trait RepReader {
pub trait RepReader<'a> {
/// Access to the underlying channel. The length of the message is inferred
/// from the number of bytes read so this must not be buffered.
fn reader(&mut self) -> Pin<&mut dyn AsyncRead>;
/// Finish reading the request
fn finish(self: Box<Self>) -> LocalBoxFuture<'static, ()>;
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, ()>;
}
/// Write guard to outbound for the purpose of serializing a notification.
///
/// Dropping this object should panic for the same reason [RepReader] panics
pub trait MsgWriter {
pub trait MsgWriter<'a> {
/// Access to the underlying channel. This may be buffered.
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite>;
/// Send the notification
fn finish(self: Box<Self>) -> LocalBoxFuture<'static, ()>;
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<()>>;
}
/// For initiating outbound requests and notifications
pub trait Client {
fn start_request(&self) -> LocalBoxFuture<'_, Box<dyn ReqWriter>>;
fn start_notif(&self) -> LocalBoxFuture<'_, Box<dyn MsgWriter>>;
fn start_request(&self) -> LocalBoxFuture<'_, io::Result<Box<dyn ReqWriter<'_> + '_>>>;
fn start_notif(&self) -> LocalBoxFuture<'_, io::Result<Box<dyn MsgWriter<'_> + '_>>>;
}
impl<T: Client + ?Sized> ClientExt for T {}
/// Extension trait with convenience methods that handle outbound request and
/// notif lifecycle and typing
#[allow(async_fn_in_trait)]
pub trait ClientExt<CH: Channel>: Client {
async fn request<T: Request + UnderRoot<Root = CH::Req>>(&self, t: T) -> T::Response {
let mut req = self.start_request().await;
t.into_root().encode(req.writer().as_mut()).await;
let mut rep = req.send().await;
pub trait ClientExt: Client {
async fn request<T: Request + UnderRoot<Root: Encode>>(&self, t: T) -> io::Result<T::Response> {
let mut req = self.start_request().await?;
t.into_root().encode(req.writer().as_mut()).await?;
let mut rep = req.send().await?;
let response = T::Response::decode(rep.reader()).await;
rep.finish().await;
response
}
async fn notify<T: UnderRoot<Root = CH::Notif>>(&self, t: T) {
let mut notif = self.start_notif().await;
t.into_root().encode(notif.writer().as_mut()).await;
notif.finish().await;
async fn notify<T: UnderRoot<Root: Encode>>(&self, t: T) -> io::Result<()> {
let mut notif = self.start_notif().await?;
t.into_root().encode(notif.writer().as_mut()).await?;
notif.finish().await?;
Ok(())
}
}
pub trait ReqReader<'a> {
fn reader(&mut self) -> Pin<&mut dyn AsyncRead>;
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, Box<dyn ReqHandle<'a> + 'a>>;
}
impl<'a, T: ReqReader<'a> + ?Sized> ReqReaderExt<'a> for T {}
#[allow(async_fn_in_trait)]
pub trait ReqReaderExt<'a>: ReqReader<'a> {
async fn read_req<R: Decode>(&mut self) -> io::Result<R> { R::decode(self.reader()).await }
async fn reply<R: Request>(
self: Box<Self>,
req: impl Evidence<R>,
rep: &R::Response,
) -> io::Result<Receipt<'a>> {
self.finish().await.reply(req, rep).await
}
async fn start_reply(self: Box<Self>) -> io::Result<Box<dyn RepWriter<'a> + 'a>> {
self.finish().await.start_reply().await
}
}
pub trait ReqHandle<'a> {
fn start_reply(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Box<dyn RepWriter<'a> + 'a>>>;
}
impl<'a, T: ReqHandle<'a> + ?Sized> ReqHandleExt<'a> for T {}
#[allow(async_fn_in_trait)]
pub trait ReqHandleExt<'a>: ReqHandle<'a> {
async fn reply<Req: Request>(
self: Box<Self>,
_: impl Evidence<Req>,
rep: &Req::Response,
) -> io::Result<Receipt<'a>> {
let mut reply = self.start_reply().await?;
rep.encode(reply.writer()).await?;
reply.finish().await
}
}
pub trait RepWriter<'a> {
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite>;
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Receipt<'a>>>;
}
pub trait MsgReader<'a> {
fn reader(&mut self) -> Pin<&mut dyn AsyncRead>;
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, ()>;
}
impl<'a, T: ?Sized + MsgReader<'a>> MsgReaderExt<'a> for T {}
#[allow(async_fn_in_trait)]
pub trait MsgReaderExt<'a>: MsgReader<'a> {
async fn read<N: Decode>(mut self: Box<Self>) -> io::Result<N> {
let n = N::decode(self.reader()).await;
self.finish().await;
n
}
}
impl<CH: Channel, T: Client + ?Sized> ClientExt<CH> for T {}
/// A form of [Evidence] that doesn't require the value to be kept around
pub struct Witness<T>(PhantomData<T>);
@@ -105,64 +167,52 @@ type IoLock<T> = Rc<Mutex<Pin<Box<T>>>>;
type IoGuard<T> = Bound<MutexGuard<'static, Pin<Box<T>>>, IoLock<T>>;
/// An incoming request. This holds a lock on the ingress channel.
pub struct ReqReader<'a> {
id: u64,
pub struct IoReqReader<'a> {
prefix: &'a [u8],
read: IoGuard<dyn AsyncRead>,
write: &'a Mutex<IoRef<dyn AsyncWrite>>,
}
impl<'a> ReqReader<'a> {
/// Access
pub fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { self.read.as_mut() }
pub async fn read_req<R: Decode>(&mut self) -> R { R::decode(self.reader()).await }
pub async fn start_reply(self) -> RepWriter<'a> { self.branch().await.start_reply().await }
pub async fn reply<R: Request>(self, req: impl Evidence<R>, rep: &R::Response) -> Receipt<'a> {
self.branch().await.reply(req, rep).await
impl<'a> ReqReader<'a> for IoReqReader<'a> {
fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { self.read.as_mut() }
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, Box<dyn ReqHandle<'a> + 'a>> {
Box::pin(async {
Box::new(IoReqHandle { prefix: self.prefix, write: self.write }) as Box<dyn ReqHandle<'a>>
})
}
pub async fn branch(self) -> ReqHandle<'a> { ReqHandle { id: self.id, write: self.write } }
}
pub struct ReqHandle<'a> {
id: u64,
pub struct IoReqHandle<'a> {
prefix: &'a [u8],
write: &'a Mutex<IoRef<dyn AsyncWrite>>,
}
impl<'a> ReqHandle<'a> {
pub async fn reply<Req: Request>(
self,
_: impl Evidence<Req>,
rep: &Req::Response,
) -> Receipt<'a> {
let mut reply = self.start_reply().await;
rep.encode(reply.writer()).await;
reply.send().await
}
pub async fn start_reply(self) -> RepWriter<'a> {
let mut write = self.write.lock().await;
(!self.id).encode(write.as_mut()).await;
RepWriter { write }
impl<'a> ReqHandle<'a> for IoReqHandle<'a> {
fn start_reply(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Box<dyn RepWriter<'a> + 'a>>> {
Box::pin(async move {
let mut write = self.write.lock().await;
write.as_mut().write_all(self.prefix).await?;
Ok(Box::new(IoRepWriter { write }) as Box<dyn RepWriter<'a>>)
})
}
}
pub struct RepWriter<'a> {
pub struct IoRepWriter<'a> {
write: MutexGuard<'a, IoRef<dyn AsyncWrite>>,
}
impl<'a> RepWriter<'a> {
pub fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.write.as_mut() }
pub async fn send(mut self) -> Receipt<'a> {
self.writer().flush().await.unwrap();
Receipt(PhantomData)
impl<'a> RepWriter<'a> for IoRepWriter<'a> {
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.write.as_mut() }
fn finish(mut self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Receipt<'a>>> {
Box::pin(async move {
self.writer().flush().await?;
Ok(Receipt(PhantomData))
})
}
}
pub struct NotifReader<'a> {
pub struct IoMsgReader<'a> {
_pd: PhantomData<&'a mut ()>,
read: IoGuard<dyn AsyncRead>,
}
impl<'a> NotifReader<'a> {
pub fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { self.read.as_mut() }
pub async fn read<N: Decode>(mut self) -> N {
let n = N::decode(self.reader()).await;
self.release().await;
n
}
pub async fn release(self) {}
impl<'a> MsgReader<'a> for IoMsgReader<'a> {
fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { self.read.as_mut() }
fn finish(self: Box<Self>) -> LocalBoxFuture<'static, ()> { Box::pin(async {}) }
}
#[derive(Debug)]
@@ -187,14 +237,14 @@ impl IoClient {
}
}
impl Client for IoClient {
fn start_notif(&self) -> LocalBoxFuture<'_, Box<dyn MsgWriter>> {
fn start_notif(&self) -> LocalBoxFuture<'_, io::Result<Box<dyn MsgWriter<'_> + '_>>> {
Box::pin(async {
let mut o = self.lock_out().await;
0u64.encode(o.as_mut()).await;
Box::new(IoNotifWriter { o }) as Box<dyn MsgWriter>
0u64.encode(o.as_mut()).await?;
Ok(Box::new(IoNotifWriter { o }) as Box<dyn MsgWriter>)
})
}
fn start_request(&self) -> LocalBoxFuture<'_, Box<dyn ReqWriter>> {
fn start_request(&self) -> LocalBoxFuture<'_, io::Result<Box<dyn ReqWriter<'_> + '_>>> {
Box::pin(async {
let id = {
let mut id_g = self.id.borrow_mut();
@@ -206,8 +256,8 @@ impl Client for IoClient {
self.subscribe.as_ref().clone().send(ReplySub { id, ack, cb }).await.unwrap();
got_ack.await.unwrap();
let mut w = self.lock_out().await;
id.encode(w.as_mut()).await;
Box::new(IoReqWriter { reply, w }) as Box<dyn ReqWriter>
id.encode(w.as_mut()).await?;
Ok(Box::new(IoReqWriter { reply, w }) as Box<dyn ReqWriter>)
})
}
}
@@ -216,13 +266,15 @@ struct IoReqWriter {
reply: oneshot::Receiver<IoGuard<dyn AsyncRead>>,
w: IoGuard<dyn AsyncWrite>,
}
impl ReqWriter for IoReqWriter {
impl<'a> ReqWriter<'a> for IoReqWriter {
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.w.as_mut() }
fn send(self: Box<Self>) -> LocalBoxFuture<'static, Box<dyn RepReader>> {
fn send(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Box<dyn RepReader<'a> + 'a>>> {
Box::pin(async {
let Self { reply, .. } = *self;
let Self { reply, mut w } = *self;
w.flush().await?;
mem::drop(w);
let i = reply.await.expect("Client dropped before reply received");
Box::new(IoRepReader { i }) as Box<dyn RepReader>
Ok(Box::new(IoRepReader { i }) as Box<dyn RepReader>)
})
}
}
@@ -230,7 +282,7 @@ impl ReqWriter for IoReqWriter {
struct IoRepReader {
i: IoGuard<dyn AsyncRead>,
}
impl RepReader for IoRepReader {
impl<'a> RepReader<'a> for IoRepReader {
fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { self.i.as_mut() }
fn finish(self: Box<Self>) -> LocalBoxFuture<'static, ()> { Box::pin(async {}) }
}
@@ -239,11 +291,10 @@ impl RepReader for IoRepReader {
struct IoNotifWriter {
o: IoGuard<dyn AsyncWrite>,
}
impl MsgWriter for IoNotifWriter {
impl<'a> MsgWriter<'a> for IoNotifWriter {
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.o.as_mut() }
fn finish(self: Box<Self>) -> LocalBoxFuture<'static, ()> {
self.destructure();
Box::pin(async {})
fn finish(mut self: Box<Self>) -> LocalBoxFuture<'static, io::Result<()>> {
Box::pin(async move { self.o.flush().await })
}
}
@@ -262,12 +313,12 @@ impl CommCtx {
/// parameters are associated with the client and serve to ensure with a runtime
/// check that the correct message families are sent in the correct directions
/// across the channel.
pub fn io_comm<CH: Channel>(
pub fn io_comm(
o: Rc<Mutex<Pin<Box<dyn AsyncWrite>>>>,
i: Mutex<Pin<Box<dyn AsyncRead>>>,
notif: impl for<'a> AsyncFn(NotifReader<'a>),
req: impl for<'a> AsyncFn(ReqReader<'a>) -> Receipt<'a>,
) -> (impl ClientExt<CH>, CommCtx, impl Future<Output = ()>) {
notif: impl for<'a> AsyncFn(Box<dyn MsgReader<'a> + 'a>) -> io::Result<()>,
req: impl for<'a> AsyncFn(Box<dyn ReqReader<'a> + 'a>) -> io::Result<Receipt<'a>>,
) -> (impl Client + 'static, CommCtx, impl Future<Output = io::Result<()>>) {
let i = Rc::new(i);
let (onsub, client) = IoClient::new(o.clone());
let (exit, onexit) = channel(1);
@@ -278,65 +329,76 @@ pub fn io_comm<CH: Channel>(
Exit,
}
let exiting = RefCell::new(false);
let input_stream = stream(async |mut h| {
let input_stream = try_stream(async |mut h| {
loop {
let mut g = Bound::async_new(i.clone(), async |i| i.lock().await).await;
let id = u64::decode(g.as_mut()).await;
h.emit(Event::Input(id, g)).await;
match u64::decode(g.as_mut()).await {
Ok(id) => h.emit(Event::Input(id, g)).await,
Err(e)
if matches!(
e.kind(),
io::ErrorKind::BrokenPipe
| io::ErrorKind::ConnectionAborted
| io::ErrorKind::UnexpectedEof
) =>
h.emit(Event::Exit).await,
Err(e) => return Err(e),
}
}
});
let pending_reqs = RefCell::new(VecDeque::<LocalBoxFuture<()>>::new());
// this stream will never yield a value
let mut fork_stream = pin!(
stream::poll_fn(|cx| {
let mut reqs_g = pending_reqs.borrow_mut();
reqs_g.retain_mut(|req| match req.as_mut().poll(cx) {
Poll::Pending => true,
Poll::Ready(()) => false,
});
if *exiting.borrow() { Poll::Ready(None) } else { Poll::Pending }
})
.fuse()
);
let (mut add_pending_req, fork_future) = LocalSet::new();
let mut fork_stream = pin!(fork_future.fuse().into_stream());
let mut pending_replies = HashMap::new();
{
'body: {
let mut shared = pin!(stream_select!(
pin!(input_stream) as Pin<&mut dyn Stream<Item = Event>>,
onsub.map(Event::Sub),
fork_stream.as_mut(),
onexit.map(|()| Event::Exit),
pin!(input_stream) as Pin<&mut dyn Stream<Item = io::Result<Event>>>,
onsub.map(|sub| Ok(Event::Sub(sub))),
fork_stream.as_mut().map(|res| {
res.map(|()| panic!("this substream cannot exit while the loop is running"))
}),
onexit.map(|()| Ok(Event::Exit)),
));
while let Some(next) = shared.next().await {
match next {
Event::Exit => {
Err(e) => break 'body Err(e),
Ok(Event::Exit) => {
*exiting.borrow_mut() = true;
break;
},
Event::Sub(ReplySub { id, ack, cb }) => {
Ok(Event::Sub(ReplySub { id, ack, cb })) => {
pending_replies.insert(id, cb);
ack.send(()).unwrap();
},
Event::Input(0, read) => {
Ok(Event::Input(0, read)) => {
let notif = &notif;
pending_reqs.borrow_mut().push_back(Box::pin(async move {
notif(NotifReader { _pd: PhantomData, read }).await
}));
let notif_job =
async move { notif(Box::new(IoMsgReader { _pd: PhantomData, read })).await };
add_pending_req.send(Box::pin(notif_job)).await.unwrap();
},
// MSB == 0 is a request, !id where MSB == 1 is the corresponding response
Ok(Event::Input(id, read)) if (id & (1 << (u64::BITS - 1))) == 0 => {
let (o, req) = (o.clone(), &req);
let req_job = async move {
let mut prefix = Vec::new();
(!id).encode_vec(&mut prefix);
let _ = req(Box::new(IoReqReader { prefix: &pin!(prefix), read, write: &o })).await;
Ok(())
};
add_pending_req.send(Box::pin(req_job)).await.unwrap();
},
Ok(Event::Input(id, read)) => {
let cb = pending_replies.remove(&!id).expect("Reply to unrecognized request");
cb.send(read).unwrap_or_else(|_| panic!("Failed to send reply"));
},
// id.msb == 0 is a request, !id where id.msb == 1 is the equivalent response
Event::Input(id, read) =>
if (id & (1 << (u64::BITS - 1))) == 0 {
let (o, req) = (o.clone(), &req);
pending_reqs.borrow_mut().push_back(Box::pin(async move {
let _ = req(ReqReader { id, read, write: &o }).await;
}) as LocalBoxFuture<()>);
} else {
let cb = pending_replies.remove(&!id).expect("Reply to unrecognized request");
cb.send(read).unwrap_or_else(|_| panic!("Failed to send reply"));
},
}
}
Ok(())
}?;
mem::drop(add_pending_req);
while let Some(next) = fork_stream.next().await {
next?
}
fork_stream.as_mut().count().await;
Ok(())
})
}
@@ -347,18 +409,48 @@ mod test {
use futures::channel::mpsc;
use futures::lock::Mutex;
use futures::{SinkExt, StreamExt, join};
use never::Never;
use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::{Channel, Request};
use orchid_api_traits::Request;
use test_executors::spin_on;
use unsync_pipe::pipe;
use crate::reqnot::{ClientExt, NotifReader, io_comm};
use crate::reqnot::{ClientExt, MsgReaderExt, ReqReaderExt, io_comm};
#[derive(Clone, Debug, PartialEq, Coding, Hierarchy)]
#[extendable]
struct TestNotif(u64);
#[test]
fn notification() {
spin_on(async {
let (in1, out2) = pipe(1024);
let (in2, out1) = pipe(1024);
let (received, mut on_receive) = mpsc::channel(2);
let (_, recv_ctx, run_recv) = io_comm(
Rc::new(Mutex::new(Box::pin(in2))),
Mutex::new(Box::pin(out2)),
async |notif| {
received.clone().send(notif.read::<TestNotif>().await?).await.unwrap();
Ok(())
},
async |_| panic!("Should receive notif, not request"),
);
let (sender, ..) = io_comm(
Rc::new(Mutex::new(Box::pin(in1))),
Mutex::new(Box::pin(out1)),
async |_| panic!("Should not receive notif"),
async |_| panic!("Should not receive request"),
);
join!(async { run_recv.await.unwrap() }, async {
sender.notify(TestNotif(3)).await.unwrap();
assert_eq!(on_receive.next().await, Some(TestNotif(3)));
sender.notify(TestNotif(4)).await.unwrap();
assert_eq!(on_receive.next().await, Some(TestNotif(4)));
recv_ctx.exit().await;
});
})
}
#[derive(Clone, Debug, Coding, Hierarchy)]
#[extendable]
struct DummyRequest(u64);
@@ -366,64 +458,28 @@ mod test {
type Response = u64;
}
struct TestChannel;
impl Channel for TestChannel {
type Notif = TestNotif;
type Req = DummyRequest;
}
#[test]
fn notification() {
spin_on(async {
let (in1, out2) = pipe(1024);
let (in2, out1) = pipe(1024);
let (received, mut on_receive) = mpsc::channel(2);
let (_, recv_ctx, run_recv) = io_comm::<Never>(
Rc::new(Mutex::new(Box::pin(in2))),
Mutex::new(Box::pin(out2)),
async |notif: NotifReader| {
received.clone().send(notif.read::<TestNotif>().await).await.unwrap();
},
async |_| panic!("Should receive notif, not request"),
);
let (sender, ..) = io_comm::<TestChannel>(
Rc::new(Mutex::new(Box::pin(in1))),
Mutex::new(Box::pin(out1)),
async |_| panic!("Should not receive notif"),
async |_| panic!("Should not receive request"),
);
join!(run_recv, async {
sender.notify(TestNotif(3)).await;
assert_eq!(on_receive.next().await, Some(TestNotif(3)));
sender.notify(TestNotif(4)).await;
assert_eq!(on_receive.next().await, Some(TestNotif(4)));
recv_ctx.exit().await;
});
})
}
#[test]
fn request() {
spin_on(async {
let (in1, out2) = pipe(1024);
let (in2, out1) = pipe(1024);
let (_, srv_ctx, run_srv) = io_comm::<Never>(
let (_, srv_ctx, run_srv) = io_comm(
Rc::new(Mutex::new(Box::pin(in2))),
Mutex::new(Box::pin(out2)),
async |_| panic!("No notifs expected"),
async |mut req| {
let val = req.read_req::<DummyRequest>().await;
let val = req.read_req::<DummyRequest>().await?;
req.reply(&val, &(val.0 + 1)).await
},
);
let (client, client_ctx, run_client) = io_comm::<TestChannel>(
let (client, client_ctx, run_client) = io_comm(
Rc::new(Mutex::new(Box::pin(in1))),
Mutex::new(Box::pin(out1)),
async |_| panic!("Not expecting ingress notif"),
async |_| panic!("Not expecting ingress req"),
);
join!(run_srv, run_client, async {
let response = client.request(DummyRequest(5)).await;
join!(async { run_srv.await.unwrap() }, async { run_client.await.unwrap() }, async {
let response = client.request(DummyRequest(5)).await.unwrap();
assert_eq!(response, 6);
srv_ctx.exit().await;
client_ctx.exit().await;