Pending a correct test of request cancellation

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

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ use never::Never;
use orchid_base::{Receipt, ReqHandle, ReqHandleExt};
use 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};
pub trait AsyncFnDyn {
@@ -21,18 +21,14 @@ pub struct CmdAtom(Rc<dyn AsyncFnDyn>);
impl Atomic for CmdAtom {
type Data = ();
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 {
async fn handle<'a>(
&self,
hand: Box<dyn ReqHandle<'a> + '_>,
req: RunCommand,
) -> std::io::Result<Receipt<'a>> {
impl Supports<StartCommand> for CmdAtom {
async fn handle(&self, hand: Box<dyn ReqHandle>, req: StartCommand) -> std::io::Result<Receipt> {
let reply = self.0.call().await;
match reply {
None => hand.reply(&req, &None).await,
Some(next) => hand.reply(&req, &Some(serialize(next).await)).await,
None => hand.reply(&req, None).await,
Some(next) => hand.reply(&req, Some(serialize(next).await)).await,
}
}
}

View File

@@ -8,12 +8,12 @@ use std::pin::Pin;
use std::rc::Rc;
use std::time::Duration;
use futures::future::{LocalBoxFuture, join_all};
use futures::{AsyncWriteExt, StreamExt, stream};
use futures::future::{LocalBoxFuture, join_all, join3};
use futures::{AsyncReadExt, AsyncWriteExt, StreamExt, stream};
use hashbrown::HashMap;
use itertools::Itertools;
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::{
Client, ClientExt, CommCtx, Comment, MsgReader, MsgReaderExt, ReqHandleExt, ReqReaderExt,
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 task_local::task_local;
use unsync_pipe::pipe;
use crate::gen_expr::serialize;
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]
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()
}
@@ -106,7 +107,7 @@ impl<F: AsyncFnOnce(LocalBoxFuture<'_, ()>) + 'static> ContextModifier for F {
pub(crate) trait DynTaskHandle: 'static {
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! {
@@ -124,7 +125,7 @@ impl<T: 'static> TaskHandle<T> {
/// Stop working on the task and return the nested future. The distinction
/// between this and waiting until the task is complete without reparenting it
/// 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
@@ -138,7 +139,9 @@ pub fn spawn<F: Future<Output: 'static> + 'static>(delay: Duration, f: F) -> Tas
impl DynTaskHandle for Handle<Box<dyn Any>> {
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
@@ -213,15 +216,15 @@ impl ExtensionBuilder {
match req {
api::HostExtReq::SystemDrop(sys_drop) => {
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)) =>
with_sys_record(sys_id, async {
take_atom(atom).await.dyn_free().await;
handle.reply(&atom_drop, &()).await
handle.reply(&atom_drop, ()).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::SysReq(api::SysReq::NewSystem(new_sys)) => {
let (ctor_idx, _) = (decls.iter().enumerate().find(|(_, s)| s.id == new_sys.system))
@@ -257,7 +260,7 @@ impl ExtensionBuilder {
.await;
let response =
api::NewSystemResponse { lex_filter, const_root, line_types, prelude };
handle.reply(&new_sys, &response).await
handle.reply(&new_sys, response).await
})
.await
},
@@ -266,17 +269,23 @@ impl ExtensionBuilder {
let (path, tree) = get_lazy(tree_id).await;
let mut tia_ctx =
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,
api::HostExtReq::SysReq(api::SysReq::SysFwded(fwd)) => {
let fwd_tok = Witness::of(&fwd);
let api::SysFwded(sys_id, payload) = fwd;
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 req = TrivialReqCycle { req: &payload, rep: &mut reply };
let _ = dyn_cted().inst().dyn_request(Box::new(req)).await;
handle.reply(fwd_tok, &reply).await
let (..) = join3(
async { req_in.write_all(&payload).await.expect("Ingress failed") },
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
},
@@ -298,7 +307,7 @@ impl ExtensionBuilder {
Err(e) => {
let eopt = e.keep_only(|e| *e != ekey_cascade).map(|e| Err(e.to_api()));
expr_store.dispose().await;
return handle.reply(&lex, &eopt).await;
return handle.reply(&lex, eopt).await;
},
Ok((s, expr)) => {
let expr = join_all(
@@ -308,13 +317,13 @@ impl ExtensionBuilder {
.await;
let pos = (text.len() - s.len()) as u32;
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;
expr_store.dispose().await;
handle.reply(&lex, &None).await
handle.reply(&lex, None).await
})
.await,
api::HostExtReq::ParseLine(pline) => {
@@ -338,14 +347,14 @@ impl ExtensionBuilder {
};
mem::drop(line);
expr_store.dispose().await;
handle.reply(req, &o_line).await
handle.reply(req, o_line).await
})
.await
},
api::HostExtReq::FetchParsedConst(ref fpc @ api::FetchParsedConst(sys, id)) =>
with_sys_record(sys, async {
let cnst = get_const(id).await;
handle.reply(fpc, &serialize(cnst).await).await
handle.reply(fpc, serialize(cnst).await).await
})
.await,
api::HostExtReq::AtomReq(atom_req) => {
@@ -357,24 +366,30 @@ impl ExtensionBuilder {
api::AtomReq::SerializeAtom(ser) => {
let mut buf = enc_vec(&id);
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) => {
let refs =
join_all(refs.into_iter().map(async |ex| ex.into_api(&mut ()).await))
.await;
handle.reply(ser, &Some((buf, refs))).await
handle.reply(ser, Some((buf, refs))).await
},
}
},
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) => {
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 key = Sym::from_api(*key).await;
let req = TrivialReqCycle { req: payload, rep: &mut reply };
let some = nfo.handle_req_ref(actx, key, Box::new(req)).await;
handle.reply(fwded, &some.then_some(reply)).await
let (.., some) = join3(
async { req_in.write_all(payload).await.expect("Ingress failed") },
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)) => {
let expr_store = BorrowedExprStore::new();
@@ -383,7 +398,7 @@ impl ExtensionBuilder {
let api_expr = serialize(ret).await;
mem::drop(expr_handle);
expr_store.dispose().await;
handle.reply(call, &api_expr).await
handle.reply(call, api_expr).await
},
api::AtomReq::FinalCall(call @ api::FinalCall(_, arg)) => {
let expr_store = BorrowedExprStore::new();
@@ -392,7 +407,7 @@ impl ExtensionBuilder {
let api_expr = serialize(ret).await;
mem::drop(expr_handle);
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 nfo = (dyn_cted().inst().card().ops_by_atid(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
},

View File

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

View File

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

View File

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

View File

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