Pending a correct test of request cancellation

This commit is contained in:
2026-04-22 15:58:34 +00:00
parent 60c96964d9
commit 7f8c247d97
31 changed files with 1059 additions and 499 deletions

View File

@@ -5,5 +5,11 @@ let main = "foo" + string::slice "hello" 1 3 + "bar"
let io_main = ( let io_main = (
stdio::get_stdout \stdout stdio::get_stdout \stdout
-- TODO: missing output commands in std std::stream::write_str stdout "Hello, World!"
(std::stream::flush
(std::stream::close
orchid::cmd::exit
\e e)
\e e)
\e e
) )

View File

@@ -0,0 +1,87 @@
use std::pin::Pin;
use std::task::{Context, Poll};
/// Future returned by [cancel_cleanup]
pub struct CancelCleanup<Fut: Future + Unpin, Fun: FnOnce(Fut)> {
/// Set to None when Ready
fut: Option<Fut>,
/// Only set to None in Drop
on_drop: Option<Fun>,
}
impl<Fut: Future + Unpin, Fun: FnOnce(Fut)> Future for CancelCleanup<Fut, Fun> {
type Output = Fut::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let Self { fut, .. } = unsafe { self.get_unchecked_mut() };
if let Some(future) = fut {
let future = unsafe { Pin::new_unchecked(future) };
let poll = future.poll(cx);
if poll.is_ready() {
*fut = None;
}
poll
} else {
Poll::Pending
}
}
}
impl<Fut: Future + Unpin, Fun: FnOnce(Fut)> Drop for CancelCleanup<Fut, Fun> {
fn drop(&mut self) {
if let Some(fut) = self.fut.take() {
(self.on_drop.take().unwrap())(fut)
}
}
}
/// Handle a Future's Drop. The callback is only called if the future has not
/// yet returned and would be cancelled, and it receives the future as an
/// argument
pub fn cancel_cleanup<Fut: Future + Unpin, Fun: FnOnce(Fut)>(
fut: Fut,
on_drop: Fun,
) -> CancelCleanup<Fut, Fun> {
CancelCleanup { fut: Some(fut), on_drop: Some(on_drop) }
}
#[cfg(test)]
mod test {
use std::pin::pin;
use futures::channel::mpsc;
use futures::future::join;
use futures::{SinkExt, StreamExt};
use super::*;
use crate::debug::spin_on;
#[test]
fn called_on_drop() {
let mut called = false;
cancel_cleanup(pin!(async {}), |_| called = true);
assert!(called, "cleanup was called when the future was dropped");
}
#[test]
fn not_called_if_finished() {
spin_on(false, async {
let (mut req_in, mut req_out) = mpsc::channel(0);
let (mut rep_in, mut rep_out) = mpsc::channel(0);
join(
async {
req_out.next().await.unwrap();
rep_in.send(()).await.unwrap();
},
async {
cancel_cleanup(
pin!(async {
req_in.send(()).await.unwrap();
rep_out.next().await.unwrap();
}),
|_| panic!("Callback called on drop even though the future was finished"),
)
.await
},
)
.await
});
}
}

View File

@@ -143,9 +143,17 @@ pub fn eprint_stream_events<'a, S: Stream + 'a>(
) )
} }
struct SpinWaker(AtomicBool); struct SpinWaker {
repeat: AtomicBool,
loud: bool,
}
impl Wake for SpinWaker { impl Wake for SpinWaker {
fn wake(self: Arc<Self>) { self.0.store(true, Ordering::Relaxed); } fn wake(self: Arc<Self>) {
self.repeat.store(true, Ordering::SeqCst);
if self.loud {
eprintln!("Triggered repeat for spin_on")
}
}
} }
/// A dumb executor that keeps synchronously re-running the future as long as it /// A dumb executor that keeps synchronously re-running the future as long as it
@@ -155,15 +163,15 @@ impl Wake for SpinWaker {
/// # Panics /// # Panics
/// ///
/// If the future doesn't wake itself and doesn't settle. /// If the future doesn't wake itself and doesn't settle.
pub fn spin_on<Fut: Future>(f: Fut) -> Fut::Output { pub fn spin_on<Fut: Future>(loud: bool, f: Fut) -> Fut::Output {
let repeat = Arc::new(SpinWaker(AtomicBool::new(false))); let spin_waker = Arc::new(SpinWaker { repeat: AtomicBool::new(false), loud });
let mut f = pin!(f); let mut f = pin!(f);
let waker = repeat.clone().into(); let waker = spin_waker.clone().into();
let mut cx = Context::from_waker(&waker); let mut cx = Context::from_waker(&waker);
loop { loop {
match f.as_mut().poll(&mut cx) { match f.as_mut().poll(&mut cx) {
Poll::Ready(t) => break t, Poll::Ready(t) => break t,
Poll::Pending if repeat.0.swap(false, Ordering::Relaxed) => (), Poll::Pending if spin_waker.repeat.swap(false, Ordering::SeqCst) => (),
Poll::Pending => panic!("The future did not exit and did not call its waker."), Poll::Pending => panic!("The future did not exit and did not call its waker."),
} }
} }

View File

@@ -1,5 +1,7 @@
pub mod debug; pub mod debug;
mod cancel_cleanup;
pub use cancel_cleanup::*;
mod localset; mod localset;
pub use localset::*; pub use localset::*;
mod task_future; mod task_future;
pub use task_future::*; pub use task_future::*;

View File

@@ -1,21 +1,35 @@
use std::collections::VecDeque;
use std::pin::Pin; use std::pin::Pin;
use std::task::Poll; use std::task::Poll;
use futures::StreamExt; use futures::channel::mpsc::{SendError, UnboundedReceiver, UnboundedSender, unbounded};
use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender, unbounded};
use futures::future::LocalBoxFuture; use futures::future::LocalBoxFuture;
use futures::stream::FuturesUnordered;
use futures::{SinkExt, StreamExt};
pub struct LocalSet<'a, E> { pub struct LocalSetController<'a, E> {
receiver: UnboundedReceiver<LocalBoxFuture<'a, Result<(), E>>>, sender: UnboundedSender<LocalBoxFuture<'a, Result<(), E>>>,
pending: VecDeque<LocalBoxFuture<'a, Result<(), E>>>,
} }
impl<'a, E> LocalSet<'a, E> { impl<'a, E> LocalSetController<'a, E> {
pub fn new() -> (UnboundedSender<LocalBoxFuture<'a, Result<(), E>>>, Self) { pub async fn spawn<F: Future<Output = Result<(), E>> + 'a>(
let (sender, receiver) = unbounded(); &mut self,
(sender, Self { receiver, pending: VecDeque::new() }) fut: F,
) -> Result<(), SendError> {
self.sender.send(Box::pin(fut)).await
} }
} }
pub fn local_set<'a, E: 'a>()
-> (LocalSetController<'a, E>, impl Future<Output = Result<(), E>> + 'a) {
let (sender, receiver) = unbounded();
let controller = LocalSetController { sender };
let set = LocalSet { receiver, pending: FuturesUnordered::new() };
(controller, set)
}
struct LocalSet<'a, E> {
receiver: UnboundedReceiver<LocalBoxFuture<'a, Result<(), E>>>,
pending: FuturesUnordered<LocalBoxFuture<'a, Result<(), E>>>,
}
impl<E> Future for LocalSet<'_, E> { impl<E> Future for LocalSet<'_, E> {
type Output = Result<(), E>; type Output = Result<(), E>;
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
@@ -23,7 +37,7 @@ impl<E> Future for LocalSet<'_, E> {
let mut any_pending = false; let mut any_pending = false;
loop { loop {
match this.receiver.poll_next_unpin(cx) { match this.receiver.poll_next_unpin(cx) {
Poll::Ready(Some(fut)) => this.pending.push_back(fut), Poll::Ready(Some(fut)) => this.pending.push(fut),
Poll::Ready(None) => break, Poll::Ready(None) => break,
Poll::Pending => { Poll::Pending => {
any_pending = true; any_pending = true;
@@ -31,15 +45,14 @@ impl<E> Future for LocalSet<'_, E> {
}, },
} }
} }
let count = this.pending.len(); loop {
for _ in 0..count { match this.pending.poll_next_unpin(cx) {
let mut req = this.pending.pop_front().unwrap(); Poll::Ready(Some(Err(e))) => return Poll::Ready(Err(e)),
match req.as_mut().poll(cx) { Poll::Ready(Some(Ok(()))) => continue,
Poll::Ready(Ok(())) => (), Poll::Ready(None) => break,
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => { Poll::Pending => {
any_pending = true; any_pending = true;
this.pending.push_back(req) break;
}, },
} }
} }

View File

@@ -1,10 +1,11 @@
use std::any::Any; use std::any::Any;
use std::cell::RefCell; use std::cell::RefCell;
use std::marker::PhantomData; use std::pin::{Pin, pin};
use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll, Waker}; use std::task::{Context, Poll, Waker};
use futures::FutureExt;
use futures::channel::oneshot::{self, Canceled};
use futures::future::{FusedFuture, LocalBoxFuture}; use futures::future::{FusedFuture, LocalBoxFuture};
struct State { struct State {
@@ -43,50 +44,56 @@ impl Future for Pollable {
} }
} }
pub struct JoinError;
/// An object that can be used to inspect the state of the task /// An object that can be used to inspect the state of the task
pub struct Handle<T: 'static>(Rc<RefCell<State>>, PhantomData<T>); pub struct Handle<T: 'static> {
send_abort: RefCell<Option<oneshot::Sender<()>>>,
ready: Rc<RefCell<bool>>,
recv_output: RefCell<oneshot::Receiver<T>>,
}
impl<T: 'static> Handle<T> { impl<T: 'static> Handle<T> {
/// Immediately stop working on this task, and return the result if it has /// Immediately stop working on this task, and return the result if it has
/// already finished /// already finished
pub fn abort(&self) -> Option<T> { pub fn abort(&self) -> Option<T> {
let mut g = self.0.borrow_mut(); if let Some(abort) = self.send_abort.take() {
g.work.take(); let _ = abort.send(());
match g.result.take() {
Some(val) => Some(*val.downcast().expect("Mismatch between type of future and handle")),
None => {
g.waker.wake_by_ref();
None
},
} }
self.recv_output.borrow_mut().try_recv().ok().flatten()
} }
/// Determine if there's any more work to do on this task /// Determine if there's any more work to do on this task
pub fn is_finished(&self) -> bool { pub fn is_finished(&self) -> bool { *self.ready.borrow() }
let g = self.0.borrow();
g.result.is_some() || g.work.is_none()
}
/// "finish" the freestanding task, and return the future instead /// "finish" the freestanding task, and return the future instead
pub async fn join(self) -> T { pub async fn join(self) -> Result<T, JoinError> {
let work = { self.recv_output.into_inner().await.map_err(|Canceled| JoinError)
let mut g = self.0.borrow_mut();
if let Some(val) = g.result.take() {
return *val.downcast().expect("Mistmatch between type of future and handle");
}
g.waker.wake_by_ref();
g.work.take().expect("Attempted to join task that was already aborted")
};
*work.await.downcast().expect("Mismatch between type of future and handle")
} }
} }
/// Split a future into an object that can be polled and one that returns /// Split a future into an object that can be polled and one that returns
/// information on its progress and its result. The first one can be passed to /// information on its progress and its result. The first one can be passed to
/// an executor or localset, the second can be used to manage it /// an executor or localset, the second can be used to manage it
pub fn to_task<F: Future<Output: 'static> + 'static>(f: F) -> (Pollable, Handle<F::Output>) { pub fn to_task<'a, F: Future<Output: 'a> + 'a>(
let dyn_future = Box::pin(async { Box::new(f.await) as Box<dyn Any> }); f: F,
let state = Rc::new(RefCell::new(State { ) -> (impl Future<Output = ()> + 'a, Handle<F::Output>) {
result: None, let (send_abort, mut on_abort) = oneshot::channel();
work: Some(dyn_future), let (send_output, on_output) = oneshot::channel();
waker: Waker::noop().clone(), let ready = Rc::new(RefCell::new(false));
})); let ready2 = ready.clone();
(Pollable(state.clone()), Handle(state, PhantomData)) let fut = async move {
let mut fut = pin!(f.fuse());
let output = futures::select_biased! {
res = on_abort => match res {
Ok(()) => return,
Err(_) => fut.await,
},
output = fut => output,
};
ready2.replace(true);
let _: Result<_, _> = send_output.send(output);
};
(fut, Handle {
ready,
recv_output: RefCell::new(on_output),
send_abort: RefCell::new(Some(send_abort)),
})
} }

View File

