Pending a correct test of request cancellation
This commit is contained in:
@@ -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
|
||||||
)
|
)
|
||||||
87
orchid-async-utils/src/cancel_cleanup.rs
Normal file
87
orchid-async-utils/src/cancel_cleanup.rs
Normal 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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::*;
|
||||||
@@ -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;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = ¬if;
|
let notif = ¬if;
|
||||||
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();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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::+),
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 })
|
||||||
|
}),
|
||||||
]),
|
]),
|
||||||
)])
|
)])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user