Unit tests pass

Fixed a nasty deadlock in reqnot
This commit is contained in:
2026-01-19 00:56:03 +01:00
parent 6a3c1d5917
commit 48942b3b2c
15 changed files with 223 additions and 85 deletions

View File

@@ -5,7 +5,7 @@ orcxdb = "xtask orcxdb"
[env] [env]
CARGO_WORKSPACE_DIR = { value = "", relative = true } CARGO_WORKSPACE_DIR = { value = "", relative = true }
ORCHID_EXTENSIONS = "target/debug/orchid-std-dbg" ORCHID_EXTENSIONS = "target/debug/orchid_std"
ORCHID_DEFAULT_SYSTEMS = "orchid::std;orchid::macros" ORCHID_DEFAULT_SYSTEMS = "orchid::std;orchid::macros"
ORCHID_LOG_BUFFERS = "true" ORCHID_LOG_BUFFERS = "true"
RUST_BACKTRACE = "1" RUST_BACKTRACE = "1"

1
Cargo.lock generated
View File

@@ -891,7 +891,6 @@ dependencies = [
"rust-embed", "rust-embed",
"substack", "substack",
"task-local", "task-local",
"test_executors",
"trait-set", "trait-set",
"unsync-pipe", "unsync-pipe",
] ]

View File

@@ -30,4 +30,3 @@ task-local = "0.1.0"
[dev-dependencies] [dev-dependencies]
futures = "0.3.31" futures = "0.3.31"
test_executors = "0.4.1"

View File