@@ -1,4 +1,4 @@
use std::cell::RefCell; use std::cell::{BorrowMutError, RefCell};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::{Pin, pin}; use std::pin::{Pin, pin};
use std::rc::Rc; use std::rc::Rc;
@@ -16,56 +16,95 @@ use futures::{
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
use orchid_api_traits::{Decode, Encode, Request, UnderRoot}; use orchid_api_traits::{Decode, Encode, Request, UnderRoot};
use orchid_async_utils::LocalSet;
use orchid_async_utils::debug::{PanicOnDrop, assert_no_drop}; use orchid_async_utils::debug::{PanicOnDrop, assert_no_drop};
use orchid_async_utils::{cancel_cleanup, local_set, to_task};
use crate::{clone, finish_or_stash, stash, with_stash};
// TODO: revise error handling; error recovery is never partial, it always
// requires dropping the server, client, and all requests
/// A token indicating that a reply to a request has been sent. Returned from
/// [RepWriter::finish] which is the raw reply channel, or [ReqHandleExt::reply]
/// or [ReqReaderExt::reply] which are type checked
#[must_use = "Receipts indicate that a required action has been performed within a function. \ #[must_use = "Receipts indicate that a required action has been performed within a function. \
Most likely this should be returned somewhere."] Most likely this should be returned somewhere."]
pub struct Receipt<'a>(PhantomData<&'a mut ()>); pub struct Receipt;
impl Receipt<'_> { impl Receipt {
/// Only call this function from a custom implementation of [RepWriter] /// Only ever call this function from a custom implementation of
pub fn _new() -> Self { Self(PhantomData) } /// [RepWriter::finish]
pub fn _new() -> Self { Self }
}
/// Return data while waiting for the response to a request. [Self::future] must
/// be awaited in order to ensure that progress is being made
pub struct ReqWait {
/// Future representing waiting for a request. This must be steadily polled.
pub future: LocalBoxFuture<'static, io::Result<Box<dyn RepReader>>>,
/// Since the [Self::future] must be awaited which exclusively borrows it,
/// this separate handle can be used for cancellation.
pub canceller: Box<dyn CancelNotifier>,
} }
/// Write guard to outbound for the purpose of serializing a request. Only one /// Write guard to outbound for the purpose of serializing a request. Only one
/// can exist at a time. Dropping this object should panic. /// can exist at a time. Dropping this object should panic.
pub trait ReqWriter<'a> { pub trait ReqWriter {
/// Access to the underlying channel. This may be buffered. /// Access to the underlying channel. This may be buffered.
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite>; fn writer(&mut self) -> Pin<&mut dyn AsyncWrite>;
/// Finalize the request, release the outbound channel, then queue for the /// Finalize the request, release the outbound channel, then queue for the
/// reply on the inbound channel. /// reply on the inbound channel.
fn send(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Box<dyn RepReader<'a> + 'a>>>; fn send(self: Box<Self>) -> ReqWait;
} }
/// Write guard to inbound for the purpose of deserializing a reply. While held, /// Write guard to inbound for the purpose of deserializing a reply. While held,
/// no inbound requests or other replies can be processed. /// no inbound requests or other replies can be processed.
/// ///
/// Dropping this object should panic even if [RepReader::finish] returns /// # Cancellation
/// 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 /// If the request has been cancelled and the server has accepted the
/// cleanup. /// cancellation instead of writing a reply (which is never guaranteed), then
pub trait RepReader<'a> { /// this object is inert and should be dropped.
///
/// Dropping this object if [Self::reader] returns [Some] should panic even if
/// [RepReader::finish] returns synchronously, because the API isn't
/// cancellation safe in general so it is a programmer error to drop an object
/// related to it without proper cleanup.
pub trait RepReader {
/// Access to the underlying channel. The length of the message is inferred /// Access to the underlying channel. The length of the message is inferred
/// from the number of bytes read so this must not be buffered. /// from the number of bytes read so this must not be buffered and a full
fn reader(&mut self) -> Pin<&mut dyn AsyncRead>; /// reply must always be read from it if available
///
/// This returns None if the request has successfully been cancelled, in which
/// case this object can be dropped without calling [Self::finish]
fn reader(&mut self) -> Option<Pin<&mut dyn AsyncRead>>;
/// Finish reading the request /// Finish reading the request
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, ()>; fn finish(self: Box<Self>) -> LocalBoxFuture<'static, ()>;
}
/// A handle for cancelling in-flight requests without a reference to
/// the wait future (which would be mutably borrowed by an await at this point)
pub trait CancelNotifier {
/// Upon cancellation the future may resolve to a stub version of [RepReader]
/// with no reader access, but since the cancellation is not synchronized
/// with the server, a full reply may still be received, and if it is, the
/// original reply must still be read from it.
fn cancel(self: Box<Self>) -> LocalBoxFuture<'static, ()>;
} }
/// Write guard to outbound for the purpose of serializing a notification. /// Write guard to outbound for the purpose of serializing a notification.
/// ///
/// Dropping this object should panic for the same reason [RepReader] panics /// Dropping this object should panic for the same reason [RepReader] panics
pub trait MsgWriter<'a> { pub trait MsgWriter {
/// Access to the underlying channel. This may be buffered. /// Access to the underlying channel. This may be buffered.
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite>; fn writer(&mut self) -> Pin<&mut dyn AsyncWrite>;
/// Send the notification /// Send the notification
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<()>>; fn finish(self: Box<Self>) -> LocalBoxFuture<'static, io::Result<()>>;
} }
/// For initiating outbound requests and notifications /// For initiating outbound requests and notifications
pub trait Client { pub trait Client {
fn start_request(&self) -> LocalBoxFuture<'_, io::Result<Box<dyn ReqWriter<'_> + '_>>>; fn start_request(&self) -> LocalBoxFuture<'static, io::Result<Box<dyn ReqWriter>>>;
fn start_notif(&self) -> LocalBoxFuture<'_, io::Result<Box<dyn MsgWriter<'_> + '_>>>; fn start_notif(&self) -> LocalBoxFuture<'static, io::Result<Box<dyn MsgWriter>>>;
} }
impl<T: Client + ?Sized> ClientExt for T {} impl<T: Client + ?Sized> ClientExt for T {}
@@ -73,62 +112,146 @@ impl<T: Client + ?Sized> ClientExt for T {}
/// notif lifecycle and typing /// notif lifecycle and typing
#[allow(async_fn_in_trait)] #[allow(async_fn_in_trait)]
pub trait ClientExt: Client { pub trait ClientExt: Client {
#[allow(
clippy::await_holding_refcell_ref,
reason = "Must bypass a future return point by sharing the common path"
)]
async fn request<T: Request + UnderRoot<Root: Encode>>(&self, t: T) -> io::Result<T::Response> { async fn request<T: Request + UnderRoot<Root: Encode>>(&self, t: T) -> io::Result<T::Response> {
let mut req = self.start_request().await?; let start_req = self.start_request();
t.into_root().encode(req.writer().as_mut()).await?; // This section must finish if it has started, and the returned writer's `send`
let mut rep = req.send().await?; // must be called as well.
let response = T::Response::decode(rep.reader()).await; let common = Rc::new(RefCell::new(Some(Box::pin(async move {
rep.finish().await; let mut writer = start_req.await?;
response t.into_root().encode(writer.writer().as_mut()).await?;
io::Result::Ok(writer)
}))));
// Initialized in the cancelable section if common returns here. If set, the
// future inside must be finished on stash after the notification is sent
// to ensure that the server acknowledges the cancellation, or to decode the
// result if the cancellation was in fact too late.
let req_wait_rc = Rc::new(RefCell::new(None));
// If both this and common are None, that means the cancelable section is
// already past its last interruptible point, and must be finished on stash
cancel_cleanup(
clone!(req_wait_rc, common; Box::pin(async move {
let req_wait;
{
let mut common_g = common.try_borrow_mut().expect("cancel will drop us before locking");
let common = (common_g.as_mut())
.expect("Only unset by us below or by cancel after dropping us");
// cancel handler may take over here
req_wait = common.await?.send();
common_g.take();
}
let mut rep;
{
let mut req_wait_g = (req_wait_rc.try_borrow_mut())
.expect("We are the first ones to access this");
*req_wait_g = Some(req_wait);
let req_wait = req_wait_g.as_mut().expect("Initialized right above");
// cancel handler may take over here
rep = req_wait.future.as_mut().await?;
req_wait_g.take();
};
// cancel handler will not interrupt if we've gotten this far
let reader = rep.reader().expect("Not been cancelled thus far");
let result = T::Response::decode(reader).await;
rep.finish().await;
result
})),
|fut| {
stash(async move {
// TODO: strategy for IO errors on stash
let req_wait = if common.try_borrow_mut().is_ok_and(|r| r.is_none()) {
// fut was already past common
match req_wait_rc.try_borrow_mut() {
Ok(mut opt) => {
let Some(req_wait) = opt.take() else {
// fut was already reading, finish that read and exit
fut.await.expect("IO error on stash");
return;
};
req_wait
},
Err(BorrowMutError { .. }) => {
// fut was in waiting, take over and do our own thing
std::mem::drop(fut);
req_wait_rc.take().expect("If it was borrowed then it was still set")
},
}
} else {
// fut was still in common, take over and finish common
std::mem::drop(fut);
let common =
(common.take()).expect("If it was still borrowed in fut, it was not yet unset");
common.await.expect("IO error on stash").send()
};
req_wait.canceller.cancel().await;
let mut rep = req_wait.future.await.expect("IO error on stash");
let Some(reader) = rep.reader() else { return };
T::Response::decode(reader).await.expect("IO error on stash");
rep.finish().await;
})
},
)
.await
} }
async fn notify<T: UnderRoot<Root: Encode>>(&self, t: T) -> io::Result<()> { async fn notify<T: UnderRoot<Root: Encode> + 'static>(&self, t: T) -> io::Result<()> {
let mut notif = self.start_notif().await?; let start_notif = self.start_notif();
t.into_root().encode(notif.writer().as_mut()).await?; finish_or_stash(Box::pin(async {
notif.finish().await?; let mut notif = start_notif.await?;
Ok(()) t.into_root().encode(notif.writer().as_mut()).await?;
notif.finish().await?;
Ok(())
}))
.await
} }
} }
pub trait ReqReader<'a> { pub trait ReqReader {
fn reader(&mut self) -> Pin<&mut dyn AsyncRead>; fn reader(&mut self) -> Pin<&mut dyn AsyncRead>;
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, Box<dyn ReqHandle<'a> + 'a>>; fn finish(self: Box<Self>) -> LocalBoxFuture<'static, Box<dyn ReqHandle>>;
} }
impl<'a, T: ReqReader<'a> + ?Sized> ReqReaderExt<'a> for T {} impl<T: ReqReader + ?Sized> ReqReaderExt for T {}
#[allow(async_fn_in_trait)] #[allow(async_fn_in_trait)]
pub trait ReqReaderExt<'a>: ReqReader<'a> { pub trait ReqReaderExt: ReqReader {
async fn read_req<R: Decode>(&mut self) -> io::Result<R> { R::decode(self.reader()).await } async fn read_req<R: Decode>(&mut self) -> io::Result<R> { R::decode(self.reader()).await }
async fn reply<R: Request>( async fn reply<R: Request>(
self: Box<Self>, self: Box<Self>,
req: impl Evidence<R>, req: impl Evidence<R>,
rep: &R::Response, rep: R::Response,
) -> io::Result<Receipt<'a>> { ) -> io::Result<Receipt> {
self.finish().await.reply(req, rep).await self.finish().await.reply(req, rep).await
} }
async fn start_reply(self: Box<Self>) -> io::Result<Box<dyn RepWriter<'a> + 'a>> { async fn start_reply(self: Box<Self>) -> io::Result<Box<dyn RepWriter>> {
self.finish().await.start_reply().await self.finish().await.start_reply().await
} }
} }
pub trait ReqHandle<'a> { pub trait ReqHandle {
fn start_reply(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Box<dyn RepWriter<'a> + 'a>>>; fn start_reply(self: Box<Self>) -> LocalBoxFuture<'static, io::Result<Box<dyn RepWriter>>>;
} }
impl<'a, T: ReqHandle<'a> + ?Sized> ReqHandleExt<'a> for T {} impl<T: ReqHandle + ?Sized> ReqHandleExt for T {}
#[allow(async_fn_in_trait)] #[allow(async_fn_in_trait)]
pub trait ReqHandleExt<'a>: ReqHandle<'a> { pub trait ReqHandleExt: ReqHandle {
async fn reply<Req: Request>( async fn reply<Req: Request>(
self: Box<Self>, self: Box<Self>,
_: impl Evidence<Req>, _: impl Evidence<Req>,
rep: &Req::Response, rep: Req::Response,
) -> io::Result<Receipt<'a>> { ) -> io::Result<Receipt> {
let mut reply = self.start_reply().await?; let start_reply = self.start_reply();
rep.encode(reply.writer()).await?; finish_or_stash(Box::pin(async move {
reply.finish().await let mut reply = start_reply.await?;
rep.encode(reply.writer()).await?;
reply.finish().await
}))
.await
} }
} }
pub trait RepWriter<'a> { pub trait RepWriter {
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite>; fn writer(&mut self) -> Pin<&mut dyn AsyncWrite>;
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Receipt<'a>>>; fn finish(self: Box<Self>) -> LocalBoxFuture<'static, io::Result<Receipt>>;
} }
pub trait MsgReader<'a> { pub trait MsgReader<'a> {
@@ -166,41 +289,43 @@ type IoLock<T> = Rc<Mutex<Pin<Box<T>>>>;
type IoGuard<T> = Bound<MutexGuard<'static, Pin<Box<T>>>, IoLock<T>>; type IoGuard<T> = Bound<MutexGuard<'static, Pin<Box<T>>>, IoLock<T>>;
/// An incoming request. This holds a lock on the ingress channel. /// An incoming request. This holds a lock on the ingress channel.
pub struct IoReqReader<'a> { pub struct IoReqReader {
prefix: &'a [u8], prefix: u64,
read: IoGuard<dyn AsyncRead>, read: IoGuard<dyn AsyncRead>,
write: &'a Mutex<IoRef<dyn AsyncWrite>>, o: Rc<Mutex<IoRef<dyn AsyncWrite>>>,
} }
impl<'a> ReqReader<'a> for IoReqReader<'a> { impl ReqReader for IoReqReader {
fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { self.read.as_mut() } fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { self.read.as_mut() }
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, Box<dyn ReqHandle<'a> + 'a>> { fn finish(self: Box<Self>) -> LocalBoxFuture<'static, Box<dyn ReqHandle>> {
Box::pin(async { Box::pin(async {
Box::new(IoReqHandle { prefix: self.prefix, write: self.write }) as Box<dyn ReqHandle<'a>> Box::new(IoReqHandle { prefix: self.prefix, write: self.o }) as Box<dyn ReqHandle>
}) })
} }
} }
pub struct IoReqHandle<'a> {
prefix: &'a [u8], pub struct IoReqHandle {
write: &'a Mutex<IoRef<dyn AsyncWrite>>, prefix: u64,
write: IoLock<dyn AsyncWrite>,
} }
impl<'a> ReqHandle<'a> for IoReqHandle<'a> { impl ReqHandle for IoReqHandle {
fn start_reply(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Box<dyn RepWriter<'a> + 'a>>> { fn start_reply(self: Box<Self>) -> LocalBoxFuture<'static, io::Result<Box<dyn RepWriter>>> {
let write = self.write.clone();
Box::pin(async move { Box::pin(async move {
let mut write = self.write.lock().await; let mut write = Bound::async_new(write, |l| l.lock()).await;
write.as_mut().write_all(self.prefix).await?; self.prefix.encode(write.as_mut()).await?;
Ok(Box::new(IoRepWriter { write }) as Box<dyn RepWriter<'a>>) Ok(Box::new(IoRepWriter { write }) as Box<dyn RepWriter>)
}) })
} }
} }
pub struct IoRepWriter<'a> { pub struct IoRepWriter {
write: MutexGuard<'a, IoRef<dyn AsyncWrite>>, write: IoGuard<dyn AsyncWrite>,
} }
impl<'a> RepWriter<'a> for IoRepWriter<'a> { impl RepWriter for IoRepWriter {
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.write.as_mut() } fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.write.as_mut() }
fn finish(mut self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Receipt<'a>>> { fn finish(mut self: Box<Self>) -> LocalBoxFuture<'static, io::Result<Receipt>> {
Box::pin(async move { Box::pin(async move {
self.writer().flush().await?; self.writer().flush().await?;
Ok(Receipt(PhantomData)) Ok(Receipt)
}) })
} }
} }
@@ -214,11 +339,16 @@ impl<'a> MsgReader<'a> for IoMsgReader<'a> {
fn finish(self: Box<Self>) -> LocalBoxFuture<'static, ()> { Box::pin(async {}) } fn finish(self: Box<Self>) -> LocalBoxFuture<'static, ()> { Box::pin(async {}) }
} }
pub enum ReplyRecord {
Cancelled,
Ready(IoGuard<dyn AsyncRead>),
}
#[derive(Debug)] #[derive(Debug)]
struct ReplySub { struct ReplySub {
id: u64, id: u64,
ack: oneshot::Sender<()>, ack: oneshot::Sender<()>,
cb: oneshot::Sender<IoGuard<dyn AsyncRead>>, cb: oneshot::Sender<ReplyRecord>,
} }
struct IoClient { struct IoClient {
@@ -231,37 +361,42 @@ impl IoClient {
let (req, rep) = mpsc::channel(0); let (req, rep) = mpsc::channel(0);
(rep, Self { output, id: Rc::new(RefCell::new(0)), subscribe: Rc::new(req) }) (rep, Self { output, id: Rc::new(RefCell::new(0)), subscribe: Rc::new(req) })
} }
async fn lock_out(&self) -> IoGuard<dyn AsyncWrite> {
Bound::async_new(self.output.clone(), async |o| o.lock().await).await
}
} }
impl Client for IoClient { impl Client for IoClient {
fn start_notif(&self) -> LocalBoxFuture<'_, io::Result<Box<dyn MsgWriter<'_> + '_>>> { fn start_notif(&self) -> LocalBoxFuture<'static, io::Result<Box<dyn MsgWriter>>> {
let output = self.output.clone();
Box::pin(async { Box::pin(async {
let drop_g = assert_no_drop("Notif future dropped"); let drop_g = assert_no_drop("Notif future dropped");
let mut o = self.lock_out().await; let mut o = Bound::async_new(output, |o| o.lock()).await;
0u64.encode(o.as_mut()).await?; 0u64.encode(o.as_mut()).await?;
drop_g.defuse(); drop_g.defuse();
Ok(Box::new(IoNotifWriter { o, drop_g: assert_no_drop("Notif writer dropped") }) Ok(Box::new(IoNotifWriter { o, drop_g: assert_no_drop("Notif writer dropped") })
as Box<dyn MsgWriter>) as Box<dyn MsgWriter>)
}) })
} }
fn start_request(&self) -> LocalBoxFuture<'_, io::Result<Box<dyn ReqWriter<'_> + '_>>> { fn start_request(&self) -> LocalBoxFuture<'static, io::Result<Box<dyn ReqWriter>>> {
Box::pin(async { let output = self.output.clone();
let id = { let id = {
let mut id_g = self.id.borrow_mut(); let mut id_g = self.id.borrow_mut();
*id_g += 1; *id_g += 1;
*id_g *id_g
}; };
let (cb, reply) = oneshot::channel(); let (cb, reply) = oneshot::channel();
let (ack, got_ack) = oneshot::channel(); let (ack, got_ack) = oneshot::channel();
let drop_g = assert_no_drop("Request future dropped"); let mut subscribe = self.subscribe.as_ref().clone();
self.subscribe.as_ref().clone().send(ReplySub { id, ack, cb }).await.unwrap(); let start_req_drop_g = assert_no_drop("Request future dropped");
Box::pin(async move {
subscribe.send(ReplySub { id, ack, cb }).await.unwrap();
got_ack.await.unwrap(); got_ack.await.unwrap();
let mut w = self.lock_out().await; let mut xfer_bytes = id.to_be_bytes();
id.encode(w.as_mut()).await?; xfer_bytes[0] = 0x00;
drop_g.defuse(); let req_prefix = u64::from_be_bytes(xfer_bytes);
let mut w = Bound::async_new(output.clone(), |o| o.lock()).await;
req_prefix.encode(w.as_mut()).await?;
start_req_drop_g.defuse();
Ok(Box::new(IoReqWriter { Ok(Box::new(IoReqWriter {
id,
output,
reply, reply,
w, w,
drop_g: assert_no_drop("Request reader dropped without reply"), drop_g: assert_no_drop("Request reader dropped without reply"),
@@ -270,34 +405,62 @@ impl Client for IoClient {
} }
} }
struct IoReqWriter { struct IoReqCanceller {
reply: oneshot::Receiver<IoGuard<dyn AsyncRead>>, id: u64,
w: IoGuard<dyn AsyncWrite>, output: IoLock<dyn AsyncWrite>,
drop_g: PanicOnDrop,
} }
impl<'a> ReqWriter<'a> for IoReqWriter { impl CancelNotifier for IoReqCanceller {
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.w.as_mut() } fn cancel(self: Box<Self>) -> LocalBoxFuture<'static, ()> {
fn send(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Box<dyn RepReader<'a> + 'a>>> { let mut xfer_bytes = self.id.to_be_bytes();
Box::pin(async { xfer_bytes[0] = 0x02;
let Self { reply, mut w, drop_g } = *self; let cancel_id = u64::from_be_bytes(xfer_bytes);
w.flush().await?; let cancel_signal_drop_g = assert_no_drop("Cannot cancel the sending of a cancellation");
mem::drop(w); let o = self.output.clone();
let i = reply.await.expect("Client dropped before reply received"); Box::pin(async move {
drop_g.defuse(); let mut o = o.lock().await;
Ok(Box::new(IoRepReader { let _ = cancel_id.encode(o.as_mut()).await;
i, cancel_signal_drop_g.defuse();
drop_g: assert_no_drop("Reply reader dropped without finishing"),
}) as Box<dyn RepReader>)
}) })
} }
} }
struct IoRepReader { struct IoReqWriter {
i: IoGuard<dyn AsyncRead>, id: u64,
reply: oneshot::Receiver<ReplyRecord>,
output: IoLock<dyn AsyncWrite>,
w: IoGuard<dyn AsyncWrite>,
drop_g: PanicOnDrop, drop_g: PanicOnDrop,
} }
impl<'a> RepReader<'a> for IoRepReader { impl ReqWriter for IoReqWriter {
fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { self.i.as_mut() } fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.w.as_mut() }
fn send(self: Box<Self>) -> ReqWait {
let Self { id, output, reply, mut w, drop_g } = *self;
let canceller = IoReqCanceller { id, output };
let future = async {
w.flush().await?;
mem::drop(w);
let reply_record = reply.await.expect("Client dropped before reply received");
drop_g.defuse();
Ok(Box::new(IoRepReader {
reply_record,
drop_g: assert_no_drop("Reply reader dropped without finishing"),
}) as Box<dyn RepReader>)
};
ReqWait { future: Box::pin(future), canceller: Box::new(canceller) }
}
}
struct IoRepReader {
reply_record: ReplyRecord,
drop_g: PanicOnDrop,
}
impl RepReader for IoRepReader {
fn reader(&mut self) -> Option<Pin<&mut dyn AsyncRead>> {
match &mut self.reply_record {
ReplyRecord::Cancelled => None,
ReplyRecord::Ready(guard) => Some(guard.as_mut()),
}
}
fn finish(self: Box<Self>) -> LocalBoxFuture<'static, ()> { fn finish(self: Box<Self>) -> LocalBoxFuture<'static, ()> {
Box::pin(async { self.drop_g.defuse() }) Box::pin(async { self.drop_g.defuse() })
} }
@@ -308,7 +471,7 @@ struct IoNotifWriter {
o: IoGuard<dyn AsyncWrite>, o: IoGuard<dyn AsyncWrite>,
drop_g: PanicOnDrop, drop_g: PanicOnDrop,
} }
impl<'a> MsgWriter<'a> for IoNotifWriter { impl MsgWriter for IoNotifWriter {
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.o.as_mut() } fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.o.as_mut() }
fn finish(mut self: Box<Self>) -> LocalBoxFuture<'static, io::Result<()>> { fn finish(mut self: Box<Self>) -> LocalBoxFuture<'static, io::Result<()>> {
Box::pin(async move { Box::pin(async move {
@@ -333,10 +496,7 @@ impl CommCtx {
/// Establish bidirectional request-notification communication over a duplex /// Establish bidirectional request-notification communication over a duplex
/// channel. The returned [IoClient] can be used for notifications immediately, /// channel. The returned [IoClient] can be used for notifications immediately,
/// but requests can only be received while the future is running. The future /// but requests can only be received while the future is running. The future
/// will only resolve when [CommCtx::quit] is called. The generic type /// will only resolve when [CommCtx::exit] is called.
/// 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( pub fn io_comm(
o: Pin<Box<dyn AsyncWrite>>, o: Pin<Box<dyn AsyncWrite>>,
i: Pin<Box<dyn AsyncRead>>, i: Pin<Box<dyn AsyncRead>>,
@@ -356,8 +516,8 @@ pub struct IoCommServer {
impl IoCommServer { impl IoCommServer {
pub async fn listen( pub async fn listen(
self, self,
notif: impl for<'a> AsyncFn(Box<dyn MsgReader<'a> + 'a>) -> io::Result<()>, notif: impl AsyncFn(Box<dyn MsgReader>) -> io::Result<()>,
req: impl for<'a> AsyncFn(Box<dyn ReqReader<'a> + 'a>) -> io::Result<Receipt<'a>>, req: impl AsyncFn(Box<dyn ReqReader>) -> io::Result<Receipt>,
) -> io::Result<()> { ) -> io::Result<()> {
let Self { o, i, onexit, onsub } = self; let Self { o, i, onexit, onsub } = self;
enum Event { enum Event {
@@ -379,7 +539,9 @@ impl IoCommServer {
} }
} }
}); });
let (mut add_pending_req, fork_future) = LocalSet::new();
let running_requests = RefCell::new(HashMap::new());
let (mut task_pool, fork_future) = local_set();
let mut fork_stream = pin!(fork_future.into_stream()); let mut fork_stream = pin!(fork_future.into_stream());
let mut pending_replies = HashMap::new(); let mut pending_replies = HashMap::new();
'body: { 'body: {
@@ -400,32 +562,73 @@ impl IoCommServer {
// this is detected and logged on client // this is detected and logged on client
let _ = ack.send(()); let _ = ack.send(());
}, },
// ID 0 is reserved for single-fire notifications
Ok(Event::Input(0, read)) => { Ok(Event::Input(0, read)) => {
let notif = &notif; let notif = &notif;
let notif_job = task_pool.spawn(notif(Box::new(IoMsgReader { _pd: PhantomData, read }))).await.unwrap();
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();
}, },
// non-zero IDs are associated with requests
Ok(Event::Input(id, read)) => { Ok(Event::Input(id, read)) => {
let cb = pending_replies.remove(&!id).expect("Reply to unrecognized request"); // the MSb decides what kind of message this is
cb.send(read).unwrap_or_else(|_| panic!("Failed to send reply")); let mut id_bytes = id.to_be_bytes();
let discr = std::mem::replace(&mut id_bytes[0], 0x00);
let id = u64::from_be_bytes(id_bytes);
match discr {
// request
0x00 => {
let (o, req, reqs) = (o.clone(), &req, &running_requests);
task_pool
.spawn(async move {
id_bytes[0] = 0x01;
let prefix = u64::from_be_bytes(id_bytes);
let reader = Box::new(IoReqReader { prefix, read, o });
let (fut, handle) = to_task(async { req(reader).await.map(|Receipt| ()) });
reqs.borrow_mut().insert(id, handle);
with_stash(fut).await;
// during this await the read guard is released and thus we may receive a
// cancel notification from below
Ok(())
})
.await
.unwrap();
},
// response
0x01 => {
let cb = pending_replies.remove(&id).expect("Reply to unrecognized request");
cb.send(ReplyRecord::Ready(read))
.unwrap_or_else(|_| panic!("Failed to send reply"));
},
// cancellation
0x02 => {
match running_requests.borrow().get(&id) {
Some(handle) => handle.abort(),
// assuming that the client is correct, if there is no record
// then the reply was already sent
None => continue,
};
// if the request starts writing back before our abort arrives, we only
// get this mutex once it's done
let mut write = o.lock().await;
// if the request is still in the store, the write didn't begin
let Some(_) = running_requests.borrow_mut().remove(&id) else { continue };
id_bytes[0] = 0x03;
let cancel_code = u64::from_be_bytes(id_bytes);
cancel_code.encode(write.as_mut()).await?;
},
// stub reply for cancelled request
0x03 => {
let cb = pending_replies.remove(&id).expect("Cancelling unrecognized request");
cb.send(ReplyRecord::Cancelled)
.unwrap_or_else(|_| panic!("Failed to send reply cancellation"))
},
n => panic!("Unrecognized message type code {n}"),
}
}, },
} }
} }
Ok(()) Ok(())
}?; }?;
mem::drop(add_pending_req); mem::drop(task_pool);
while let Some(next) = fork_stream.next().await { while let Some(next) = fork_stream.next().await {
next? next?
} }
@@ -441,13 +644,15 @@ mod test {
use std::cell::RefCell; use std::cell::RefCell;
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::{SinkExt, StreamExt, join}; use futures::{FutureExt, SinkExt, StreamExt, join, select};
use never::Never;
use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::Request; use orchid_api_traits::Request;
use orchid_async_utils::debug::spin_on; use orchid_async_utils::debug::spin_on;
use unsync_pipe::pipe; use unsync_pipe::pipe;
use crate::comm::{ClientExt, MsgReaderExt, ReqReaderExt, io_comm}; use crate::comm::{ClientExt, MsgReaderExt, ReqReaderExt, io_comm};
use crate::with_stash;
#[derive(Clone, Debug, PartialEq, Coding, Hierarchy)] #[derive(Clone, Debug, PartialEq, Coding, Hierarchy)]
#[extendable] #[extendable]
@@ -455,7 +660,7 @@ mod test {
#[test] #[test]
fn notification() { fn notification() {
spin_on(async { spin_on(false, async {
let (in1, out2) = pipe(1024); let (in1, out2) = pipe(1024);
let (in2, out1) = pipe(1024); let (in2, out1) = pipe(1024);
let (received, mut on_receive) = mpsc::channel(2); let (received, mut on_receive) = mpsc::channel(2);
@@ -494,7 +699,7 @@ mod test {
#[test] #[test]
fn request() { fn request() {
spin_on(async { spin_on(false, async {
let (in1, out2) = pipe(1024); let (in1, out2) = pipe(1024);
let (in2, out1) = pipe(1024); let (in2, out1) = pipe(1024);
let (_, srv_ctx, srv) = io_comm(Box::pin(in2), Box::pin(out2)); let (_, srv_ctx, srv) = io_comm(Box::pin(in2), Box::pin(out2));
@@ -506,7 +711,7 @@ mod test {
async |_| panic!("No notifs expected"), async |_| panic!("No notifs expected"),
async |mut req| { async |mut req| {
let val = req.read_req::<DummyRequest>().await?; let val = req.read_req::<DummyRequest>().await?;
req.reply(&val, &(val.0 + 1)).await req.reply(&val, val.0 + 1).await
}, },
) )
.await .await
@@ -533,7 +738,7 @@ mod test {
#[test] #[test]
fn exit() { fn exit() {
spin_on(async { spin_on(false, async {
let (input1, output1) = pipe(1024); let (input1, output1) = pipe(1024);
let (input2, output2) = pipe(1024); let (input2, output2) = pipe(1024);
let (reply_client, reply_context, reply_server) = let (reply_client, reply_context, reply_server) =
@@ -553,7 +758,7 @@ mod test {
}, },
async |mut hand| { async |mut hand| {
let req = hand.read_req::<DummyRequest>().await?; let req = hand.read_req::<DummyRequest>().await?;
hand.reply(&req, &(req.0 + 1)).await hand.reply(&req, req.0 + 1).await
}, },
) )
.await .await
@@ -579,4 +784,49 @@ mod test {
) )
}); });
} }
#[test]
fn timely_cancel() {
spin_on(false, async {
let (in1, out2) = pipe(1024);
let (in2, out1) = pipe(1024);
let (wait_in, mut wait_out) = mpsc::channel(0);
let (_, srv_ctx, srv) = io_comm(Box::pin(in2), Box::pin(out2));
let (client, client_ctx, client_srv) = io_comm(Box::pin(in1), Box::pin(out1));
join!(
async {
srv
.listen(
async |_| panic!("No notifs expected"),
async |mut req| {
let _ = req.read_req::<DummyRequest>().await?;
wait_in.clone().send(()).await.unwrap();
// TODO: verify cancellation
futures::future::pending::<Never>().await;
unreachable!("request should be cancelled before resume is triggered")
},
)
.await
.unwrap()
},
async {
client_srv
.listen(
async |_| panic!("Not expecting ingress notif"),
async |_| panic!("Not expecting ingress req"),
)
.await
.unwrap()
},
with_stash(async {
select! {
_ = client.request(DummyRequest(5)).fuse() => panic!("This one should not run"),
rep = wait_out.next() => rep.expect("something?"),
};
srv_ctx.exit().await.unwrap();
client_ctx.exit().await.unwrap();
})
);
})
}
} }

View File

@@ -1,44 +1,188 @@
//! A pattern for running async code from sync destructors and other //! A pattern for running async code from sync destructors and other
//! unfortunately sync callbacks //! unfortunately sync callbacks, and for ensuring that these futures finish in
//! a timely fashion
//! //!
//! We create a task_local vecdeque which is moved into a thread_local whenever //! We create a task_local vecdeque which is moved into a thread_local whenever
//! the task is being polled. A call to [stash] pushes the future onto this //! the task is being polled. A call to [stash] pushes the future onto this
//! deque. Before [with_stash] returns, it pops everything from the deque //! deque. Before [with_stash] returns, it awaits everything stashed up to that
//! individually and awaits each of them, pushing any additionally stashed //! point or inside the stashed futures.
//! futures onto the back of the same deque.
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::VecDeque;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll};
use task_local::task_local; use futures::StreamExt;
use futures::future::LocalBoxFuture;
use futures::stream::FuturesUnordered;
use orchid_async_utils::cancel_cleanup;
#[derive(Default)] thread_local! {
struct StashedFutures { /// # Invariant
queue: RefCell<VecDeque<Pin<Box<dyn Future<Output = ()>>>>>, ///
} /// Any function that changes the value of this thread_local must restore it before returning
static CURRENT_STASH: RefCell<Option<Vec<LocalBoxFuture<'static, ()>>>> = RefCell::default();
task_local! {
static STASHED_FUTURES: StashedFutures;
} }
/// Complete the argument future, and any futures spawned from it via [stash]. /// Complete the argument future, and any futures spawned from it via [stash].
/// This is useful mostly to guarantee that messaging destructors have run. /// This is useful mostly to guarantee that messaging destructors have run.
pub async fn with_stash<F: Future>(fut: F) -> F::Output { ///
STASHED_FUTURES /// # Cancellation
.scope(StashedFutures::default(), async { ///
let val = fut.await; /// To ensure that stashed futures run, the returned future re-stashes them a
while let Some(fut) = STASHED_FUTURES.with(|sf| sf.queue.borrow_mut().pop_front()) { /// layer above when dropped. Therefore cancelling `with_stash` is only safe
fut.await; /// within an enclosing `with_stash` outside of a panic.
} pub fn with_stash<F: Future>(fut: F) -> impl Future<Output = F::Output> {
val WithStash { stash: FuturesUnordered::new(), state: WithStashState::Main(fut) }
})
.await
} }
/// Schedule a future to be run before the next [with_stash] guard ends. This is /// Schedule a future to be run before the next [with_stash] guard ends. This is
/// most useful for sending messages from destructors. /// most useful for sending messages from destructors.
///
/// # Panics
///
/// If no enclosing stash is found, this function panics, unless we are already
/// panicking. The assumption is that a panic is a vis-major where proper
/// cleanup is secondary to avoiding an abort.
pub fn stash<F: Future<Output = ()> + 'static>(fut: F) { pub fn stash<F: Future<Output = ()> + 'static>(fut: F) {
(STASHED_FUTURES.try_with(|sf| sf.queue.borrow_mut().push_back(Box::pin(fut)))) CURRENT_STASH.with(|stash| {
.expect("No stash! Timely completion cannot be guaranteed") let mut g = stash.borrow_mut();
let Some(stash) = g.as_mut() else {
if !std::thread::panicking() {
panic!("No stash! Timely completion cannot be guaranteed");
}
return;
};
stash.push(Box::pin(fut))
})
}
pub fn finish_or_stash<F: Future + Unpin + 'static>(
fut: F,
) -> impl Future<Output = F::Output> + Unpin + 'static {
cancel_cleanup(fut, |fut| {
stash(async {
fut.await;
})
})
}
enum WithStashState<F: Future> {
Main(F),
Stash {
/// Optional to simplify state management, but only ever null on a very
/// short stretch
output: Option<F::Output>,
},
}
struct WithStash<F: Future> {
stash: FuturesUnordered<LocalBoxFuture<'static, ()>>,
state: WithStashState<F>,
}
impl<F: Future> Future for WithStash<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// SAFETY: the only non-Unpin item is Main#main, and it's pinned right back
let Self { state, stash } = unsafe { Pin::get_unchecked_mut(self) };
if let WithStashState::Main(main) = state {
// SAFETY: this comes from the pin we break on the line above
let main = unsafe { Pin::new_unchecked(main) };
let prev = CURRENT_STASH.with_borrow_mut(|key| key.replace(Vec::new()));
let poll = main.poll(cx);
let stash_init = CURRENT_STASH
.with_borrow_mut(|key| std::mem::replace(key, prev))
.expect("We put a Some() in here and CURRENT_STASH demands restoration");
stash.extend(stash_init);
if let Poll::Ready(o) = poll {
// skip this branch from this point onwards
*state = WithStashState::Stash { output: Some(o) };
}
}
match state {
WithStashState::Main(_) | WithStashState::Stash { output: None, .. } => Poll::Pending,
WithStashState::Stash { output: output @ Some(_) } => loop {
// if the queue has new elements, poll_next_unpin has to be called in the next
// loop to ensure that wake-ups are triggered for them too, and if
// poll_next_unpin is called, the queue may get yet more elements synchronously,
// hence the loop
let prev = CURRENT_STASH.with_borrow_mut(|key| key.replace(Vec::new()));
let poll = stash.poll_next_unpin(cx);
let stash_new = CURRENT_STASH
.with_borrow_mut(|key| std::mem::replace(key, prev))
.expect("We put a Some() in here and CURRENT_STASH demands restoration");
stash.extend(stash_new);
match poll {
Poll::Ready(None) if stash.is_empty() => {
let output = output.take().expect("Checked in branching");
break Poll::Ready(output);
},
Poll::Pending => {
break Poll::Pending;
},
Poll::Ready(_) => continue,
}
},
}
}
}
impl<F: Future> Drop for WithStash<F> {
fn drop(&mut self) {
if std::thread::panicking() {
eprintln!("Panicking through with_stash may silently drop stashed cleanup work")
}
for future in std::mem::take(&mut self.stash) {
stash(future);
}
}
}
#[cfg(test)]
mod test {
use futures::SinkExt;
use futures::channel::mpsc;
use futures::future::join;
use orchid_async_utils::debug::spin_on;
use super::*;
#[test]
fn run_stashed_future() {
let (mut send, recv) = mpsc::channel(0);
spin_on(
false,
join(
with_stash(async {
let mut send1 = send.clone();
stash(async move {
send1.send(1).await.unwrap();
});
let mut send1 = send.clone();
stash(async move {
let mut send2 = send1.clone();
stash(async move {
send2.send(2).await.unwrap();
});
send1.send(3).await.unwrap();
stash(async move {
send1.send(4).await.unwrap();
})
});
let mut send1 = send.clone();
stash(async move {
send1.send(5).await.unwrap();
});
send.send(6).await.unwrap();
}),
async {
let mut results = recv.take(6).collect::<Vec<_>>().await;
results.sort();
assert_eq!(
&results,
&[1, 2, 3, 4, 5, 6],
"all variations completed in unspecified order"
);
},
),
);
}
} }

View File

@@ -184,27 +184,15 @@ pub trait AtomMethod: Coding + InHierarchy {
/// A handler for an [AtomMethod] on an [Atomic]. The [AtomMethod] must also be /// A handler for an [AtomMethod] on an [Atomic]. The [AtomMethod] must also be
/// registered in [Atomic::reg_methods] /// registered in [Atomic::reg_methods]
pub trait Supports<M: AtomMethod>: Atomic { pub trait Supports<M: AtomMethod>: Atomic {
fn handle<'a>( fn handle(&self, hand: Box<dyn ReqHandle>, req: M) -> impl Future<Output = io::Result<Receipt>>;
&self,
hand: Box<dyn ReqHandle<'a> + '_>,
req: M,
) -> impl Future<Output = io::Result<Receipt<'a>>>;
} }
trait HandleAtomMethod<A> { trait HandleAtomMethod<A> {
fn handle<'a, 'b: 'a>( fn handle<'a>(&'a self, atom: &'a A, reader: Box<dyn ReqReader>) -> LocalBoxFuture<'a, ()>;
&'a self,
atom: &'a A,
reader: Box<dyn ReqReader<'b> + 'a>,
) -> LocalBoxFuture<'a, ()>;
} }
struct AtomMethodHandler<M, A>(PhantomData<M>, PhantomData<A>); struct AtomMethodHandler<M, A>(PhantomData<M>, PhantomData<A>);
impl<M: AtomMethod, A: Supports<M>> HandleAtomMethod<A> for AtomMethodHandler<M, A> { impl<M: AtomMethod, A: Supports<M>> HandleAtomMethod<A> for AtomMethodHandler<M, A> {
fn handle<'a, 'b: 'a>( fn handle<'a>(&'a self, atom: &'a A, mut reader: Box<dyn ReqReader>) -> LocalBoxFuture<'a, ()> {
&'a self,
atom: &'a A,
mut reader: Box<dyn ReqReader<'b> + 'a>,
) -> LocalBoxFuture<'a, ()> {
Box::pin(async { Box::pin(async {
let req = reader.read_req::<M>().await.unwrap(); let req = reader.read_req::<M>().await.unwrap();
let _ = Supports::<M>::handle(atom, reader.finish().await, req).await.unwrap(); let _ = Supports::<M>::handle(atom, reader.finish().await, req).await.unwrap();
@@ -244,12 +232,7 @@ pub(crate) struct MethodSet<A: Atomic> {
handlers: HashMap<Sym, Rc<dyn HandleAtomMethod<A>>>, handlers: HashMap<Sym, Rc<dyn HandleAtomMethod<A>>>,
} }
impl<A: Atomic> MethodSet<A> { impl<A: Atomic> MethodSet<A> {
pub(crate) async fn dispatch<'a>( pub(crate) async fn dispatch(&self, atom: &A, key: Sym, req: Box<dyn ReqReader>) -> bool {
&self,
atom: &'_ A,
key: Sym,
req: Box<dyn ReqReader<'a> + 'a>,
) -> bool {
match self.handlers.get(&key) { match self.handlers.get(&key) {
None => false, None => false,
Some(handler) => { Some(handler) => {
@@ -341,7 +324,7 @@ pub trait AtomOps: 'static {
&'a self, &'a self,
ctx: AtomCtx<'a>, ctx: AtomCtx<'a>,
key: Sym, key: Sym,
req: Box<dyn ReqReader<'a> + 'a>, req: Box<dyn ReqReader>,
) -> LocalBoxFuture<'a, bool>; ) -> LocalBoxFuture<'a, bool>;
fn serialize<'a, 'b: 'a>( fn serialize<'a, 'b: 'a>(
&'a self, &'a self,

View File

@@ -119,7 +119,7 @@ impl<A: OwnedAtom> AtomOps for OwnedAtomOps<A> {
&'a self, &'a self,
AtomCtx(_, id): AtomCtx<'a>, AtomCtx(_, id): AtomCtx<'a>,
key: Sym, key: Sym,
req: Box<dyn orchid_base::ReqReader<'a> + 'a>, req: Box<dyn orchid_base::ReqReader>,
) -> LocalBoxFuture<'a, bool> { ) -> LocalBoxFuture<'a, bool> {
Box::pin(async move { Box::pin(async move {
let a = AtomReadGuard::new(id.unwrap()).await; let a = AtomReadGuard::new(id.unwrap()).await;

View File

@@ -53,7 +53,7 @@ impl<T: ThinAtom> AtomOps for ThinAtomOps<T> {
&'a self, &'a self,
AtomCtx(buf, ..): AtomCtx<'a>, AtomCtx(buf, ..): AtomCtx<'a>,
key: Sym, key: Sym,
req: Box<dyn orchid_base::ReqReader<'a> + 'a>, req: Box<dyn orchid_base::ReqReader>,
) -> LocalBoxFuture<'a, bool> { ) -> LocalBoxFuture<'a, bool> {
Box::pin(async move { Box::pin(async move {
let ms = self.ms.get_or_init(self.msbuild.pack()).await; let ms = self.ms.get_or_init(self.msbuild.pack()).await;

View File

@@ -6,7 +6,7 @@ use never::Never;
use orchid_base::{Receipt, ReqHandle, ReqHandleExt}; use orchid_base::{Receipt, ReqHandle, ReqHandleExt};
use crate::gen_expr::{GExpr, new_atom, serialize}; use crate::gen_expr::{GExpr, new_atom, serialize};
use crate::std_reqs::RunCommand; use crate::std_reqs::StartCommand;
use crate::{Atomic, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, ToExpr}; use crate::{Atomic, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, ToExpr};
pub trait AsyncFnDyn { pub trait AsyncFnDyn {
@@ -21,18 +21,14 @@ pub struct CmdAtom(Rc<dyn AsyncFnDyn>);
impl Atomic for CmdAtom { impl Atomic for CmdAtom {
type Data = (); type Data = ();
type Variant = OwnedVariant; type Variant = OwnedVariant;
fn reg_methods() -> MethodSetBuilder<Self> { MethodSetBuilder::new().handle::<RunCommand>() } fn reg_methods() -> MethodSetBuilder<Self> { MethodSetBuilder::new().handle::<StartCommand>() }
} }
impl Supports<RunCommand> for CmdAtom { impl Supports<StartCommand> for CmdAtom {
async fn handle<'a>( async fn handle(&self, hand: Box<dyn ReqHandle>, req: StartCommand) -> std::io::Result<Receipt> {
&self,
hand: Box<dyn ReqHandle<'a> + '_>,
req: RunCommand,
) -> std::io::Result<Receipt<'a>> {
let reply = self.0.call().await; let reply = self.0.call().await;
match reply { match reply {
None => hand.reply(&req, &None).await, None => hand.reply(&req, None).await,
Some(next) => hand.reply(&req, &Some(serialize(next).await)).await, Some(next) => hand.reply(&req, Some(serialize(next).await)).await,
} }
} }
} }

View File

@@ -8,12 +8,12 @@ use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
use futures::future::{LocalBoxFuture, join_all}; use futures::future::{LocalBoxFuture, join_all, join3};
use futures::{AsyncWriteExt, StreamExt, stream}; use futures::{AsyncReadExt, AsyncWriteExt, StreamExt, stream};
use hashbrown::HashMap; use hashbrown::HashMap;
use itertools::Itertools; use itertools::Itertools;
use orchid_api_traits::{Decode, Encode, Request, UnderRoot, enc_vec}; use orchid_api_traits::{Decode, Encode, Request, UnderRoot, enc_vec};
use orchid_async_utils::{Handle, to_task}; use orchid_async_utils::{Handle, JoinError, to_task};
use orchid_base::{ use orchid_base::{
Client, ClientExt, CommCtx, Comment, MsgReader, MsgReaderExt, ReqHandleExt, ReqReaderExt, Client, ClientExt, CommCtx, Comment, MsgReader, MsgReaderExt, ReqHandleExt, ReqReaderExt,
Snippet, Sym, TokenVariant, Witness, char_filter_match, char_filter_union, es, io_comm, is, log, Snippet, Sym, TokenVariant, Witness, char_filter_match, char_filter_union, es, io_comm, is, log,
@@ -21,6 +21,7 @@ use orchid_base::{
}; };
use substack::Substack; use substack::Substack;
use task_local::task_local; use task_local::task_local;
use unsync_pipe::pipe;
use crate::gen_expr::serialize; use crate::gen_expr::serialize;
use crate::interner::new_interner; use crate::interner::new_interner;
@@ -74,7 +75,7 @@ pub async fn request<T: Request + UnderRoot<Root = api::ExtHostReq>>(t: T) -> T:
} }
/// Send a notification through the global client's [ClientExt::notify] /// Send a notification through the global client's [ClientExt::notify]
pub async fn notify<T: UnderRoot<Root = api::ExtHostNotif>>(t: T) { pub async fn notify<T: UnderRoot<Root = api::ExtHostNotif> + 'static>(t: T) {
get_client().notify(t).await.unwrap() get_client().notify(t).await.unwrap()
} }
@@ -106,7 +107,7 @@ impl<F: AsyncFnOnce(LocalBoxFuture<'_, ()>) + 'static> ContextModifier for F {
pub(crate) trait DynTaskHandle: 'static { pub(crate) trait DynTaskHandle: 'static {
fn abort(self: Box<Self>); fn abort(self: Box<Self>);
fn join(self: Box<Self>) -> LocalBoxFuture<'static, Box<dyn Any>>; fn join(self: Box<Self>) -> LocalBoxFuture<'static, Result<Box<dyn Any>, JoinError>>;
} }
task_local! { task_local! {
@@ -124,7 +125,7 @@ impl<T: 'static> TaskHandle<T> {
/// Stop working on the task and return the nested future. The distinction /// Stop working on the task and return the nested future. The distinction
/// between this and waiting until the task is complete without reparenting it /// between this and waiting until the task is complete without reparenting it
/// is significant for the purpose of [task_local] context /// is significant for the purpose of [task_local] context
pub async fn join(self) -> T { *self.0.join().await.downcast().unwrap() } pub async fn join(self) -> Result<T, JoinError> { Ok(*self.0.join().await?.downcast().unwrap()) }
} }
/// Spawn a future that is not associated with a pending request or a past /// Spawn a future that is not associated with a pending request or a past
@@ -138,7 +139,9 @@ pub fn spawn<F: Future<Output: 'static> + 'static>(delay: Duration, f: F) -> Tas
impl DynTaskHandle for Handle<Box<dyn Any>> { impl DynTaskHandle for Handle<Box<dyn Any>> {
fn abort(self: Box<Self>) { Self::abort(&self); } fn abort(self: Box<Self>) { Self::abort(&self); }
fn join(self: Box<Self>) -> LocalBoxFuture<'static, Box<dyn Any>> { Box::pin(Self::join(*self)) } fn join(self: Box<Self>) -> LocalBoxFuture<'static, Result<Box<dyn Any>, JoinError>> {
Box::pin(Self::join(*self))
}
} }
/// A new Orchid extension as specified in loaders. An extension is a unit of /// A new Orchid extension as specified in loaders. An extension is a unit of
@@ -213,15 +216,15 @@ impl ExtensionBuilder {
match req { match req {
api::HostExtReq::SystemDrop(sys_drop) => { api::HostExtReq::SystemDrop(sys_drop) => {
SYSTEM_TABLE.with(|l| l.borrow_mut().remove(&sys_drop.0)); SYSTEM_TABLE.with(|l| l.borrow_mut().remove(&sys_drop.0));
handle.reply(&sys_drop, &()).await handle.reply(&sys_drop, ()).await
}, },
api::HostExtReq::AtomDrop(atom_drop @ api::AtomDrop(sys_id, atom)) => api::HostExtReq::AtomDrop(atom_drop @ api::AtomDrop(sys_id, atom)) =>
with_sys_record(sys_id, async { with_sys_record(sys_id, async {
take_atom(atom).await.dyn_free().await; take_atom(atom).await.dyn_free().await;
handle.reply(&atom_drop, &()).await handle.reply(&atom_drop, ()).await
}) })
.await, .await,
api::HostExtReq::Ping(ping @ api::Ping) => handle.reply(&ping, &()).await, api::HostExtReq::Ping(ping @ api::Ping) => handle.reply(&ping, ()).await,
api::HostExtReq::Sweep(api::Sweep) => todo!(), api::HostExtReq::Sweep(api::Sweep) => todo!(),
api::HostExtReq::SysReq(api::SysReq::NewSystem(new_sys)) => { api::HostExtReq::SysReq(api::SysReq::NewSystem(new_sys)) => {
let (ctor_idx, _) = (decls.iter().enumerate().find(|(_, s)| s.id == new_sys.system)) let (ctor_idx, _) = (decls.iter().enumerate().find(|(_, s)| s.id == new_sys.system))
@@ -257,7 +260,7 @@ impl ExtensionBuilder {
.await; .await;
let response = let response =
api::NewSystemResponse { lex_filter, const_root, line_types, prelude }; api::NewSystemResponse { lex_filter, const_root, line_types, prelude };
handle.reply(&new_sys, &response).await handle.reply(&new_sys, response).await
}) })
.await .await
}, },
@@ -266,17 +269,23 @@ impl ExtensionBuilder {
let (path, tree) = get_lazy(tree_id).await; let (path, tree) = get_lazy(tree_id).await;
let mut tia_ctx = let mut tia_ctx =
TreeIntoApiCtxImpl { path: Substack::Bottom, basepath: &path[..] }; TreeIntoApiCtxImpl { path: Substack::Bottom, basepath: &path[..] };
handle.reply(&get_tree, &tree.into_api(&mut tia_ctx).await).await handle.reply(&get_tree, tree.into_api(&mut tia_ctx).await).await
}) })
.await, .await,
api::HostExtReq::SysReq(api::SysReq::SysFwded(fwd)) => { api::HostExtReq::SysReq(api::SysReq::SysFwded(fwd)) => {
let fwd_tok = Witness::of(&fwd); let fwd_tok = Witness::of(&fwd);
let api::SysFwded(sys_id, payload) = fwd; let api::SysFwded(sys_id, payload) = fwd;
with_sys_record(sys_id, async { with_sys_record(sys_id, async {
let (mut req_in, req) = pipe(1024);
let (rep, mut rep_out) = pipe(1024);
let mut reply = Vec::new(); let mut reply = Vec::new();
let req = TrivialReqCycle { req: &payload, rep: &mut reply }; let (..) = join3(
let _ = dyn_cted().inst().dyn_request(Box::new(req)).await; async { req_in.write_all(&payload).await.expect("Ingress failed") },
handle.reply(fwd_tok, &reply).await async { rep_out.read_to_end(&mut reply).await.expect("Egress failed") },
dyn_cted().inst().dyn_request(Box::new(TrivialReqCycle { req, rep })),
)
.await;
handle.reply(fwd_tok, reply).await
}) })
.await .await
}, },
@@ -298,7 +307,7 @@ impl ExtensionBuilder {
Err(e) => { Err(e) => {
let eopt = e.keep_only(|e| *e != ekey_cascade).map(|e| Err(e.to_api())); let eopt = e.keep_only(|e| *e != ekey_cascade).map(|e| Err(e.to_api()));
expr_store.dispose().await; expr_store.dispose().await;
return handle.reply(&lex, &eopt).await; return handle.reply(&lex, eopt).await;
}, },
Ok((s, expr)) => { Ok((s, expr)) => {
let expr = join_all( let expr = join_all(
@@ -308,13 +317,13 @@ impl ExtensionBuilder {
.await; .await;
let pos = (text.len() - s.len()) as u32; let pos = (text.len() - s.len()) as u32;
expr_store.dispose().await; expr_store.dispose().await;
return handle.reply(&lex, &Some(Ok(api::LexedExpr { pos, expr }))).await; return handle.reply(&lex, Some(Ok(api::LexedExpr { pos, expr }))).await;
}, },
} }
} }
writeln!(log("warn"), "Got notified about n/a character '{trigger_char}'").await; writeln!(log("warn"), "Got notified about n/a character '{trigger_char}'").await;
expr_store.dispose().await; expr_store.dispose().await;
handle.reply(&lex, &None).await handle.reply(&lex, None).await
}) })
.await, .await,
api::HostExtReq::ParseLine(pline) => { api::HostExtReq::ParseLine(pline) => {
@@ -338,14 +347,14 @@ impl ExtensionBuilder {
}; };
mem::drop(line); mem::drop(line);
expr_store.dispose().await; expr_store.dispose().await;
handle.reply(req, &o_line).await handle.reply(req, o_line).await
}) })
.await .await
}, },
api::HostExtReq::FetchParsedConst(ref fpc @ api::FetchParsedConst(sys, id)) => api::HostExtReq::FetchParsedConst(ref fpc @ api::FetchParsedConst(sys, id)) =>
with_sys_record(sys, async { with_sys_record(sys, async {
let cnst = get_const(id).await; let cnst = get_const(id).await;
handle.reply(fpc, &serialize(cnst).await).await handle.reply(fpc, serialize(cnst).await).await
}) })
.await, .await,
api::HostExtReq::AtomReq(atom_req) => { api::HostExtReq::AtomReq(atom_req) => {
@@ -357,24 +366,30 @@ impl ExtensionBuilder {
api::AtomReq::SerializeAtom(ser) => { api::AtomReq::SerializeAtom(ser) => {
let mut buf = enc_vec(&id); let mut buf = enc_vec(&id);
match nfo.serialize(actx, Pin::<&mut Vec<_>>::new(&mut buf)).await { match nfo.serialize(actx, Pin::<&mut Vec<_>>::new(&mut buf)).await {
None => handle.reply(ser, &None).await, None => handle.reply(ser, None).await,
Some(refs) => { Some(refs) => {
let refs = let refs =
join_all(refs.into_iter().map(async |ex| ex.into_api(&mut ()).await)) join_all(refs.into_iter().map(async |ex| ex.into_api(&mut ()).await))
.await; .await;
handle.reply(ser, &Some((buf, refs))).await handle.reply(ser, Some((buf, refs))).await
}, },
} }
}, },
api::AtomReq::AtomPrint(print @ api::AtomPrint(_)) => api::AtomReq::AtomPrint(print @ api::AtomPrint(_)) =>
handle.reply(print, &nfo.print(actx).await.to_api()).await, handle.reply(print, nfo.print(actx).await.to_api()).await,
api::AtomReq::Fwded(fwded) => { api::AtomReq::Fwded(fwded) => {
let api::Fwded(_, key, payload) = &fwded; let api::Fwded(_, key, payload) = &fwded;
let (mut req_in, req) = pipe(1024);
let (rep, mut rep_out) = pipe(1024);
let mut reply = Vec::new(); let mut reply = Vec::new();
let key = Sym::from_api(*key).await; let key = Sym::from_api(*key).await;
let req = TrivialReqCycle { req: payload, rep: &mut reply }; let (.., some) = join3(
let some = nfo.handle_req_ref(actx, key, Box::new(req)).await; async { req_in.write_all(payload).await.expect("Ingress failed") },
handle.reply(fwded, &some.then_some(reply)).await async { rep_out.read_to_end(&mut reply).await.expect("Egress failed") },
nfo.handle_req_ref(actx, key, Box::new(TrivialReqCycle { req, rep })),
)
.await;
handle.reply(fwded, some.then_some(reply)).await
}, },
api::AtomReq::CallRef(call @ api::CallRef(_, arg)) => { api::AtomReq::CallRef(call @ api::CallRef(_, arg)) => {
let expr_store = BorrowedExprStore::new(); let expr_store = BorrowedExprStore::new();
@@ -383,7 +398,7 @@ impl ExtensionBuilder {
let api_expr = serialize(ret).await; let api_expr = serialize(ret).await;
mem::drop(expr_handle); mem::drop(expr_handle);
expr_store.dispose().await; expr_store.dispose().await;
handle.reply(call, &api_expr).await handle.reply(call, api_expr).await
}, },
api::AtomReq::FinalCall(call @ api::FinalCall(_, arg)) => { api::AtomReq::FinalCall(call @ api::FinalCall(_, arg)) => {
let expr_store = BorrowedExprStore::new(); let expr_store = BorrowedExprStore::new();
@@ -392,7 +407,7 @@ impl ExtensionBuilder {
let api_expr = serialize(ret).await; let api_expr = serialize(ret).await;
mem::drop(expr_handle); mem::drop(expr_handle);
expr_store.dispose().await; expr_store.dispose().await;
handle.reply(call, &api_expr).await handle.reply(call, api_expr).await
}, },
} }
}) })
@@ -409,7 +424,7 @@ impl ExtensionBuilder {
let id = AtomTypeId::decode_slice(read); let id = AtomTypeId::decode_slice(read);
let nfo = (dyn_cted().inst().card().ops_by_atid(id)) let nfo = (dyn_cted().inst().card().ops_by_atid(id))
.expect("Deserializing atom with invalid ID"); .expect("Deserializing atom with invalid ID");
handle.reply(&deser, &nfo.deserialize(read, &refs).await).await handle.reply(&deser, nfo.deserialize(read, &refs).await).await
}) })
.await .await
}, },

View File

@@ -16,11 +16,11 @@ impl Request for Spawn {
/// Execute the atom as a command. /// Execute the atom as a command.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
pub struct RunCommand; pub struct StartCommand;
impl Request for RunCommand { impl Request for StartCommand {
type Response = Option<api::Expression>; type Response = Option<api::Expression>;
} }
impl AtomMethod for RunCommand { impl AtomMethod for StartCommand {
const NAME: &str = "orchid::cmd::run"; const NAME: &str = "orchid::cmd::run";
} }

View File

@@ -40,23 +40,19 @@ impl OwnedAtom for WriterAtom {
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
} }
impl Supports<OutputReq> for WriterAtom { impl Supports<OutputReq> for WriterAtom {
async fn handle<'a>( async fn handle(&self, hand: Box<dyn ReqHandle>, req: OutputReq) -> Result<Receipt> {
&self,
hand: Box<dyn ReqHandle<'a> + '_>,
req: OutputReq,
) -> Result<Receipt<'a>> {
match req { match req {
OutputReq::WriteReq(ref wr @ WriteReq { ref data }) => { OutputReq::WriteReq(ref wr @ WriteReq { ref data }) => {
self.0.lock().await.buf.extend(data); self.0.lock().await.buf.extend(data);
hand.reply(wr, &Ok(())).await hand.reply(wr, Ok(())).await
}, },
OutputReq::FlushReq(ref fr @ FlushReq) => { OutputReq::FlushReq(ref fr @ FlushReq) => {
let mut g = self.0.lock().await; let mut g = self.0.lock().await;
let WriterState { buf, writer } = &mut *g; let WriterState { buf, writer } = &mut *g;
hand.reply(fr, &writer.write_all(&buf[..]).await.map_err(|e| e.into())).await hand.reply(fr, writer.write_all(&buf[..]).await.map_err(|e| e.into())).await
}, },
OutputReq::CloseReq(ref cr @ CloseReq) => OutputReq::CloseReq(ref cr @ CloseReq) =>
hand.reply(cr, &self.0.lock().await.writer.close().await.map_err(|e| e.into())).await, hand.reply(cr, self.0.lock().await.writer.close().await.map_err(|e| e.into())).await,
} }
} }
} }
@@ -80,11 +76,7 @@ impl OwnedAtom for ReaderAtom {
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
} }
impl Supports<ReadReq> for ReaderAtom { impl Supports<ReadReq> for ReaderAtom {
async fn handle<'a>( async fn handle(&self, hand: Box<dyn ReqHandle>, req: ReadReq) -> Result<Receipt> {
&self,
hand: Box<dyn ReqHandle<'a> + '_>,
req: ReadReq,
) -> Result<Receipt<'a>> {
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut reader = self.0.lock().await; let mut reader = self.0.lock().await;
let rep = match match req.limit { let rep = match match req.limit {
@@ -98,6 +90,6 @@ impl Supports<ReadReq> for ReaderAtom {
Err(e) => Err(e.into()), Err(e) => Err(e.into()),
Ok(()) => Ok(buf), Ok(()) => Ok(buf),
}; };
hand.reply(&req, &rep).await hand.reply(&req, rep).await
} }
} }

View File

@@ -20,11 +20,11 @@ pub trait System: Debug + 'static {
fn env(&self) -> impl Future<Output = Vec<GenMember>> { futures::future::ready(Vec::new()) } fn env(&self) -> impl Future<Output = Vec<GenMember>> { futures::future::ready(Vec::new()) }
fn lexers(&self) -> Vec<LexerObj> { Vec::new() } fn lexers(&self) -> Vec<LexerObj> { Vec::new() }
fn parsers(&self) -> Vec<ParserObj> { Vec::new() } fn parsers(&self) -> Vec<ParserObj> { Vec::new() }
fn request<'a>( fn request(
&self, &self,
hand: Box<dyn ReqHandle<'a> + 'a>, hand: Box<dyn ReqHandle>,
req: ReqForSystem<Self>, req: ReqForSystem<Self>,
) -> impl Future<Output = Receipt<'a>>; ) -> impl Future<Output = Receipt>;
} }
pub trait DynSystem: Debug + 'static { pub trait DynSystem: Debug + 'static {
@@ -32,10 +32,7 @@ pub trait DynSystem: Debug + 'static {
fn dyn_env(&self) -> LocalBoxFuture<'_, Vec<GenMember>>; fn dyn_env(&self) -> LocalBoxFuture<'_, Vec<GenMember>>;
fn dyn_lexers(&self) -> Vec<LexerObj>; fn dyn_lexers(&self) -> Vec<LexerObj>;
fn dyn_parsers(&self) -> Vec<ParserObj>; fn dyn_parsers(&self) -> Vec<ParserObj>;
fn dyn_request<'a, 'b: 'a>( fn dyn_request<'a, 'b: 'a>(&'a self, hand: Box<dyn ReqReader>) -> LocalBoxFuture<'a, Receipt>;
&'a self,
hand: Box<dyn ReqReader<'b> + 'b>,
) -> LocalBoxFuture<'a, Receipt<'b>>;
fn card(&self) -> Box<dyn DynSystemCard>; fn card(&self) -> Box<dyn DynSystemCard>;
} }
@@ -46,8 +43,8 @@ impl<T: System> DynSystem for T {
fn dyn_parsers(&self) -> Vec<ParserObj> { self.parsers() } fn dyn_parsers(&self) -> Vec<ParserObj> { self.parsers() }
fn dyn_request<'a, 'b: 'a>( fn dyn_request<'a, 'b: 'a>(
&'a self, &'a self,
mut hand: Box<dyn ReqReader<'b> + 'b>, mut hand: Box<dyn ReqReader>,
) -> LocalBoxFuture<'a, Receipt<'b>> { ) -> LocalBoxFuture<'a, Receipt> {
Box::pin(async move { Box::pin(async move {
let value = hand.read_req().await.unwrap(); let value = hand.read_req().await.unwrap();
self.request(hand.finish().await, value).await self.request(hand.finish().await, value).await

View File

@@ -4,25 +4,26 @@ use std::pin::Pin;
use futures::future::LocalBoxFuture; use futures::future::LocalBoxFuture;
use futures::{AsyncRead, AsyncWrite}; use futures::{AsyncRead, AsyncWrite};
use orchid_base::{Receipt, RepWriter, ReqHandle, ReqReader}; use orchid_base::{Receipt, RepWriter, ReqHandle, ReqReader};
use unsync_pipe::{Reader, Writer};
pub struct TrivialReqCycle<'a> { pub struct TrivialReqCycle {
pub req: &'a [u8], pub req: Reader,
pub rep: &'a mut Vec<u8>, pub rep: Writer,
} }
impl<'a> ReqReader<'a> for TrivialReqCycle<'a> { impl ReqReader for TrivialReqCycle {
fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { Pin::new(&mut self.req) as Pin<&mut _> } fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { Pin::new(&mut self.req) as Pin<&mut _> }
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, Box<dyn ReqHandle<'a> + 'a>> { fn finish(self: Box<Self>) -> LocalBoxFuture<'static, Box<dyn ReqHandle>> {
Box::pin(async { self as Box<_> }) Box::pin(async { self as Box<_> })
} }
} }
impl<'a> ReqHandle<'a> for TrivialReqCycle<'a> { impl ReqHandle for TrivialReqCycle {
fn start_reply(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Box<dyn RepWriter<'a> + 'a>>> { fn start_reply(self: Box<Self>) -> LocalBoxFuture<'static, io::Result<Box<dyn RepWriter>>> {
Box::pin(async { Ok(self as Box<_>) }) Box::pin(async { Ok(self as Box<_>) })
} }
} }
impl<'a> RepWriter<'a> for TrivialReqCycle<'a> { impl RepWriter for TrivialReqCycle {
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { Pin::new(&mut self.rep) as Pin<&mut _> } fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { Pin::new(&mut self.rep) as Pin<&mut _> }
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Receipt<'a>>> { fn finish(self: Box<Self>) -> LocalBoxFuture<'static, io::Result<Receipt>> {
Box::pin(async { Ok(Receipt::_new()) }) Box::pin(async { Ok(Receipt::_new()) })
} }
} }

View File

@@ -13,7 +13,7 @@ use futures::future::LocalBoxFuture;
use futures::stream::FuturesUnordered; use futures::stream::FuturesUnordered;
use futures::{SinkExt, StreamExt, select}; use futures::{SinkExt, StreamExt, select};
use never::Never; use never::Never;
use orchid_base::{OrcErrv, Receipt, ReqHandle, Sym, fmt, is, log, mk_errv}; use orchid_base::{OrcErrv, Receipt, ReqHandle, Sym, fmt, is, log, mk_errv, sym};
use orchid_extension::{self as ox, AtomicFeatures as _, get_arg}; use orchid_extension::{self as ox, AtomicFeatures as _, get_arg};
use crate::ctx::Ctx; use crate::ctx::Ctx;
@@ -100,7 +100,6 @@ pub enum CmdEvent {
} }
pub struct CmdRunner { pub struct CmdRunner {
root: Root,
queue: CommandQueue, queue: CommandQueue,
gas: Option<u64>, gas: Option<u64>,
interrupted: Option<ExecCtx>, interrupted: Option<ExecCtx>,
@@ -108,7 +107,7 @@ pub struct CmdRunner {
futures: FuturesUnordered<LocalBoxFuture<'static, Option<CmdEvent>>>, futures: FuturesUnordered<LocalBoxFuture<'static, Option<CmdEvent>>>,
} }
impl CmdRunner { impl CmdRunner {
pub async fn new(root: Root, ctx: Ctx, init: impl IntoIterator<Item = Expr>) -> Self { pub async fn new(root: &mut Root, ctx: Ctx, init: impl IntoIterator<Item = Expr>) -> Self {
let queue = CommandQueue::new(ctx.clone(), init); let queue = CommandQueue::new(ctx.clone(), init);
let ext_builder = ox::ExtensionBuilder::new("orchid::cmd").system(CmdSystemCtor(queue.clone())); let ext_builder = ox::ExtensionBuilder::new("orchid::cmd").system(CmdSystemCtor(queue.clone()));
let extension = (Extension::new(ext_inline(ext_builder, ctx.clone()).await, ctx).await) let extension = (Extension::new(ext_inline(ext_builder, ctx.clone()).await, ctx).await)
@@ -116,16 +115,17 @@ impl CmdRunner {
let system_ctor = (extension.system_ctors().find(|ctor| ctor.decl.name == "orchid::cmd")) let system_ctor = (extension.system_ctors().find(|ctor| ctor.decl.name == "orchid::cmd"))
.expect("Missing command system ctor"); .expect("Missing command system ctor");
let (cmd_root, system) = system_ctor.run(vec![]).await; let (cmd_root, system) = system_ctor.run(vec![]).await;
let root = root.merge(&cmd_root).await.expect("Could not merge command system into tree"); *root = root.merge(&cmd_root).await.expect("Could not merge command system into tree");
Self { futures: FuturesUnordered::new(), gas: None, root, interrupted: None, queue, system } Self { futures: FuturesUnordered::new(), gas: None, interrupted: None, queue, system }
} }
pub fn push(&self, expr: Expr) { self.queue.push(Task::RunCommand(expr)); }
#[must_use] #[must_use]
pub fn sys(&self) -> &System { &self.system } pub fn sys(&self) -> &System { &self.system }
#[must_use] #[must_use]
pub fn get_gas(&self) -> u64 { self.gas.expect("queried gas but no gas was set") } pub fn get_gas(&self) -> u64 { self.gas.expect("queried gas but no gas was set") }
pub fn set_gas(&mut self, gas: u64) { self.gas = Some(gas) } pub fn set_gas(&mut self, gas: u64) { self.gas = Some(gas) }
pub fn disable_gas(&mut self) { self.gas = None } pub fn disable_gas(&mut self) { self.gas = None }
pub async fn execute(&mut self) -> CmdEvent { pub async fn execute(&mut self, root: &Root) -> CmdEvent {
let waiting_on_queue = RefCell::new(false); let waiting_on_queue = RefCell::new(false);
let (mut spawn, mut on_spawn) = mpsc::channel::<LocalBoxFuture<Option<CmdEvent>>>(1); let (mut spawn, mut on_spawn) = mpsc::channel::<LocalBoxFuture<Option<CmdEvent>>>(1);
let mut normalize_stream = pin!( let mut normalize_stream = pin!(
@@ -134,7 +134,7 @@ impl CmdRunner {
waiting_on_queue.replace(false); waiting_on_queue.replace(false);
let mut xctx = match self.interrupted.take() { let mut xctx = match self.interrupted.take() {
None => match self.queue.get_new().await { None => match self.queue.get_new().await {
Task::RunCommand(expr) => ExecCtx::new(self.root.clone(), expr).await, Task::RunCommand(expr) => ExecCtx::new(root.clone(), expr).await,
Task::Sleep(until, expr) => { Task::Sleep(until, expr) => {
let queue = self.queue.clone(); let queue = self.queue.clone();
let ctx = queue.0.borrow_mut().ctx.clone(); let ctx = queue.0.borrow_mut().ctx.clone();
@@ -186,7 +186,7 @@ impl CmdRunner {
let ctx = queue.0.borrow_mut().ctx.clone(); let ctx = queue.0.borrow_mut().ctx.clone();
spawn spawn
.send(Box::pin(async move { .send(Box::pin(async move {
match atom.ipc(ox::std_reqs::RunCommand).await { match atom.ipc(ox::std_reqs::StartCommand).await {
None => Some(CmdEvent::NonCommand(val)), None => Some(CmdEvent::NonCommand(val)),
Some(None) => None, Some(None) => None,
Some(Some(expr)) => { Some(Some(expr)) => {
@@ -209,8 +209,14 @@ impl CmdRunner {
loop { loop {
let task = select!( let task = select!(
r_opt = self.futures.by_ref().next() => match r_opt { r_opt = self.futures.by_ref().next() => match r_opt {
Some(Some(r)) => break r, Some(Some(r)) => {
None if *waiting_on_queue.borrow() => break CmdEvent::Settled, eprintln!("Exiting because ");
break r
},
None if *waiting_on_queue.borrow() => {
eprintln!("Exiting because settled");
break CmdEvent::Settled
},
None | Some(None) => continue, None | Some(None) => continue,
}, },
r = normalize_stream.by_ref().next() => match r { r = normalize_stream.by_ref().next() => match r {
@@ -218,7 +224,7 @@ impl CmdRunner {
Some(r) => break r, Some(r) => break r,
}, },
task = on_spawn.by_ref().next() => match task { task = on_spawn.by_ref().next() => match task {
None => break CmdEvent::Exit, None => continue,
Some(r) => r, Some(r) => r,
}, },
); );
@@ -301,14 +307,10 @@ impl ox::System for CmdSystemInst {
}), }),
]) ])
} }
async fn prelude(&self) -> Vec<Sym> { vec![] } async fn prelude(&self) -> Vec<Sym> { vec![sym!("orchid")] }
fn lexers(&self) -> Vec<ox::LexerObj> { vec![] } fn lexers(&self) -> Vec<ox::LexerObj> { vec![] }
fn parsers(&self) -> Vec<ox::ParserObj> { vec![] } fn parsers(&self) -> Vec<ox::ParserObj> { vec![] }
async fn request<'a>( async fn request(&self, _hand: Box<dyn ReqHandle>, req: ox::ReqForSystem<Self>) -> Receipt {
&self,
_hand: Box<dyn ReqHandle<'a> + 'a>,
req: ox::ReqForSystem<Self>,
) -> Receipt<'a> {
match req {} match req {}
} }
} }

View File

@@ -133,31 +133,31 @@ impl Extension {
} }
let this = Self(weak.upgrade().unwrap()); let this = Self(weak.upgrade().unwrap());
match req { match req {
api::ExtHostReq::Ping(ping) => handle.reply(&ping, &()).await, api::ExtHostReq::Ping(ping) => handle.reply(&ping, ()).await,
api::ExtHostReq::IntReq(intreq) => match intreq { api::ExtHostReq::IntReq(intreq) => match intreq {
api::IntReq::InternStr(s) => { api::IntReq::InternStr(s) => {
let i = is(&s.0).await; let i = is(&s.0).await;
this.0.strings.borrow_mut().insert(i.clone()); this.0.strings.borrow_mut().insert(i.clone());
handle.reply(&s, &i.to_api()).await handle.reply(&s, i.to_api()).await
}, },
api::IntReq::InternStrv(v) => { api::IntReq::InternStrv(v) => {
let tokens = join_all(v.0.iter().map(|m| es(*m))).await; let tokens = join_all(v.0.iter().map(|m| es(*m))).await;
this.0.strings.borrow_mut().extend(tokens.iter().cloned()); this.0.strings.borrow_mut().extend(tokens.iter().cloned());
let i = iv(&tokens).await; let i = iv(&tokens).await;
this.0.string_vecs.borrow_mut().insert(i.clone()); this.0.string_vecs.borrow_mut().insert(i.clone());
handle.reply(&v, &i.to_api()).await handle.reply(&v, i.to_api()).await
}, },
api::IntReq::ExternStr(si) => { api::IntReq::ExternStr(si) => {
let i = es(si.0).await; let i = es(si.0).await;
this.0.strings.borrow_mut().insert(i.clone()); this.0.strings.borrow_mut().insert(i.clone());
handle.reply(&si, &i.to_string()).await handle.reply(&si, i.to_string()).await
}, },
api::IntReq::ExternStrv(vi) => { api::IntReq::ExternStrv(vi) => {
let i = ev(vi.0).await; let i = ev(vi.0).await;
this.0.strings.borrow_mut().extend(i.iter().cloned()); this.0.strings.borrow_mut().extend(i.iter().cloned());
this.0.string_vecs.borrow_mut().insert(i.clone()); this.0.string_vecs.borrow_mut().insert(i.clone());
let markerv = i.iter().map(|t| t.to_api()).collect_vec(); let markerv = i.iter().map(|t| t.to_api()).collect_vec();
handle.reply(&vi, &markerv).await handle.reply(&vi, markerv).await
}, },
}, },
api::ExtHostReq::Fwd(ref fw @ api::Fwd { ref target, ref method, ref body }) => { api::ExtHostReq::Fwd(ref fw @ api::Fwd { ref target, ref method, ref body }) => {
@@ -168,11 +168,11 @@ impl Extension {
.request(api::Fwded(target.clone(), *method, body.clone())) .request(api::Fwded(target.clone(), *method, body.clone()))
.await .await
.unwrap(); .unwrap();
handle.reply(fw, &reply).await handle.reply(fw, reply).await
}, },
api::ExtHostReq::SysFwd(ref fw @ api::SysFwd(id, ref body)) => { api::ExtHostReq::SysFwd(ref fw @ api::SysFwd(id, ref body)) => {
let sys = ctx.system_inst(id).await.unwrap(); let sys = ctx.system_inst(id).await.unwrap();
handle.reply(fw, &sys.request(body.clone()).await).await handle.reply(fw, sys.request(body.clone()).await).await
}, },
api::ExtHostReq::SubLex(sl) => { api::ExtHostReq::SubLex(sl) => {
let (rep_in, mut rep_out) = channel(0); let (rep_in, mut rep_out) = channel(0);
@@ -182,13 +182,13 @@ impl Extension {
lex_g.get(&sl.id).cloned().expect("Sublex for nonexistent lexid"); lex_g.get(&sl.id).cloned().expect("Sublex for nonexistent lexid");
req_in.send(ReqPair(sl.clone(), rep_in)).await.unwrap(); req_in.send(ReqPair(sl.clone(), rep_in)).await.unwrap();
} }
handle.reply(&sl, &rep_out.next().await.unwrap()).await handle.reply(&sl, rep_out.next().await.unwrap()).await
}, },
api::ExtHostReq::ExprReq(expr_req) => match expr_req { api::ExtHostReq::ExprReq(expr_req) => match expr_req {
api::ExprReq::Inspect(ins @ api::Inspect { target }) => { api::ExprReq::Inspect(ins @ api::Inspect { target }) => {
let expr = ctx.exprs.get_expr(target).expect("Invalid ticket"); let expr = ctx.exprs.get_expr(target).expect("Invalid ticket");
handle handle
.reply(&ins, &api::Inspected { .reply(&ins, api::Inspected {
refcount: expr.strong_count() as u32, refcount: expr.strong_count() as u32,
location: expr.pos().to_api(), location: expr.pos().to_api(),
kind: expr.to_api().await, kind: expr.to_api().await,
@@ -201,7 +201,7 @@ impl Extension {
Some(expr) => expr.print(&FmtCtxImpl::default()).await, Some(expr) => expr.print(&FmtCtxImpl::default()).await,
} }
.to_api(); .to_api();
handle.reply(&prt, &msg).await handle.reply(&prt, msg).await
}, },
api::ExprReq::Create(cre) => { api::ExprReq::Create(cre) => {
let req = Witness::of(&cre); let req = Witness::of(&cre);
@@ -213,7 +213,7 @@ impl Extension {
.await; .await;
let expr_id = expr.id(); let expr_id = expr.id();
ctx.exprs.give_expr(expr); ctx.exprs.give_expr(expr);
handle.reply(req, &expr_id).await handle.reply(req, expr_id).await
}, },
}, },
api::ExtHostReq::LsModule(ref ls @ api::LsModule(_sys, path)) => { api::ExtHostReq::LsModule(ref ls @ api::LsModule(_sys, path)) => {
@@ -246,7 +246,7 @@ impl Extension {
} }
Ok(api::ModuleInfo { members }) Ok(api::ModuleInfo { members })
}; };
handle.reply(ls, &reply).await handle.reply(ls, reply).await
}, },
api::ExtHostReq::ResolveNames(ref rn) => { api::ExtHostReq::ResolveNames(ref rn) => {
let api::ResolveNames { constid, names, sys } = rn; let api::ResolveNames { constid, names, sys } = rn;
@@ -275,12 +275,12 @@ impl Extension {
}) })
.collect() .collect()
.await; .await;
handle.reply(rn, &responses).await handle.reply(rn, responses).await
}, },
api::ExtHostReq::ExtAtomPrint(ref eap @ api::ExtAtomPrint(ref atom)) => { api::ExtHostReq::ExtAtomPrint(ref eap @ api::ExtAtomPrint(ref atom)) => {
let atom = AtomHand::from_api(atom, Pos::None, &mut ctx.clone()).await; let atom = AtomHand::from_api(atom, Pos::None, &mut ctx.clone()).await;
let unit = atom.print(&FmtCtxImpl::default()).await; let unit = atom.print(&FmtCtxImpl::default()).await;
handle.reply(eap, &unit.to_api()).await handle.reply(eap, unit.to_api()).await
}, },
} }
}) })

View File

@@ -48,9 +48,7 @@ pub struct MacroSystemInst {
} }
impl System for MacroSystemInst { impl System for MacroSystemInst {
type Ctor = MacroSystem; type Ctor = MacroSystem;
async fn request<'a>(&self, _: Box<dyn ReqHandle<'a> + 'a>, req: Never) -> Receipt<'a> { async fn request(&self, _: Box<dyn ReqHandle>, req: Never) -> Receipt { match req {} }
match req {}
}
async fn prelude(&self) -> Vec<Sym> { async fn prelude(&self) -> Vec<Sym> {
vec![ vec![
sym!(macros::common::+), sym!(macros::common::+),

View File

@@ -45,13 +45,9 @@ impl ToExpr for Int {
} }
} }
impl Supports<ProtocolMethod> for Int { impl Supports<ProtocolMethod> for Int {
async fn handle<'a>( async fn handle(&self, hand: Box<dyn ReqHandle>, req: ProtocolMethod) -> io::Result<Receipt> {
&self,
hand: Box<dyn ReqHandle<'a> + '_>,
req: ProtocolMethod,
) -> io::Result<Receipt<'a>> {
match req { match req {
ProtocolMethod::GetTagId(req) => hand.reply(&req, &sym!(std::number::Int).to_api()).await, ProtocolMethod::GetTagId(req) => hand.reply(&req, sym!(std::number::Int).to_api()).await,
ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => { ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => {
let name = Sym::from_api(key).await; let name = Sym::from_api(key).await;
let val = if name == sym!(std::ops::add) { let val = if name == sym!(std::ops::add) {
@@ -65,20 +61,16 @@ impl Supports<ProtocolMethod> for Int {
} else if name == sym!(std::ops::mod) { } else if name == sym!(std::ops::mod) {
sym!(std::number::imod) sym!(std::number::imod)
} else { } else {
return hand.reply(req, &None).await; return hand.reply(req, None).await;
}; };
hand.reply(req, &Some(val.to_expr().await.serialize().await)).await hand.reply(req, Some(val.to_expr().await.serialize().await)).await
}, },
} }
} }
} }
impl Supports<ToStringMethod> for Int { impl Supports<ToStringMethod> for Int {
async fn handle<'a>( async fn handle(&self, hand: Box<dyn ReqHandle>, req: ToStringMethod) -> io::Result<Receipt> {
&self, hand.reply(&req, self.0.to_string()).await
hand: Box<dyn ReqHandle<'a> + '_>,
req: ToStringMethod,
) -> io::Result<Receipt<'a>> {
hand.reply(&req, &self.0.to_string()).await
} }
} }
@@ -112,13 +104,9 @@ impl ToExpr for Float {
} }
} }
impl Supports<ProtocolMethod> for Float { impl Supports<ProtocolMethod> for Float {
async fn handle<'a>( async fn handle(&self, hand: Box<dyn ReqHandle>, req: ProtocolMethod) -> io::Result<Receipt> {
&self,
hand: Box<dyn ReqHandle<'a> + '_>,
req: ProtocolMethod,
) -> io::Result<Receipt<'a>> {
match req { match req {
ProtocolMethod::GetTagId(req) => hand.reply(&req, &sym!(std::number::Float).to_api()).await, ProtocolMethod::GetTagId(req) => hand.reply(&req, sym!(std::number::Float).to_api()).await,
ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => { ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => {
let name = Sym::from_api(key).await; let name = Sym::from_api(key).await;
let val = if name == sym!(std::ops::add) { let val = if name == sym!(std::ops::add) {
@@ -132,20 +120,16 @@ impl Supports<ProtocolMethod> for Float {
} else if name == sym!(std::ops::mod) { } else if name == sym!(std::ops::mod) {
sym!(std::number::fmod) sym!(std::number::fmod)
} else { } else {
return hand.reply(req, &None).await; return hand.reply(req, None).await;
}; };
hand.reply(req, &Some(val.to_expr().await.serialize().await)).await hand.reply(req, Some(val.to_expr().await.serialize().await)).await
}, },
} }
} }
} }
impl Supports<ToStringMethod> for Float { impl Supports<ToStringMethod> for Float {
async fn handle<'a>( async fn handle(&self, hand: Box<dyn ReqHandle>, req: ToStringMethod) -> io::Result<Receipt> {
&self, hand.reply(&req, self.0.to_string()).await
hand: Box<dyn ReqHandle<'a> + '_>,
req: ToStringMethod,
) -> io::Result<Receipt<'a>> {
hand.reply(&req, &self.0.to_string()).await
} }
} }