@@ -3,13 +3,13 @@ use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use orchid_api::binary::{FutureBin, FutureContextBin, OwnedWakerBin, UnitPoll}; use crate::api;
type WideBox = Box<dyn Future<Output = ()>>; type WideBox = Box<dyn Future<Output = ()>>;
static OWNED_VTABLE: RawWakerVTable = RawWakerVTable::new( static OWNED_VTABLE: RawWakerVTable = RawWakerVTable::new(
|data| { |data| {
let data = unsafe { Rc::<OwnedWakerBin>::from_raw(data as *const _) }; let data = unsafe { Rc::<api::binary::OwnedWakerBin>::from_raw(data as *const _) };
let val = RawWaker::new(Rc::into_raw(data.clone()) as *const (), &OWNED_VTABLE); let val = RawWaker::new(Rc::into_raw(data.clone()) as *const (), &OWNED_VTABLE);
// Clone must create a duplicate of the Rc, so it has to be un-leaked, cloned, // Clone must create a duplicate of the Rc, so it has to be un-leaked, cloned,
// then leaked again. // then leaked again.
@@ -19,19 +19,19 @@ static OWNED_VTABLE: RawWakerVTable = RawWakerVTable::new(
|data| { |data| {
// Wake must awaken the task and then clean up the state, so the waker must be // Wake must awaken the task and then clean up the state, so the waker must be
// un-leaked // un-leaked
let data = unsafe { Rc::<OwnedWakerBin>::from_raw(data as *const _) }; let data = unsafe { Rc::<api::binary::OwnedWakerBin>::from_raw(data as *const _) };
(data.wake)(data.data); (data.wake)(data.data);
mem::drop(data); mem::drop(data);
}, },
|data| { |data| {
// Wake-by-ref must awaken the task while preserving the future, so the Rc is // Wake-by-ref must awaken the task while preserving the future, so the Rc is
// untouched // untouched
let data = unsafe { (data as *const OwnedWakerBin).as_ref() }.unwrap(); let data = unsafe { (data as *const api::binary::OwnedWakerBin).as_ref() }.unwrap();
(data.wake_ref)(data.data); (data.wake_ref)(data.data);
}, },
|data| { |data| {
// Drop must clean up the state, so the waker must be un-leaked // Drop must clean up the state, so the waker must be un-leaked
let data = unsafe { Rc::<OwnedWakerBin>::from_raw(data as *const _) }; let data = unsafe { Rc::<api::binary::OwnedWakerBin>::from_raw(data as *const _) };
(data.drop)(data.data); (data.drop)(data.data);
mem::drop(data); mem::drop(data);
}, },
@@ -39,12 +39,12 @@ static OWNED_VTABLE: RawWakerVTable = RawWakerVTable::new(
struct BorrowedWakerData<'a> { struct BorrowedWakerData<'a> {
go_around: &'a mut bool, go_around: &'a mut bool,
cx: FutureContextBin, cx: api::binary::FutureContextBin,
} }
static BORROWED_VTABLE: RawWakerVTable = RawWakerVTable::new( static BORROWED_VTABLE: RawWakerVTable = RawWakerVTable::new(
|data| { |data| {
let data = unsafe { (data as *mut BorrowedWakerData).as_mut() }.unwrap(); let data = unsafe { (data as *mut BorrowedWakerData).as_mut() }.unwrap();
let owned_data = Rc::<OwnedWakerBin>::new((data.cx.waker)(data.cx.data)); let owned_data = Rc::<api::binary::OwnedWakerBin>::new((data.cx.waker)(data.cx.data));
RawWaker::new(Rc::into_raw(owned_data) as *const (), &OWNED_VTABLE) RawWaker::new(Rc::into_raw(owned_data) as *const (), &OWNED_VTABLE)
}, },
|data| *unsafe { (data as *mut BorrowedWakerData).as_mut() }.unwrap().go_around = true, |data| *unsafe { (data as *mut BorrowedWakerData).as_mut() }.unwrap().go_around = true,
@@ -54,13 +54,13 @@ static BORROWED_VTABLE: RawWakerVTable = RawWakerVTable::new(
/// Convert a future to a binary-compatible format that can be sent across /// Convert a future to a binary-compatible format that can be sent across
/// dynamic library boundaries /// dynamic library boundaries
pub fn future_to_vt<Fut: Future<Output = ()> + 'static>(fut: Fut) -> FutureBin { pub fn future_to_vt<Fut: Future<Output = ()> + 'static>(fut: Fut) -> api::binary::FutureBin {
let wide_box = Box::new(fut) as WideBox; let wide_box = Box::new(fut) as WideBox;
let data = Box::into_raw(Box::new(wide_box)); let data = Box::into_raw(Box::new(wide_box));
extern "C" fn drop(raw: *const ()) { extern "C" fn drop(raw: *const ()) {
mem::drop(unsafe { Box::<WideBox>::from_raw(raw as *mut _) }) mem::drop(unsafe { Box::<WideBox>::from_raw(raw as *mut _) })
} }
extern "C" fn poll(raw: *const (), cx: FutureContextBin) -> UnitPoll { extern "C" fn poll(raw: *const (), cx: api::binary::FutureContextBin) -> api::binary::UnitPoll {
let mut this = unsafe { Pin::new_unchecked(&mut **(raw as *mut WideBox).as_mut().unwrap()) }; let mut this = unsafe { Pin::new_unchecked(&mut **(raw as *mut WideBox).as_mut().unwrap()) };
loop { loop {
let mut go_around = false; let mut go_around = false;
@@ -73,27 +73,27 @@ pub fn future_to_vt<Fut: Future<Output = ()> + 'static>(fut: Fut) -> FutureBin {
let mut ctx = Context::from_waker(&borrowed_waker); let mut ctx = Context::from_waker(&borrowed_waker);
let result = this.as_mut().poll(&mut ctx); let result = this.as_mut().poll(&mut ctx);
if matches!(result, Poll::Ready(())) { if matches!(result, Poll::Ready(())) {
break UnitPoll::Ready; break api::binary::UnitPoll::Ready;
} }
if !go_around { if !go_around {
break UnitPoll::Pending; break api::binary::UnitPoll::Pending;
} }
} }
} }
FutureBin { data: data as *const _, drop, poll } api::binary::FutureBin { data: data as *const _, drop, poll }
} }
struct VirtualFuture { struct VirtualFuture {
vt: FutureBin, vt: api::binary::FutureBin,
} }
impl Unpin for VirtualFuture {} impl Unpin for VirtualFuture {}
impl Future for VirtualFuture { impl Future for VirtualFuture {
type Output = (); type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
extern "C" fn waker(raw: *const ()) -> OwnedWakerBin { extern "C" fn waker(raw: *const ()) -> api::binary::OwnedWakerBin {
let waker = unsafe { (raw as *mut Context).as_mut() }.unwrap().waker().clone(); let waker = unsafe { (raw as *mut Context).as_mut() }.unwrap().waker().clone();
let data = Box::into_raw(Box::<Waker>::new(waker)) as *const (); let data = Box::into_raw(Box::<Waker>::new(waker)) as *const ();
return OwnedWakerBin { data, drop, wake, wake_ref }; return api::binary::OwnedWakerBin { data, drop, wake, wake_ref };
extern "C" fn drop(raw: *const ()) { extern "C" fn drop(raw: *const ()) {
mem::drop(unsafe { Box::<Waker>::from_raw(raw as *mut Waker) }) mem::drop(unsafe { Box::<Waker>::from_raw(raw as *mut Waker) })
} }
@@ -104,11 +104,11 @@ impl Future for VirtualFuture {
unsafe { (raw as *mut Waker).as_mut() }.unwrap().wake_by_ref(); unsafe { (raw as *mut Waker).as_mut() }.unwrap().wake_by_ref();
} }
} }
let cx = FutureContextBin { data: cx as *mut Context as *const (), waker }; let cx = api::binary::FutureContextBin { data: cx as *mut Context as *const (), waker };
let result = (self.vt.poll)(self.vt.data, cx); let result = (self.vt.poll)(self.vt.data, cx);
match result { match result {
UnitPoll::Pending => Poll::Pending, api::binary::UnitPoll::Pending => Poll::Pending,
UnitPoll::Ready => Poll::Ready(()), api::binary::UnitPoll::Ready => Poll::Ready(()),
} }
} }
} }
@@ -118,4 +118,4 @@ impl Drop for VirtualFuture {
/// Receive a future sent across dynamic library boundaries and convert it into /// Receive a future sent across dynamic library boundaries and convert it into
/// an owned object /// an owned object
pub fn vt_to_future(vt: FutureBin) -> impl Future<Output = ()> { VirtualFuture { vt } } pub fn vt_to_future(vt: api::binary::FutureBin) -> impl Future<Output = ()> { VirtualFuture { vt } }

View File

@@ -0,0 +1,157 @@
use std::cell::RefCell;
use std::fmt::Display;
use std::pin::pin;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::task::{Context, Poll, Wake, Waker};
use futures::Stream;
use itertools::Itertools;
use task_local::task_local;
struct OnPollWaker<F: Fn() + 'static>(Waker, F);
impl<F: Fn() + 'static> Wake for OnPollWaker<F> {
fn wake(self: Arc<Self>) {
(self.1)();
self.0.wake_by_ref()
}
}
/// Attach a callback to the [Future] protocol for testing and debugging. Note
/// that this function is safe and simple in order to facilitate debugging
/// without adding more points of failure, but it's not fast; it performs a heap
/// allocation on each poll of the returned future.
pub async fn on_wake<F: Future>(
f: F,
wake: impl Fn() + Clone + Send + Sync + 'static,
) -> F::Output {
let mut f = pin!(f);
futures::future::poll_fn(|cx| {
let waker = Arc::new(OnPollWaker(cx.waker().clone(), wake.clone())).into();
f.as_mut().poll(&mut Context::from_waker(&waker))
})
.await
}
/// Respond to [Future::poll] with a callback. For maximum flexibility and state
/// control, your callback receives the actual poll job as a callback function.
/// Failure to call this function will result in an immediate panic.
pub async fn wrap_poll<Fut: Future>(
f: Fut,
mut cb: impl FnMut(Box<dyn FnOnce() -> bool + '_>),
) -> Fut::Output {
let mut f = pin!(f);
futures::future::poll_fn(|cx| {
let poll = RefCell::new(None);
cb(Box::new(|| {
let poll1 = f.as_mut().poll(cx);
let ret = poll1.is_ready();
*poll.borrow_mut() = Some(poll1);
ret
}));
poll.into_inner().expect("Callback to on_poll failed to call its argument")
})
.await
}
pub fn wrap_poll_next<'a, S: Stream + 'a>(
s: S,
mut cb: impl FnMut(Box<dyn FnOnce() -> bool + '_>) + 'a,
) -> impl Stream<Item = S::Item> + 'a {
let mut s = Box::pin(s);
futures::stream::poll_fn(move |cx| {
let poll = RefCell::new(None);
cb(Box::new(|| {
let poll1 = s.as_mut().poll_next(cx);
let ret = poll1.is_ready();
*poll.borrow_mut() = Some(poll1);
ret
}));
poll.into_inner().expect("Callback to on_poll failed to call its argument")
})
}
pub fn on_stream_wake<'a, S: Stream + 'a>(
s: S,
wake: impl Fn() + Clone + Send + Sync + 'static,
) -> impl Stream<Item = S::Item> {
let mut s = Box::pin(s);
futures::stream::poll_fn(move |cx| {
let waker = Arc::new(OnPollWaker(cx.waker().clone(), wake.clone())).into();
s.as_mut().poll_next(&mut Context::from_waker(&waker))
})
}
task_local! {
static LABEL_STATE: Vec<Rc<String>>
}
pub async fn with_label<Fut: Future>(label: &str, f: Fut) -> Fut::Output {
let mut new_lbl = LABEL_STATE.try_with(|lbl| lbl.clone()).unwrap_or_default();
new_lbl.push(Rc::new(label.to_string()));
LABEL_STATE.scope(new_lbl, f).await
}
pub fn label() -> impl Display + Clone + Send + Sync + 'static {
LABEL_STATE.try_with(|lbl| lbl.iter().join("/")).unwrap_or("".to_string())
}
pub struct Label;
impl Display for Label {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", label()) }
}
pub async fn eprint_events<Fut: Future>(note: &str, f: Fut) -> Fut::Output {
let label = label();
let note1 = note.to_string();
on_wake(
wrap_poll(f, |cb| {
eprintln!("{Label} polling {note}");
eprintln!("{Label} polled {note} (ready? {})", cb())
}),
move || eprintln!("{label} woke {note1}"),
)
.await
}
pub fn eprint_stream_events<'a, S: Stream + 'a>(
note: &'a str,
s: S,
) -> impl Stream<Item = S::Item> + 'a {
let label = label();
let note1 = note.to_string();
on_stream_wake(
wrap_poll_next(s, move |cb| {
eprintln!("{Label} polling {note}");
eprintln!("{Label} polled {note} (ready? {})", cb())
}),
move || eprintln!("{label} woke {note1}"),
)
}
struct SpinWaker(AtomicBool);
impl Wake for SpinWaker {
fn wake(self: Arc<Self>) { self.0.store(true, Ordering::Relaxed); }
}
/// A dumb executor that keeps synchronously re-running the future as long as it
/// keeps synchronously waking itself.
///
/// # Panics
///
/// If the future doesn't wake itself and doesn't settle. This is useful for
/// deterministic tests that don't contain side effects or threading.
pub fn spin_on<Fut: Future>(f: Fut) -> Fut::Output {
let repeat = Arc::new(SpinWaker(AtomicBool::new(false)));
let mut f = pin!(f);
let waker = repeat.clone().into();
let mut cx = Context::from_waker(&waker);
loop {
match f.as_mut().poll(&mut cx) {
Poll::Ready(t) => break t,
Poll::Pending if repeat.0.swap(false, Ordering::Relaxed) => (),
Poll::Pending => panic!("The future did not exit and did not call its waker."),
}
}
}

View File

@@ -10,6 +10,7 @@ pub mod combine;
pub mod error; pub mod error;
pub mod event; pub mod event;
pub mod format; pub mod format;
pub mod future_debug;
pub mod id_store; pub mod id_store;
pub mod interner; pub mod interner;
pub mod iter_utils; pub mod iter_utils;

View File

@@ -23,12 +23,12 @@ 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(None) => break,
Poll::Pending => { Poll::Pending => {
any_pending = true; any_pending = true;
break; break;
}, },
Poll::Ready(None) => break,
Poll::Ready(Some(fut)) => this.pending.push_back(fut),
} }
} }
let count = this.pending.len(); let count = this.pending.len();