View File

@@ -36,16 +36,16 @@ impl OwnedAtom for Tag {
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(self.id.to_api()) } async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(self.id.to_api()) }
} }
impl Supports<ProtocolMethod> for Tag { impl Supports<ProtocolMethod> for Tag {
async fn handle<'a>( async fn handle(
&self, &self,
hand: Box<dyn orchid_base::ReqHandle<'a> + '_>, hand: Box<dyn orchid_base::ReqHandle>,
req: ProtocolMethod, req: ProtocolMethod,
) -> std::io::Result<orchid_base::Receipt<'a>> { ) -> std::io::Result<orchid_base::Receipt> {
match req { match req {
ProtocolMethod::GetTagId(req) => hand.reply(&req, &self.id.to_api()).await, ProtocolMethod::GetTagId(req) => hand.reply(&req, self.id.to_api()).await,
ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => ProtocolMethod::GetImpl(ref req @ GetImpl(key)) =>
hand hand
.reply(req, &self.impls.get(&Sym::from_api(key).await).map(|expr| expr.handle().ticket())) .reply(req, self.impls.get(&Sym::from_api(key).await).map(|expr| expr.handle().ticket()))
.await, .await,
} }
} }
@@ -87,11 +87,11 @@ impl OwnedAtom for Tagged {
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(self.tag.id.to_api()) } async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(self.tag.id.to_api()) }
} }
impl Supports<ProtocolMethod> for Tagged { impl Supports<ProtocolMethod> for Tagged {
async fn handle<'a>( async fn handle(
&self, &self,
hand: Box<dyn orchid_base::ReqHandle<'a> + '_>, hand: Box<dyn orchid_base::ReqHandle>,
req: ProtocolMethod, req: ProtocolMethod,
) -> io::Result<orchid_base::Receipt<'a>> { ) -> io::Result<orchid_base::Receipt> {
self.tag.handle(hand, req).await self.tag.handle(hand, req).await
} }
} }

View File

@@ -40,13 +40,9 @@ impl OwnedAtom for RecordAtom {
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
} }
impl Supports<ProtocolMethod> for RecordAtom { impl Supports<ProtocolMethod> for RecordAtom {
async fn handle<'a>( async fn handle(&self, hand: Box<dyn ReqHandle>, req: ProtocolMethod) -> io::Result<Receipt> {
&self,
hand: Box<dyn ReqHandle<'a> + '_>,
req: ProtocolMethod,
) -> io::Result<Receipt<'a>> {
match req { match req {
ProtocolMethod::GetTagId(req) => hand.reply(&req, &sym!(std::record::Record).to_api()).await, ProtocolMethod::GetTagId(req) => hand.reply(&req, sym!(std::record::Record).to_api()).await,
ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => { ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => {
let name = Sym::from_api(key).await; let name = Sym::from_api(key).await;
let val = if name == sym!(std::ops::get) { let val = if name == sym!(std::ops::get) {
@@ -54,9 +50,9 @@ impl Supports<ProtocolMethod> for RecordAtom {
} else if name == sym!(std::ops::set) { } else if name == sym!(std::ops::set) {
sym!(std::record::set) sym!(std::record::set)
} else { } else {
return hand.reply(req, &None).await; return hand.reply(req, None).await;
}; };
return hand.reply(req, &Some(val.to_expr().await.serialize().await)).await; return hand.reply(req, Some(val.to_expr().await.serialize().await)).await;
}, },
} }
} }