View File

@@ -71,4 +71,7 @@ pub mod test {
Self(Rc::new(move |s| clone!(f; Box::pin(async move { f(s).await })))) Self(Rc::new(move |s| clone!(f; Box::pin(async move { f(s).await }))))
} }
} }
impl Default for TestLogger {
fn default() -> Self { TestLogger::new(async |s| eprint!("{s}")) }
}
} }

View File

@@ -299,12 +299,10 @@ impl<'a> MsgWriter<'a> for IoNotifWriter {
pub struct CommCtx { pub struct CommCtx {
exit: Sender<()>, exit: Sender<()>,
o: Rc<Mutex<Pin<Box<dyn AsyncWrite>>>>,
} }
impl CommCtx { impl CommCtx {
pub async fn exit(self) -> io::Result<()> { pub async fn exit(self) -> io::Result<()> {
self.o.lock().await.as_mut().close().await?;
self.exit.clone().send(()).await.expect("quit channel dropped"); self.exit.clone().send(()).await.expect("quit channel dropped");
Ok(()) Ok(())
} }
@@ -325,7 +323,7 @@ pub fn io_comm(
let o = Rc::new(Mutex::new(o)); let o = Rc::new(Mutex::new(o));
let (onsub, client) = IoClient::new(o.clone()); let (onsub, client) = IoClient::new(o.clone());
let (exit, onexit) = channel(1); let (exit, onexit) = channel(1);
(client, CommCtx { exit, o: o.clone() }, IoCommServer { o, i, onsub, onexit }) (client, CommCtx { exit }, IoCommServer { o, i, onsub, onexit })
} }
pub struct IoCommServer { pub struct IoCommServer {
o: Rc<Mutex<Pin<Box<dyn AsyncWrite>>>>, o: Rc<Mutex<Pin<Box<dyn AsyncWrite>>>>,
@@ -345,7 +343,6 @@ impl IoCommServer {
Sub(ReplySub), Sub(ReplySub),
Exit, Exit,
} }
let exiting = RefCell::new(false);
let input_stream = try_stream(async |mut h| { let input_stream = try_stream(async |mut h| {
loop { loop {
let mut g = Bound::async_new(i.clone(), async |i| i.lock().await).await; let mut g = Bound::async_new(i.clone(), async |i| i.lock().await).await;
@@ -361,27 +358,21 @@ impl IoCommServer {
} }
}); });
let (mut add_pending_req, fork_future) = LocalSet::new(); let (mut add_pending_req, fork_future) = LocalSet::new();
let mut fork_stream = pin!(fork_future.fuse().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: {
let mut shared = pin!(stream_select!( let mut shared = stream_select! {
pin!(input_stream) as Pin<&mut dyn Stream<Item = io::Result<Event>>>, pin!(input_stream) as Pin<&mut dyn Stream<Item = io::Result<Event>>>,
onsub.map(|sub| Ok(Event::Sub(sub))), onsub.map(|sub| Ok(Event::Sub(sub))),
fork_stream.as_mut().map(|res| { fork_stream.as_mut().map(|res| {
res.map(|()| panic!("this substream cannot exit while the loop is running")) res.map(|()| panic!("this substream cannot exit while the loop is running") as Event)
}), }),
onexit.map(|()| Ok(Event::Exit)), onexit.map(|()| Ok(Event::Exit)),
)); };
while let Some(next) = shared.next().await { while let Some(next) = shared.next().await {
match next { match next {
Err(e) => break 'body Err(e), Err(e) => break 'body Err(e),
Ok(Event::Exit) => { Ok(Event::Exit) => break,
*exiting.borrow_mut() = true;
let mut out = o.lock().await;
out.as_mut().flush().await?;
out.as_mut().close().await?;
break;
},
Ok(Event::Sub(ReplySub { id, ack, cb })) => { Ok(Event::Sub(ReplySub { id, ack, cb })) => {
pending_replies.insert(id, cb); pending_replies.insert(id, cb);
ack.send(()).unwrap(); ack.send(()).unwrap();
@@ -415,6 +406,9 @@ impl IoCommServer {
while let Some(next) = fork_stream.next().await { while let Some(next) = fork_stream.next().await {
next? next?
} }
let mut out = o.lock().await;
out.as_mut().flush().await?;
out.as_mut().close().await?;
Ok(()) Ok(())
} }
} }
@@ -427,11 +421,9 @@ mod test {
use futures::{SinkExt, StreamExt, join}; use futures::{SinkExt, StreamExt, join};
use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::Request; use orchid_api_traits::Request;
use test_executors::spin_on;
use unsync_pipe::pipe; use unsync_pipe::pipe;
use crate::logging::test::TestLogger; use crate::future_debug::spin_on;
use crate::logging::with_logger;
use crate::reqnot::{ClientExt, MsgReaderExt, ReqReaderExt, io_comm}; use crate::reqnot::{ClientExt, MsgReaderExt, ReqReaderExt, io_comm};
#[derive(Clone, Debug, PartialEq, Coding, Hierarchy)] #[derive(Clone, Debug, PartialEq, Coding, Hierarchy)]
@@ -440,8 +432,7 @@ mod test {
#[test] #[test]
fn notification() { fn notification() {
let logger = TestLogger::new(async |s| eprint!("{s}")); spin_on(async {
spin_on(with_logger(logger, 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);
@@ -468,7 +459,7 @@ mod test {
recv_ctx.exit().await.unwrap(); recv_ctx.exit().await.unwrap();
} }
); );
})) })
} }
#[derive(Clone, Debug, Coding, Hierarchy)] #[derive(Clone, Debug, Coding, Hierarchy)]
@@ -480,8 +471,7 @@ mod test {
#[test] #[test]
fn request() { fn request() {
let logger = TestLogger::new(async |s| eprint!("{s}")); spin_on(async {
spin_on(with_logger(logger, 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));
@@ -515,13 +505,12 @@ mod test {
client_ctx.exit().await.unwrap(); client_ctx.exit().await.unwrap();
} }
); );
})) })
} }
#[test] #[test]
fn exit() { fn exit() {
let logger = TestLogger::new(async |s| eprint!("{s}")); spin_on(async {
spin_on(with_logger(logger, 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) =
@@ -565,6 +554,6 @@ mod test {
onexit.await.unwrap(); onexit.await.unwrap();
} }
) )
})); });
} }
} }