View File

@@ -27,12 +27,12 @@ impl OwnedAtom for SymAtom {
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(SymAtomData(self.0.tok().to_api())) } async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(SymAtomData(self.0.tok().to_api())) }
} }
impl Supports<ToStringMethod> for SymAtom { impl Supports<ToStringMethod> for SymAtom {
async fn handle<'a>( async fn handle(
&self, &self,
hand: Box<dyn orchid_base::ReqHandle<'a> + '_>, hand: Box<dyn orchid_base::ReqHandle>,
req: ToStringMethod, req: ToStringMethod,
) -> std::io::Result<orchid_base::Receipt<'a>> { ) -> std::io::Result<orchid_base::Receipt> {
hand.reply(&req, &self.0.to_string()).await hand.reply(&req, self.0.to_string()).await
} }
} }

View File

@@ -27,6 +27,7 @@ use crate::std::protocol::types::{CreateTag, Tag, Tagged, gen_protocol_lib};
use crate::std::record::record_atom::{CreateRecord, RecordAtom}; use crate::std::record::record_atom::{CreateRecord, RecordAtom};
use crate::std::record::record_lib::gen_record_lib; use crate::std::record::record_lib::gen_record_lib;
use crate::std::reflection::sym_atom::{CreateSymAtom, SymAtom, gen_sym_lib}; use crate::std::reflection::sym_atom::{CreateSymAtom, SymAtom, gen_sym_lib};
use crate::std::stream::stream_cmds::{ReadStreamCmd, WriteStreamCmd};
use crate::std::stream::stream_lib::gen_stream_lib; use crate::std::stream::stream_lib::gen_stream_lib;
use crate::std::string::str_lexer::StringLexer; use crate::std::string::str_lexer::StringLexer;
use crate::std::time::{CreateDT, gen_time_lib}; use crate::std::time::{CreateDT, gen_time_lib};
@@ -72,27 +73,25 @@ impl SystemCard for StdSystem {
Some(TupleBuilder::ops()), Some(TupleBuilder::ops()),
Some(Tag::ops()), Some(Tag::ops()),
Some(Tagged::ops()), Some(Tagged::ops()),
Some(ReadStreamCmd::ops()),
Some(WriteStreamCmd::ops()),
] ]
} }
} }
impl System for StdSystem { impl System for StdSystem {
type Ctor = Self; type Ctor = Self;
async fn request<'a>( async fn request(&self, xreq: Box<dyn ReqHandle>, req: ReqForSystem<Self>) -> Receipt {
&self,
xreq: Box<dyn ReqHandle<'a> + 'a>,
req: ReqForSystem<Self>,
) -> Receipt<'a> {
match req { match req {
StdReq::CreateInt(ref req @ CreateInt(int)) => StdReq::CreateInt(ref req @ CreateInt(int)) =>
xreq.reply(req, &new_atom(int).to_expr().await.serialize().await).await.unwrap(), xreq.reply(req, new_atom(int).to_expr().await.serialize().await).await.unwrap(),
StdReq::CreateFloat(ref req @ CreateFloat(float)) => StdReq::CreateFloat(ref req @ CreateFloat(float)) =>
xreq.reply(req, &new_atom(float).to_expr().await.serialize().await).await.unwrap(), xreq.reply(req, new_atom(float).to_expr().await.serialize().await).await.unwrap(),
StdReq::CreateDT(ref req @ CreateDT(dt)) => StdReq::CreateDT(ref req @ CreateDT(dt)) =>
xreq.reply(req, &new_atom(dt).to_expr().await.serialize().await).await.unwrap(), xreq.reply(req, new_atom(dt).to_expr().await.serialize().await).await.unwrap(),
StdReq::CreateTuple(ref req @ CreateTuple(ref items)) => { StdReq::CreateTuple(ref req @ CreateTuple(ref items)) => {
let tpl = Tuple(Rc::new(join_all(items.iter().copied().map(Expr::deserialize)).await)); let tpl = Tuple(Rc::new(join_all(items.iter().copied().map(Expr::deserialize)).await));
let tk = new_atom(tpl).to_expr().await.serialize().await; let tk = new_atom(tpl).to_expr().await.serialize().await;
xreq.reply(req, &tk).await.unwrap() xreq.reply(req, tk).await.unwrap()
}, },
StdReq::CreateRecord(ref req @ CreateRecord(ref items)) => { StdReq::CreateRecord(ref req @ CreateRecord(ref items)) => {
let values = let values =
@@ -100,11 +99,11 @@ impl System for StdSystem {
.await; .await;
let rec = RecordAtom(Rc::new(values.into_iter().collect())); let rec = RecordAtom(Rc::new(values.into_iter().collect()));
let tk = new_atom(rec).to_expr().await.serialize().await; let tk = new_atom(rec).to_expr().await.serialize().await;
xreq.reply(req, &tk).await.unwrap() xreq.reply(req, tk).await.unwrap()
}, },
StdReq::CreateSymAtom(ref req @ CreateSymAtom(sym_tok)) => { StdReq::CreateSymAtom(ref req @ CreateSymAtom(sym_tok)) => {
let sym_atom = SymAtom(Sym::from_api(sym_tok).await); let sym_atom = SymAtom(Sym::from_api(sym_tok).await);
xreq.reply(req, &new_atom(sym_atom).to_expr().await.serialize().await).await.unwrap() xreq.reply(req, new_atom(sym_atom).to_expr().await.serialize().await).await.unwrap()
}, },
StdReq::CreateTag(ref req @ CreateTag { name, ref impls }) => { StdReq::CreateTag(ref req @ CreateTag { name, ref impls }) => {
let tag_atom = Tag { let tag_atom = Tag {
@@ -119,7 +118,7 @@ impl System for StdSystem {
.collect(), .collect(),
), ),
}; };
xreq.reply(req, &new_atom(tag_atom).to_expr().await.serialize().await).await.unwrap() xreq.reply(req, new_atom(tag_atom).to_expr().await.serialize().await).await.unwrap()
}, },
} }
} }

View File

@@ -3,12 +3,15 @@ use std::io;
use std::rc::Rc; use std::rc::Rc;
use never::Never; use never::Never;
use orchid_base::{ReqHandleExt, fmt, is, mk_errv}; use orchid_base::{Receipt, ReqHandle, ReqHandleExt, fmt, is, mk_errv};
use orchid_extension::gen_expr::{bot, call, new_atom, serialize}; use orchid_extension::gen_expr::{bot, call, new_atom, serialize};
use orchid_extension::std_reqs::{ReadLimit, ReadReq, RunCommand}; use orchid_extension::std_reqs::{CloseReq, FlushReq, ReadLimit, ReadReq, StartCommand, WriteReq};
use orchid_extension::{Atomic, Expr, ForeignAtom, OwnedAtom, OwnedVariant, Supports, ToExpr}; use orchid_extension::{
Atomic, Expr, ForeignAtom, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, ToExpr,
};
use crate::std::binary::binary_atom::BlobAtom; use crate::std::binary::binary_atom::BlobAtom;
use crate::std::string::str_atom::StrAtom;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ReadStreamCmd { pub struct ReadStreamCmd {
@@ -16,40 +19,99 @@ pub struct ReadStreamCmd {
pub limit: ReadLimit, pub limit: ReadLimit,
pub succ: Expr, pub succ: Expr,
pub fail: Expr, pub fail: Expr,
pub as_str: bool,
} }
impl Atomic for ReadStreamCmd { impl Atomic for ReadStreamCmd {
type Variant = OwnedVariant; type Variant = OwnedVariant;
type Data = (); type Data = ();
fn reg_methods() -> MethodSetBuilder<Self> { MethodSetBuilder::new().handle::<StartCommand>() }
} }
impl OwnedAtom for ReadStreamCmd { impl OwnedAtom for ReadStreamCmd {
type Refs = Never; type Refs = Never;
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
} }
impl Supports<RunCommand> for ReadStreamCmd { impl Supports<StartCommand> for ReadStreamCmd {
async fn handle<'a>( async fn handle(&self, hand: Box<dyn ReqHandle>, req: StartCommand) -> io::Result<Receipt> {
&self, let ret = 'ret: {
hand: Box<dyn orchid_base::ReqHandle<'a> + '_>, let Some(read_res) = self.hand.call(ReadReq { limit: self.limit.clone() }).await else {
req: RunCommand, break 'ret Err(mk_errv(
) -> io::Result<orchid_base::Receipt<'a>> { is("Atom is not readable").await,
let ret = match self.hand.call(ReadReq { limit: self.limit.clone() }).await { format!("Expected a readable stream handle, found {}", fmt(&self.hand).await),
None => Err(mk_errv( [self.hand.pos()],
is("Atom is not readable").await, ));
format!("Expected a readable stream handle, found {}", fmt(&self.hand).await), };
[self.hand.pos()], let res = match read_res {
Err(e) => Err(mk_errv(
is(e.kind.message()).await,
format!("An error occurred while reading: {}", e.message),
[self.hand.pos(), self.succ.pos().await],
)),
Ok(v) if !self.as_str => Ok(new_atom(BlobAtom(Rc::new(v)))),
Ok(v) => match String::from_utf8(v) {
Ok(s) => Ok(new_atom(StrAtom(Rc::new(s)))),
Err(e) => Err(mk_errv(is("Invalid utf8 in input string").await, e.to_string(), [
self.hand.pos(),
self.succ.pos().await,
])),
},
};
Ok(match res {
Err(e) => call(self.fail.clone(), bot(e)),
Ok(gex) => call(self.succ.clone(), gex),
})
};
hand.reply(&req, Some(serialize(ret.to_gen().await).await)).await
}
}
#[derive(Clone, Debug)]
pub enum WriteAction {
Write(Rc<Vec<u8>>),
Flush,
Close,
}
#[derive(Clone, Debug)]
pub struct WriteStreamCmd {
pub hand: ForeignAtom,
pub action: WriteAction,
pub succ: Expr,
pub fail: Expr,
}
impl Atomic for WriteStreamCmd {
type Variant = OwnedVariant;
type Data = ();
fn reg_methods() -> MethodSetBuilder<Self> { MethodSetBuilder::new().handle::<StartCommand>() }
}
impl OwnedAtom for WriteStreamCmd {
type Refs = Never;
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
}
impl Supports<StartCommand> for WriteStreamCmd {
async fn handle(&self, hand: Box<dyn ReqHandle>, req: StartCommand) -> io::Result<Receipt> {
let result = match &self.action {
WriteAction::Write(bin) => self.hand.call(WriteReq { data: bin.to_vec() }).await,
WriteAction::Flush => self.hand.call(FlushReq).await,
WriteAction::Close => self.hand.call(CloseReq).await,
};
let cont = match result {
None => bot(mk_errv(
is("Not a writer").await,
format!("{} cannot be written to", fmt(&self.hand).await),
[self.hand.pos(), self.succ.pos().await],
)), )),
Some(Err(e)) => Ok( Some(Err(e)) =>
call( call(
self.fail.clone(), self.fail.clone(),
bot(mk_errv( bot(mk_errv(
is(e.kind.message()).await, is(e.kind.message()).await,
format!("An error occurred while reading: {}", e.message), format!("An error occurred while writing: {}", e.message),
[self.hand.pos(), self.succ.pos().await], [self.hand.pos(), self.succ.pos().await],
)), )),
) )
.await, .await,
), Some(Ok(())) => self.succ.clone().to_gen().await,
Some(Ok(v)) => Ok(call(self.succ.clone(), new_atom(BlobAtom(Rc::new(v)))).await),
}; };
hand.reply(&req, &Some(serialize(ret.to_gen().await).await)).await hand.reply(&req, Some(serialize(cont.to_gen().await).await)).await
} }
} }

View File