View File

@@ -34,6 +34,10 @@ pub fn orchid_extension_main_body(cx: ExtCx, builder: ExtensionBuilder) {
/// # Usage /// # Usage
/// ///
/// ``` /// ```
/// #[macro_use]
/// use orchid_extension::dylib_main;
/// use orchid_extension::entrypoint::ExtensionBuilder;
///
/// dylib_main! { /// dylib_main! {
/// ExtensionBuilder::new("orchid-std::main") /// ExtensionBuilder::new("orchid-std::main")
/// } /// }

View File

@@ -9,7 +9,6 @@ use futures::future::{LocalBoxFuture, join_all};
use futures::{AsyncRead, AsyncWrite, AsyncWriteExt, StreamExt, stream}; use futures::{AsyncRead, AsyncWrite, AsyncWriteExt, StreamExt, stream};
use hashbrown::HashMap; use hashbrown::HashMap;
use itertools::Itertools; use itertools::Itertools;
use orchid_api::{ExtHostNotif, ExtHostReq};
use orchid_api_traits::{Decode, Encode, Request, UnderRoot, enc_vec}; use orchid_api_traits::{Decode, Encode, Request, UnderRoot, enc_vec};
use orchid_base::char_filter::{char_filter_match, char_filter_union, mk_char_filter}; use orchid_base::char_filter::{char_filter_match, char_filter_union, mk_char_filter};
use orchid_base::error::try_with_reporter; use orchid_base::error::try_with_reporter;
@@ -63,7 +62,7 @@ task_local! {
} }
/// Send a request through the global client's [ClientExt::request] /// Send a request through the global client's [ClientExt::request]
pub async fn request<T: Request + UnderRoot<Root = ExtHostReq>>(t: T) -> T::Response { pub async fn request<T: Request + UnderRoot<Root = api::ExtHostReq>>(t: T) -> T::Response {
let response = get_client().request(t).await.unwrap(); let response = get_client().request(t).await.unwrap();
if MUTE_REPLY.try_with(|b| *b).is_err() { if MUTE_REPLY.try_with(|b| *b).is_err() {
writeln!(log("msg"), "Got response {response:?}").await; writeln!(log("msg"), "Got response {response:?}").await;
@@ -72,7 +71,7 @@ pub async fn request<T: Request + UnderRoot<Root = ExtHostReq>>(t: T) -> T::Resp
} }
/// 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 = ExtHostNotif>>(t: T) { pub async fn notify<T: UnderRoot<Root = api::ExtHostNotif>>(t: T) {
get_client().notify(t).await.unwrap() get_client().notify(t).await.unwrap()
} }

View File

@@ -4,7 +4,6 @@ use async_fn_stream::stream;
use futures::future::join_all; use futures::future::join_all;
use futures::{Stream, StreamExt, stream}; use futures::{Stream, StreamExt, stream};
use never::Never; use never::Never;
use orchid_api::ExprTicket;
use orchid_api_derive::Coding; use orchid_api_derive::Coding;
use orchid_base::error::{OrcRes, mk_errv}; use orchid_base::error::{OrcRes, mk_errv};
use orchid_base::format::fmt; use orchid_base::format::fmt;
@@ -28,7 +27,7 @@ use crate::{HomoTpl, MacTok, MacTree, OrcOpt, Tpl, UntypedTuple, api};
#[derive(Clone, Coding)] #[derive(Clone, Coding)]
pub struct MatcherData { pub struct MatcherData {
keys: Vec<api::TStrv>, keys: Vec<api::TStrv>,
matcher: ExprTicket, matcher: api::ExprTicket,
} }
impl MatcherData { impl MatcherData {
async fn matcher(&self) -> Expr { Expr::from_handle(ExprHandle::from_ticket(self.matcher).await) } async fn matcher(&self) -> Expr { Expr::from_handle(ExprHandle::from_ticket(self.matcher).await) }

View File

@@ -1,6 +1,5 @@
use std::borrow::Cow; use std::borrow::Cow;
use orchid_api::TStrv;
use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::Request; use orchid_api_traits::Request;
use orchid_base::error::mk_errv; use orchid_base::error::mk_errv;
@@ -37,7 +36,7 @@ impl Supports<ToStringMethod> for SymAtom {
#[derive(Clone, Debug, Coding, Hierarchy)] #[derive(Clone, Debug, Coding, Hierarchy)]
#[extends(StdReq)] #[extends(StdReq)]
pub struct CreateSymAtom(pub TStrv); pub struct CreateSymAtom(pub api::TStrv);
impl Request for CreateSymAtom { impl Request for CreateSymAtom {
type Response = api::ExprTicket; type Response = api::ExprTicket;
} }

View File

@@ -6,7 +6,6 @@ use std::rc::Rc;
use futures::AsyncWrite; use futures::AsyncWrite;
use futures::future::join_all; use futures::future::join_all;
use never::Never; use never::Never;
use orchid_api::ExprTicket;
use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::Request; use orchid_api_traits::Request;
use orchid_base::error::{OrcRes, mk_errv}; use orchid_base::error::{OrcRes, mk_errv};
@@ -27,7 +26,7 @@ use crate::{Int, StdSystem, api};
pub struct Tuple(pub(super) Rc<Vec<Expr>>); pub struct Tuple(pub(super) Rc<Vec<Expr>>);
impl Atomic for Tuple { impl Atomic for Tuple {
type Data = Vec<ExprTicket>; type Data = Vec<api::ExprTicket>;
type Variant = OwnedVariant; type Variant = OwnedVariant;
} }

View File

@@ -27,7 +27,6 @@ pub fn pipe(size: usize) -> (Writer, Reader) {
size, size,
mut read_waker, mut read_waker,
mut write_waker, mut write_waker,
mut flush_waker,
reader_dropped, reader_dropped,
writer_dropped, writer_dropped,
// irrelevant if correctly dropped // irrelevant if correctly dropped
@@ -42,7 +41,6 @@ pub fn pipe(size: usize) -> (Writer, Reader) {
} }
read_waker.drop(); read_waker.drop();
write_waker.drop(); write_waker.drop();
flush_waker.drop();
unsafe { dealloc(start, pipe_layout(size)) } unsafe { dealloc(start, pipe_layout(size)) }
} }
let state = Box::into_raw(Box::new(AsyncRingbuffer { let state = Box::into_raw(Box::new(AsyncRingbuffer {
@@ -53,7 +51,6 @@ pub fn pipe(size: usize) -> (Writer, Reader) {
write_idx: 0, write_idx: 0,
read_waker: Trigger::empty(), read_waker: Trigger::empty(),
write_waker: Trigger::empty(), write_waker: Trigger::empty(),
flush_waker: Trigger::empty(),
reader_dropped: false, reader_dropped: false,
writer_dropped: false, writer_dropped: false,
drop, drop,
@@ -110,21 +107,22 @@ struct AsyncRingbuffer {
write_idx: usize, write_idx: usize,
read_waker: Trigger, read_waker: Trigger,
write_waker: Trigger, write_waker: Trigger,
flush_waker: Trigger,
reader_dropped: bool, reader_dropped: bool,
writer_dropped: bool, writer_dropped: bool,
drop: extern "C" fn(*const ()), drop: extern "C" fn(*const ()),
} }
impl AsyncRingbuffer { impl AsyncRingbuffer {
fn wake_reader(&mut self) { self.read_waker.invoke() }
fn wake_writer(&mut self) { self.write_waker.invoke() }
fn drop_writer(&mut self) { fn drop_writer(&mut self) {
self.read_waker.invoke(); self.wake_reader();
self.writer_dropped = true; self.writer_dropped = true;
if self.reader_dropped { if self.reader_dropped {
(self.drop)(self.state) (self.drop)(self.state)
} }
} }
fn drop_reader(&mut self) { fn drop_reader(&mut self) {
self.write_waker.invoke(); self.wake_writer();
self.reader_dropped = true; self.reader_dropped = true;
if self.writer_dropped { if self.writer_dropped {
(self.drop)(self.state) (self.drop)(self.state)
@@ -134,25 +132,14 @@ impl AsyncRingbuffer {
if self.reader_dropped { if self.reader_dropped {
return Poll::Ready(Err(broken_pipe_error())); return Poll::Ready(Err(broken_pipe_error()));
} }
self.read_waker.invoke();
self.write_waker.drop(); self.write_waker.drop();
self.write_waker = Trigger::new(waker.clone()); self.write_waker = Trigger::new(waker.clone());
Poll::Pending Poll::Pending
} }
fn flush_wait<T>(&mut self, waker: &Waker) -> Poll<io::Result<T>> {
if self.reader_dropped {
return Poll::Ready(Err(broken_pipe_error()));
}
self.read_waker.invoke();
self.flush_waker.drop();
self.flush_waker = Trigger::new(waker.clone());
Poll::Pending
}
fn reader_wait(&mut self, waker: &Waker) -> Poll<io::Result<usize>> { fn reader_wait(&mut self, waker: &Waker) -> Poll<io::Result<usize>> {
if self.writer_dropped { if self.writer_dropped {
return Poll::Ready(Err(broken_pipe_error())); return Poll::Ready(Err(broken_pipe_error()));
} }
self.write_waker.invoke();
self.read_waker.drop(); self.read_waker.drop();
self.read_waker = Trigger::new(waker.clone()); self.read_waker = Trigger::new(waker.clone());
Poll::Pending Poll::Pending
@@ -234,7 +221,7 @@ impl Writer {
return Err(SyncWriteError::BufferFull); return Err(SyncWriteError::BufferFull);
} }
state.wrapping_write_unchecked(data); state.wrapping_write_unchecked(data);
state.write_waker.invoke(); state.wake_reader();
Ok(()) Ok(())
} }
} }
@@ -255,7 +242,7 @@ impl AsyncWrite for Writer {
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> { fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
unsafe { unsafe {
let data = self.as_mut().get_state()?; let data = self.as_mut().get_state()?;
if data.is_empty() { Poll::Ready(Ok(())) } else { data.flush_wait(cx.waker()) } if data.is_empty() { Poll::Ready(Ok(())) } else { data.writer_wait(cx.waker()) }
} }
} }
fn poll_write( fn poll_write(
@@ -263,12 +250,15 @@ impl AsyncWrite for Writer {
cx: &mut Context<'_>, cx: &mut Context<'_>,
buf: &[u8], buf: &[u8],
) -> Poll<io::Result<usize>> { ) -> Poll<io::Result<usize>> {
if buf.is_empty() {
return Poll::Ready(Ok(0));
}
unsafe { unsafe {
let data = self.as_mut().get_state()?; let data = self.as_mut().get_state()?;
if !buf.is_empty() && data.is_empty() { if data.is_empty() {
data.read_waker.invoke(); data.wake_reader();
} }
if !buf.is_empty() && data.is_full() { if data.is_full() {
data.writer_wait(cx.waker()) data.writer_wait(cx.waker())
} else { } else {
Poll::Ready(Ok(data.wrapping_write_unchecked(buf))) Poll::Ready(Ok(data.wrapping_write_unchecked(buf)))
@@ -300,7 +290,7 @@ impl AsyncRead for Reader {
let data = self.0.as_mut().expect("Cannot be null"); let data = self.0.as_mut().expect("Cannot be null");
let AsyncRingbuffer { read_idx, write_idx, size, .. } = *data; let AsyncRingbuffer { read_idx, write_idx, size, .. } = *data;
if !buf.is_empty() && data.is_full() { if !buf.is_empty() && data.is_full() {
data.write_waker.invoke(); data.wake_writer();
} }
let poll = if !buf.is_empty() && data.is_empty() { let poll = if !buf.is_empty() && data.is_empty() {
// Nothing to read, waiting... // Nothing to read, waiting...
@@ -322,8 +312,8 @@ impl AsyncRead for Reader {
data.non_wrapping_read_unchecked(&mut start[0..start_count]); data.non_wrapping_read_unchecked(&mut start[0..start_count]);
Poll::Ready(Ok(end.len() + start_count)) Poll::Ready(Ok(end.len() + start_count))
}; };
if !buf.is_empty() && data.is_empty() { if data.is_empty() {
data.flush_waker.invoke(); data.wake_writer();
} }
poll poll
} }