@@ -1,22 +1,26 @@
use std::num::NonZero; use std::num::NonZero;
use std::rc::Rc; use std::rc::Rc;
use itertools::Itertools;
use orchid_base::{is, mk_errv}; use orchid_base::{is, mk_errv};
use orchid_extension::gen_expr::{call, new_atom}; use orchid_extension::gen_expr::{call, new_atom};
use orchid_extension::std_reqs::ReadLimit; use orchid_extension::std_reqs::ReadLimit;
use orchid_extension::tree::{GenMember, comments, fun, prefix}; use orchid_extension::tree::{GenMember, comments, fun, prefix};
use orchid_extension::{Expr, ForeignAtom, get_arg}; use orchid_extension::{Expr, ForeignAtom, TAtom, get_arg};
use crate::Int;
use crate::std::binary::binary_atom::BlobAtom; use crate::std::binary::binary_atom::BlobAtom;
use crate::std::stream::stream_cmds::ReadStreamCmd; use crate::std::stream::stream_cmds::{ReadStreamCmd, WriteAction, WriteStreamCmd};
use crate::{Int, OrcString};
pub fn gen_stream_lib() -> Vec<GenMember> { pub fn gen_stream_lib() -> Vec<GenMember> {
prefix("std", [comments( prefix("std", [comments(
["Read from and write to byte streams"], ["Read from and write to byte streams"],
prefix("stream", [ prefix("stream", [
fun(true, "read_bin", async |hand: ForeignAtom, succ: Expr, fail: Expr| { fun(true, "read_bin", async |hand: ForeignAtom, succ: Expr, fail: Expr| {
new_atom(ReadStreamCmd { hand, succ, fail, limit: ReadLimit::End }) new_atom(ReadStreamCmd { hand, succ, fail, as_str: false, limit: ReadLimit::End })
}),
fun(true, "read_str", async |hand: ForeignAtom, succ: Expr, fail: Expr| {
new_atom(ReadStreamCmd { hand, succ, fail, as_str: true, limit: ReadLimit::End })
}), }),
fun(true, "read_until", async |hand: ForeignAtom, delim: Int, succ: Expr, fail: Expr| { fun(true, "read_until", async |hand: ForeignAtom, delim: Int, succ: Expr, fail: Expr| {
let Ok(end) = delim.0.try_into() else { let Ok(end) = delim.0.try_into() else {
@@ -29,12 +33,15 @@ pub fn gen_stream_lib() -> Vec<GenMember> {
[get_arg(1).pos().await], [get_arg(1).pos().await],
)); ));
}; };
Ok(new_atom(ReadStreamCmd { hand, succ, fail, limit: ReadLimit::Delimiter(end) })) let limit = ReadLimit::Delimiter(end);
Ok(new_atom(ReadStreamCmd { hand, succ, fail, as_str: false, limit }))
}), }),
fun(true, "read_bytes", async |hand: ForeignAtom, count: Int, succ: Expr, fail: Expr| { fun(true, "read_bytes", async |hand: ForeignAtom, count: Int, succ: Expr, fail: Expr| {
match count.0.try_into().map(NonZero::new) { match count.0.try_into().map(NonZero::new) {
Ok(Some(nzlen)) => Ok(Some(nzlen)) => {
Ok(new_atom(ReadStreamCmd { hand, succ, fail, limit: ReadLimit::Length(nzlen) })), let limit = ReadLimit::Length(nzlen);
Ok(new_atom(ReadStreamCmd { hand, succ, fail, as_str: false, limit }))
},
Ok(None) => Ok(call(succ, new_atom(BlobAtom(Rc::default()))).await), Ok(None) => Ok(call(succ, new_atom(BlobAtom(Rc::default()))).await),
Err(_) => Err(mk_errv( Err(_) => Err(mk_errv(
is("Length cannot be negative").await, is("Length cannot be negative").await,
@@ -43,6 +50,28 @@ pub fn gen_stream_lib() -> Vec<GenMember> {
)), )),
} }
}), }),
fun(true, "read_line", async |hand: ForeignAtom, succ: Expr, fail: Expr| {
const LIMIT_BR: ReadLimit = ReadLimit::Delimiter(b'\n');
new_atom(ReadStreamCmd { hand, succ, fail, as_str: true, limit: LIMIT_BR })
}),
fun(true, "write_str", async |hand: ForeignAtom, str: OrcString, succ: Expr, fail: Expr| {
let action = WriteAction::Write(Rc::new(str.get_string().await.bytes().collect_vec()));
new_atom(WriteStreamCmd { hand, action, succ, fail })
}),
fun(
true,
"write_bin",
async |hand: ForeignAtom, bin: TAtom<BlobAtom>, succ: Expr, fail: Expr| {
let action = WriteAction::Write(bin.own().await.0.clone());
new_atom(WriteStreamCmd { hand, action, succ, fail })
},
),
fun(true, "flush", async |hand: ForeignAtom, succ: Expr, fail: Expr| {
new_atom(WriteStreamCmd { hand, action: WriteAction::Flush, succ, fail })
}),
fun(true, "close", async |hand: ForeignAtom, succ: Expr, fail: Expr| {
new_atom(WriteStreamCmd { hand, action: WriteAction::Close, succ, fail })
}),
]), ]),
)]) )])
} }

View File

@@ -28,7 +28,7 @@ impl AtomMethod for StringGetValMethod {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct StrAtom(Rc<String>); pub struct StrAtom(pub(crate) Rc<String>);
impl Atomic for StrAtom { impl Atomic for StrAtom {
type Variant = OwnedVariant; type Variant = OwnedVariant;
type Data = (); type Data = ();
@@ -60,39 +60,39 @@ impl OwnedAtom for StrAtom {
} }
} }
impl Supports<StringGetValMethod> for StrAtom { impl Supports<StringGetValMethod> for StrAtom {
async fn handle<'a>( async fn handle(
&self, &self,
hand: Box<dyn ReqHandle<'a> + '_>, hand: Box<dyn ReqHandle>,
req: StringGetValMethod, req: StringGetValMethod,
) -> io::Result<Receipt<'a>> { ) -> io::Result<Receipt> {
hand.reply(&req, &self.0).await hand.reply(&req, self.0.clone()).await
} }
} }
impl Supports<ToStringMethod> for StrAtom { impl Supports<ToStringMethod> for StrAtom {
async fn handle<'a>( async fn handle(
&self, &self,
hand: Box<dyn ReqHandle<'a> + '_>, hand: Box<dyn ReqHandle>,
req: ToStringMethod, req: ToStringMethod,
) -> io::Result<Receipt<'a>> { ) -> io::Result<Receipt> {
hand.reply(&req, &self.0).await hand.reply(&req, self.0.to_string()).await
} }
} }
impl Supports<ProtocolMethod> for StrAtom { impl Supports<ProtocolMethod> for StrAtom {
async fn handle<'a>( async fn handle(
&self, &self,
hand: Box<dyn ReqHandle<'a> + '_>, hand: Box<dyn ReqHandle>,
req: ProtocolMethod, req: ProtocolMethod,
) -> io::Result<Receipt<'a>> { ) -> io::Result<Receipt> {
match req { match req {
ProtocolMethod::GetTagId(req) => hand.reply(&req, &sym!(std::string::StrAtom).to_api()).await, ProtocolMethod::GetTagId(req) => hand.reply(&req, sym!(std::string::StrAtom).to_api()).await,
ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => { ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => {
let name = Sym::from_api(key).await; let name = Sym::from_api(key).await;
let val = if name == sym!(std::ops::add) { let val = if name == sym!(std::ops::add) {
sym!(std::string::concat) sym!(std::string::concat)
} else { } else {
return hand.reply(req, &None).await; return hand.reply(req, None).await;
}; };
hand.reply(req, &Some(val.to_expr().await.serialize().await)).await hand.reply(req, Some(val.to_expr().await.serialize().await)).await
}, },
} }
} }
@@ -130,31 +130,31 @@ impl TryFromExpr for IntStrAtom {
} }
} }
impl Supports<ToStringMethod> for IntStrAtom { impl Supports<ToStringMethod> for IntStrAtom {
async fn handle<'a>( async fn handle(
&self, &self,
hand: Box<dyn ReqHandle<'a> + '_>, hand: Box<dyn ReqHandle>,
req: ToStringMethod, req: ToStringMethod,
) -> io::Result<Receipt<'a>> { ) -> io::Result<Receipt> {
hand.reply(&req, &self.0.rc()).await hand.reply(&req, self.0.to_string()).await
} }
} }
impl Supports<ProtocolMethod> for IntStrAtom { impl Supports<ProtocolMethod> for IntStrAtom {
async fn handle<'a>( async fn handle(
&self, &self,
hand: Box<dyn ReqHandle<'a> + '_>, hand: Box<dyn ReqHandle>,
req: ProtocolMethod, req: ProtocolMethod,
) -> io::Result<Receipt<'a>> { ) -> io::Result<Receipt> {
match req { match req {
ProtocolMethod::GetTagId(req) => ProtocolMethod::GetTagId(req) =>
hand.reply(&req, &sym!(std::string::IntStrAtom).to_api()).await, hand.reply(&req, sym!(std::string::IntStrAtom).to_api()).await,
ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => { ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => {
let name = Sym::from_api(key).await; let name = Sym::from_api(key).await;
let val = if name == sym!(std::ops::add) { let val = if name == sym!(std::ops::add) {
sym!(std::string::concat) sym!(std::string::concat)
} else { } else {
return hand.reply(req, &None).await; return hand.reply(req, None).await;
}; };
hand.reply(req, &Some(val.to_expr().await.serialize().await)).await hand.reply(req, Some(val.to_expr().await.serialize().await)).await
}, },
} }
} }

View File

@@ -8,7 +8,7 @@ use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::Request; use orchid_api_traits::Request;
use orchid_base::{Numeric, OrcRes, Receipt, ReqHandle, ReqHandleExt}; use orchid_base::{Numeric, OrcRes, Receipt, ReqHandle, ReqHandleExt};
use orchid_extension::gen_expr::{GExpr, call, new_atom, serialize}; use orchid_extension::gen_expr::{GExpr, call, new_atom, serialize};
use orchid_extension::std_reqs::{AsInstant, RunCommand}; use orchid_extension::std_reqs::{AsInstant, StartCommand};
use orchid_extension::tree::{GenMember, fun, prefix}; use orchid_extension::tree::{GenMember, fun, prefix};
use orchid_extension::{ use orchid_extension::{
Atomic, Expr, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, TAtom, ThinAtom, ThinVariant, Atomic, Expr, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, TAtom, ThinAtom, ThinVariant,
@@ -55,12 +55,8 @@ impl OwnedAtom for InstantAtom {
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
} }
impl Supports<AsInstant> for InstantAtom { impl Supports<AsInstant> for InstantAtom {
async fn handle<'a>( async fn handle(&self, hand: Box<dyn ReqHandle>, req: AsInstant) -> std::io::Result<Receipt> {
&self, hand.reply(&req, self.0).await
hand: Box<dyn ReqHandle<'a> + '_>,
req: AsInstant,
) -> std::io::Result<Receipt<'a>> {
hand.reply(&req, &self.0).await
} }
} }
@@ -69,20 +65,16 @@ struct Now(Expr);
impl Atomic for Now { impl Atomic for Now {
type Variant = OwnedVariant; type Variant = OwnedVariant;
type Data = (); type Data = ();
fn reg_methods() -> MethodSetBuilder<Self> { MethodSetBuilder::new().handle::<RunCommand>() } fn reg_methods() -> MethodSetBuilder<Self> { MethodSetBuilder::new().handle::<StartCommand>() }
} }
impl OwnedAtom for Now { impl OwnedAtom for Now {
type Refs = Never; type Refs = Never;
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
} }
impl Supports<RunCommand> for Now { impl Supports<StartCommand> for Now {
async fn handle<'a>( async fn handle(&self, hand: Box<dyn ReqHandle>, req: StartCommand) -> io::Result<Receipt> {
&self,
hand: Box<dyn ReqHandle<'a> + '_>,
req: RunCommand,
) -> io::Result<Receipt<'a>> {
let cont = serialize(call(self.0.clone(), new_atom(InstantAtom(Utc::now()))).await).await; let cont = serialize(call(self.0.clone(), new_atom(InstantAtom(Utc::now()))).await).await;
hand.reply(&req, &Some(cont)).await hand.reply(&req, Some(cont)).await
} }
} }

View File

@@ -48,13 +48,13 @@ impl OwnedAtom for Tuple {
} }
} }
impl Supports<ProtocolMethod> for Tuple { impl Supports<ProtocolMethod> for Tuple {
async fn handle<'a>( async fn handle(
&self, &self,
hand: Box<dyn orchid_base::ReqHandle<'a> + '_>, hand: Box<dyn orchid_base::ReqHandle>,
req: ProtocolMethod, req: ProtocolMethod,
) -> std::io::Result<orchid_base::Receipt<'a>> { ) -> std::io::Result<orchid_base::Receipt> {
match req { match req {
ProtocolMethod::GetTagId(req) => hand.reply(&req, &sym!(std::tuple).to_api()).await, ProtocolMethod::GetTagId(req) => hand.reply(&req, sym!(std::tuple).to_api()).await,
ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => { ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => {
let name = Sym::from_api(key).await; let name = Sym::from_api(key).await;
let val = if name == sym!(std::ops::get) { let val = if name == sym!(std::ops::get) {
@@ -62,9 +62,9 @@ impl Supports<ProtocolMethod> for Tuple {
} else if name == sym!(std::ops::set) { } else if name == sym!(std::ops::set) {
sym!(std::tuple::set) sym!(std::tuple::set)
} else { } else {
return hand.reply(req, &None).await; return hand.reply(req, None).await;
}; };
hand.reply(req, &Some(val.to_expr().await.serialize().await)).await hand.reply(req, Some(val.to_expr().await.serialize().await)).await
}, },
} }
} }

View File

@@ -163,7 +163,7 @@ pub enum Commands {
/// Execute effectful Orchid code /// Execute effectful Orchid code
Exec { Exec {
/// Entrypoint or startup command /// Entrypoint or startup command
#[arg()] #[arg(long)]
main: String, main: String,
}, },
} }
@@ -288,11 +288,7 @@ impl ox::SystemCtor for StdIoSystem {
} }
impl ox::System for StdIoSystem { impl ox::System for StdIoSystem {
type Ctor = Self; type Ctor = Self;
async fn request<'a>( async fn request(&self, _: Box<dyn ReqHandle>, req: ox::ReqForSystem<Self>) -> Receipt {
&self,
_: Box<dyn ReqHandle<'a> + 'a>,
req: ox::ReqForSystem<Self>,
) -> Receipt<'a> {
match req {} match req {}
} }
async fn env(&self) -> Vec<ox::tree::GenMember> { async fn env(&self) -> Vec<ox::tree::GenMember> {
@@ -450,20 +446,21 @@ fn main() -> io::Result<ExitCode> {
let io_ext_init = ext_inline(io_ext_builder, ctx.clone()).await; let io_ext_init = ext_inline(io_ext_builder, ctx.clone()).await;
let io_ext = let io_ext =
Extension::new(io_ext_init, ctx.clone()).await.map_err(|e| e.to_string())?; Extension::new(io_ext_init, ctx.clone()).await.map_err(|e| e.to_string())?;
let io_ctor = (io_ext.system_ctors().find(|ctor| ctor.name() == "orchid::cmd")) let io_ctor = (io_ext.system_ctors().find(|ctor| ctor.name() == "orcx::stdio"))
.expect("Missing command system ctor"); .expect("Missing io system ctor");
let (io_root, io_system) = io_ctor.run(vec![]).await; let (io_root, io_system) = io_ctor.run(vec![]).await;
root = root.merge(&io_root).await.expect("Failed to merge stdio root into tree"); root = root.merge(&io_root).await.expect("Failed to merge stdio root into tree");
systems.push(io_system); systems.push(io_system);
extensions.push(io_ext); extensions.push(io_ext);
let mut crun = CmdRunner::new(&mut root, ctx.clone(), []).await;
systems.push(crun.sys().clone());
load_proj_if_set(&mut root, &args, ctx).await?; load_proj_if_set(&mut root, &args, ctx).await?;
add_const_at(&mut root, ctx, &systems[..], path.clone(), is(main).await).await?; add_const_at(&mut root, ctx, &systems[..], path.clone(), is(main).await).await?;
let expr = ExprKind::Const(path.clone()).at(SrcRange::zw(path.clone(), 0).pos()); crun.push(ExprKind::Const(path.clone()).at(SrcRange::zw(path.clone(), 0).pos()));
let mut crun = CmdRunner::new(root, ctx.clone(), [expr]).await;
if !args.no_gas { if !args.no_gas {
crun.set_gas(args.gas); crun.set_gas(args.gas);
} }
match crun.execute().await { match crun.execute(&root).await {
CmdEvent::Exit | CmdEvent::Settled => (), CmdEvent::Exit | CmdEvent::Settled => (),
CmdEvent::Err(e) => println!("error: {e}"), CmdEvent::Err(e) => println!("error: {e}"),
CmdEvent::Gas => println!("Exceeded gas limit of {}", args.gas), CmdEvent::Gas => println!("Exceeded gas limit of {}", args.gas),