Introduced dylib extension format, cleared up shutdown sequence

This commit is contained in:
2026-01-17 00:23:35 +01:00
parent 1a7230ce9b
commit 6a3c1d5917
18 changed files with 214 additions and 113 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" ORCHID_EXTENSIONS = "target/debug/orchid-std-dbg"
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

@@ -926,6 +926,7 @@ dependencies = [
"tokio", "tokio",
"tokio-util", "tokio-util",
"trait-set", "trait-set",
"unsync-pipe",
] ]
[[package]] [[package]]

View File

@@ -120,6 +120,3 @@ fn get_ancestry(input: &DeriveInput) -> Option<Vec<pm2::TokenStream>> {
fn is_extendable(input: &DeriveInput) -> bool { fn is_extendable(input: &DeriveInput) -> bool {
input.attrs.iter().any(|a| a.path().get_ident().is_some_and(|i| *i == "extendable")) input.attrs.iter().any(|a| a.path().get_ident().is_some_and(|i| *i == "extendable"))
} }
#[test]
fn test_wtf() { eprintln!("{}", gen_casts(&[quote!(ExtHostReq)], &quote!(BogusReq))) }

View File

@@ -13,7 +13,7 @@ use unsync_pipe::{Reader, Writer};
/// interactions must reflect a single logical owner /// interactions must reflect a single logical owner
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[repr(C)] #[repr(C)]
pub struct OwnedWakerVT { pub struct OwnedWakerBin {
pub data: *const (), pub data: *const (),
/// `self` /// `self`
pub drop: extern "C" fn(*const ()), pub drop: extern "C" fn(*const ()),
@@ -24,7 +24,7 @@ pub struct OwnedWakerVT {
} }
/// !Send !Sync, equivalent to `&mut Context<'a>`, hence no `drop`. /// !Send !Sync, equivalent to `&mut Context<'a>`, hence no `drop`.
/// When received in [FutureVT::poll], it must not outlive the call. /// When received in [FutureBin::poll], it must not outlive the call.
/// ///
/// You cannot directly wake using this waker, because such a trampoline would /// You cannot directly wake using this waker, because such a trampoline would
/// pass through the binary interface twice for no reason. An efficient /// pass through the binary interface twice for no reason. An efficient
@@ -33,10 +33,10 @@ pub struct OwnedWakerVT {
/// it up. /// it up.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[repr(C)] #[repr(C)]
pub struct FutureContextVT { pub struct FutureContextBin {
pub data: *const (), pub data: *const (),
/// `&self` /// `&self`
pub waker: extern "C" fn(*const ()) -> OwnedWakerVT, pub waker: extern "C" fn(*const ()) -> OwnedWakerBin,
} }
/// ABI-stable `Poll<()>` /// ABI-stable `Poll<()>`
@@ -53,24 +53,24 @@ pub enum UnitPoll {
/// interactions must reflect a single logical owner /// interactions must reflect a single logical owner
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[repr(C)] #[repr(C)]
pub struct FutureVT { pub struct FutureBin {
pub data: *const (), pub data: *const (),
/// `self` /// `self`
pub drop: extern "C" fn(*const ()), pub drop: extern "C" fn(*const ()),
/// `&mut self` Equivalent to [Future::poll] /// `&mut self` Equivalent to [Future::poll]
pub poll: extern "C" fn(*const (), FutureContextVT) -> UnitPoll, pub poll: extern "C" fn(*const (), FutureContextBin) -> UnitPoll,
} }
/// Handle for a runtime that allows its holder to spawn futures across dynamic /// Handle for a runtime that allows its holder to spawn futures across dynamic
/// library boundaries /// library boundaries
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[repr(C)] #[repr(C)]
pub struct Spawner { pub struct SpawnerBin {
pub data: *const (), pub data: *const (),
/// `self` /// `self`
pub drop: extern "C" fn(*const ()), pub drop: extern "C" fn(*const ()),
/// `&self` Add a future to this extension's task /// `&self` Add a future to this extension's task
pub spawn: extern "C" fn(*const (), FutureVT), pub spawn: extern "C" fn(*const (), FutureBin),
} }
/// Extension context. /// Extension context.
@@ -80,7 +80,7 @@ pub struct Spawner {
#[repr(C)] #[repr(C)]
pub struct ExtensionContext { pub struct ExtensionContext {
/// Spawns tasks associated with this extension /// Spawns tasks associated with this extension
pub spawner: Spawner, pub spawner: SpawnerBin,
/// serialized [crate::HostExtChannel] /// serialized [crate::HostExtChannel]
pub input: Reader, pub input: Reader,
/// serialized [crate::ExtHostChannel] /// serialized [crate::ExtHostChannel]

View File

@@ -1,14 +1,15 @@
use std::mem;
use std::pin::Pin; 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::{FutureContextVT, FutureVT, OwnedWakerVT, UnitPoll}; use orchid_api::binary::{FutureBin, FutureContextBin, OwnedWakerBin, UnitPoll};
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::<OwnedWakerVT>::from_raw(data as *const _) }; let data = unsafe { Rc::<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.
@@ -18,30 +19,32 @@ 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::<OwnedWakerVT>::from_raw(data as *const _) }; let data = unsafe { Rc::<OwnedWakerBin>::from_raw(data as *const _) };
(data.wake)(data.data); (data.wake)(data.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 OwnedWakerVT).as_ref() }.unwrap(); let data = unsafe { (data as *const 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::<OwnedWakerVT>::from_raw(data as *const _) }; let data = unsafe { Rc::<OwnedWakerBin>::from_raw(data as *const _) };
(data.drop)(data.data); (data.drop)(data.data);
mem::drop(data);
}, },
); );
struct BorrowedWakerData<'a> { struct BorrowedWakerData<'a> {
go_around: &'a mut bool, go_around: &'a mut bool,
cx: FutureContextVT, cx: 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::<OwnedWakerVT>::new((data.cx.waker)(data.cx.data)); let owned_data = Rc::<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,
@@ -51,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) -> FutureVT { pub fn future_to_vt<Fut: Future<Output = ()> + 'static>(fut: Fut) -> 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 ()) {
std::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: FutureContextVT) -> UnitPoll { extern "C" fn poll(raw: *const (), cx: FutureContextBin) -> 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;
@@ -77,22 +80,22 @@ pub fn future_to_vt<Fut: Future<Output = ()> + 'static>(fut: Fut) -> FutureVT {
} }
} }
} }
FutureVT { data: data as *const _, drop, poll } FutureBin { data: data as *const _, drop, poll }
} }
struct VirtualFuture { struct VirtualFuture {
vt: FutureVT, vt: 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 ()) -> OwnedWakerVT { extern "C" fn waker(raw: *const ()) -> 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 OwnedWakerVT { data, drop, wake, wake_ref }; return OwnedWakerBin { data, drop, wake, wake_ref };
extern "C" fn drop(raw: *const ()) { extern "C" fn drop(raw: *const ()) {
std::mem::drop(unsafe { Box::<Waker>::from_raw(raw as *mut Waker) }) mem::drop(unsafe { Box::<Waker>::from_raw(raw as *mut Waker) })
} }
extern "C" fn wake(raw: *const ()) { extern "C" fn wake(raw: *const ()) {
unsafe { Box::<Waker>::from_raw(raw as *mut Waker) }.wake(); unsafe { Box::<Waker>::from_raw(raw as *mut Waker) }.wake();
@@ -101,7 +104,7 @@ 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 = FutureContextVT { data: cx as *mut Context as *const (), waker }; let cx = 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, UnitPoll::Pending => Poll::Pending,
@@ -115,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: FutureVT) -> impl Future<Output = ()> { VirtualFuture { vt } } pub fn vt_to_future(vt: FutureBin) -> impl Future<Output = ()> { VirtualFuture { vt } }

View File

@@ -299,10 +299,15 @@ 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) { self.exit.clone().send(()).await.expect("quit channel dropped"); } pub async fn exit(self) -> io::Result<()> {
self.o.lock().await.as_mut().close().await?;
self.exit.clone().send(()).await.expect("quit channel dropped");
Ok(())
}
} }
/// Establish bidirectional request-notification communication over a duplex /// Establish bidirectional request-notification communication over a duplex
@@ -313,13 +318,14 @@ impl CommCtx {
/// check that the correct message families are sent in the correct directions /// check that the correct message families are sent in the correct directions
/// across the channel. /// across the channel.
pub fn io_comm( pub fn io_comm(
o: Rc<Mutex<Pin<Box<dyn AsyncWrite>>>>, o: Pin<Box<dyn AsyncWrite>>,
i: Mutex<Pin<Box<dyn AsyncRead>>>, i: Pin<Box<dyn AsyncRead>>,
) -> (impl Client + 'static, CommCtx, IoCommServer) { ) -> (impl Client + 'static, CommCtx, IoCommServer) {
let i = Rc::new(i); let i = Rc::new(Mutex::new(i));
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 }, IoCommServer { o, i, onsub, onexit }) (client, CommCtx { exit, o: o.clone() }, 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,15 +351,12 @@ impl IoCommServer {
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;
match u64::decode(g.as_mut()).await { match u64::decode(g.as_mut()).await {
Ok(id) => h.emit(Event::Input(id, g)).await, Ok(id) => h.emit(Event::Input(id, g)).await,
Err(e) Err(e) => match e.kind() {
if matches!(
e.kind(),
io::ErrorKind::BrokenPipe io::ErrorKind::BrokenPipe
| io::ErrorKind::ConnectionAborted | io::ErrorKind::ConnectionAborted
| io::ErrorKind::UnexpectedEof | io::ErrorKind::UnexpectedEof => h.emit(Event::Exit).await,
) => _ => return Err(e),
h.emit(Event::Exit).await, },
Err(e) => return Err(e),
} }
} }
}); });
@@ -419,10 +422,8 @@ impl IoCommServer {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc;
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::lock::Mutex;
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;
@@ -444,9 +445,8 @@ mod test {
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);
let (_, recv_ctx, recv_srv) = let (_, recv_ctx, recv_srv) = io_comm(Box::pin(in2), Box::pin(out2));
io_comm(Rc::new(Mutex::new(Box::pin(in2))), Mutex::new(Box::pin(out2))); let (sender, ..) = io_comm(Box::pin(in1), Box::pin(out1));
let (sender, ..) = io_comm(Rc::new(Mutex::new(Box::pin(in1))), Mutex::new(Box::pin(out1)));
join!( join!(
async { async {
recv_srv recv_srv
@@ -465,7 +465,7 @@ mod test {
assert_eq!(on_receive.next().await, Some(TestNotif(3))); assert_eq!(on_receive.next().await, Some(TestNotif(3)));
sender.notify(TestNotif(4)).await.unwrap(); sender.notify(TestNotif(4)).await.unwrap();
assert_eq!(on_receive.next().await, Some(TestNotif(4))); assert_eq!(on_receive.next().await, Some(TestNotif(4)));
recv_ctx.exit().await; recv_ctx.exit().await.unwrap();
} }
); );
})) }))
@@ -484,10 +484,8 @@ mod test {
spin_on(with_logger(logger, 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) = let (_, srv_ctx, srv) = io_comm(Box::pin(in2), Box::pin(out2));
io_comm(Rc::new(Mutex::new(Box::pin(in2))), Mutex::new(Box::pin(out2))); let (client, client_ctx, client_srv) = io_comm(Box::pin(in1), Box::pin(out1));
let (client, client_ctx, client_srv) =
io_comm(Rc::new(Mutex::new(Box::pin(in1))), Mutex::new(Box::pin(out1)));
join!( join!(
async { async {
srv srv
@@ -513,8 +511,8 @@ mod test {
async { async {
let response = client.request(DummyRequest(5)).await.unwrap(); let response = client.request(DummyRequest(5)).await.unwrap();
assert_eq!(response, 6); assert_eq!(response, 6);
srv_ctx.exit().await; srv_ctx.exit().await.unwrap();
client_ctx.exit().await; client_ctx.exit().await.unwrap();
} }
); );
})) }))
@@ -527,9 +525,8 @@ mod test {
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) =
io_comm(Rc::new(Mutex::new(Box::pin(input1))), Mutex::new(Box::pin(output2))); io_comm(Box::pin(input1), Box::pin(output2));
let (req_client, req_context, req_server) = let (req_client, req_context, req_server) = io_comm(Box::pin(input2), Box::pin(output1));
io_comm(Rc::new(Mutex::new(Box::pin(input2))), Mutex::new(Box::pin(output1)));
let reply_context = RefCell::new(Some(reply_context)); let reply_context = RefCell::new(Some(reply_context));
let (exit, onexit) = futures::channel::oneshot::channel::<()>(); let (exit, onexit) = futures::channel::oneshot::channel::<()>();
join!( join!(
@@ -539,7 +536,7 @@ mod test {
async |hand| { async |hand| {
let _notif = hand.read::<TestNotif>().await.unwrap(); let _notif = hand.read::<TestNotif>().await.unwrap();
let context = reply_context.borrow_mut().take().unwrap(); let context = reply_context.borrow_mut().take().unwrap();
context.exit().await; context.exit().await?;
Ok(()) Ok(())
}, },
async |mut hand| { async |mut hand| {

View File

@@ -36,6 +36,7 @@ tokio = { version = "1.49.0", optional = true, features = [] }
tokio-util = { version = "0.7.17", optional = true, features = ["compat"] } tokio-util = { version = "0.7.17", optional = true, features = ["compat"] }
trait-set = "0.3.0" trait-set = "0.3.0"
unsync-pipe = { version = "0.2.0", path = "../unsync-pipe" }
[features] [features]
tokio = ["dep:tokio", "dep:tokio-util"] tokio = ["dep:tokio", "dep:tokio-util"]

View File

@@ -9,7 +9,7 @@ use crate::ext_port::ExtPort;
pub type ExtCx = api::binary::ExtensionContext; pub type ExtCx = api::binary::ExtensionContext;
struct Spawner(api::binary::Spawner); struct Spawner(api::binary::SpawnerBin);
impl Drop for Spawner { impl Drop for Spawner {
fn drop(&mut self) { (self.0.drop)(self.0.data) } fn drop(&mut self) { (self.0.drop)(self.0.data) }
} }
@@ -28,3 +28,22 @@ pub fn orchid_extension_main_body(cx: ExtCx, builder: ExtensionBuilder) {
spawn: Rc::new(move |fut| spawner.spawn(fut)), spawn: Rc::new(move |fut| spawner.spawn(fut)),
}); });
} }
/// Generate entrypoint for the dylib extension loader
///
/// # Usage
///
/// ```
/// dylib_main! {
/// ExtensionBuilder::new("orchid-std::main")
/// }
/// ```
#[macro_export]
macro_rules! dylib_main {
($builder:expr) => {
#[unsafe(no_mangle)]
pub extern "C" fn orchid_extension_main(cx: ::orchid_api::binary::ExtensionContext) {
$crate::binary::orchid_extension_main_body(cx, $builder);
}
};
}

View File

@@ -6,7 +6,6 @@ use std::rc::Rc;
use std::{io, mem}; use std::{io, mem};
use futures::future::{LocalBoxFuture, join_all}; use futures::future::{LocalBoxFuture, join_all};
use futures::lock::Mutex;
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;
@@ -50,7 +49,7 @@ task_local::task_local! {
fn get_client() -> Rc<dyn Client> { CLIENT.get() } fn get_client() -> Rc<dyn Client> { CLIENT.get() }
pub async fn exit() { pub async fn exit() {
let cx = CTX.get().borrow_mut().take(); let cx = CTX.get().borrow_mut().take();
cx.unwrap().exit().await cx.unwrap().exit().await.unwrap()
} }
/// Sent the client used for global [request] and [notify] functions within the /// Sent the client used for global [request] and [notify] functions within the
@@ -139,8 +138,7 @@ impl ExtensionBuilder {
ctx.output.as_mut().flush().await.unwrap(); ctx.output.as_mut().flush().await.unwrap();
let logger1 = LoggerImpl::from_api(&host_header.logger); let logger1 = LoggerImpl::from_api(&host_header.logger);
let logger2 = logger1.clone(); let logger2 = logger1.clone();
let (client, comm_ctx, extension_srv) = let (client, comm_ctx, extension_srv) = io_comm(ctx.output, ctx.input);
io_comm(Rc::new(Mutex::new(ctx.output)), Mutex::new(ctx.input));
let extension_fut = extension_srv.listen( let extension_fut = extension_srv.listen(
async |n: Box<dyn MsgReader<'_>>| { async |n: Box<dyn MsgReader<'_>>| {
let notif = n.read().await.unwrap(); let notif = n.read().await.unwrap();

View File

@@ -10,7 +10,7 @@ use crate::ext_port::ExtPort;
/// value returned by [crate::system_ctor::SystemCtor::inst] to initiate /// value returned by [crate::system_ctor::SystemCtor::inst] to initiate
/// shutdown. /// shutdown.
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
pub async fn tokio_main(builder: ExtensionBuilder) -> ! { pub async fn tokio_entrypoint(builder: ExtensionBuilder) {
use tokio::io::{stderr, stdin, stdout}; use tokio::io::{stderr, stdin, stdout};
use tokio::task::{LocalSet, spawn_local}; use tokio::task::{LocalSet, spawn_local};
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt}; use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
@@ -27,5 +27,12 @@ pub async fn tokio_main(builder: ExtensionBuilder) -> ! {
}); });
}); });
local_set.await; local_set.await;
std::process::exit(0) }
#[macro_export]
macro_rules! tokio_main {
($builder:expr) => {
#[tokio::main]
pub async fn main() { $crate::tokio::tokio_entrypoint($builder).await }
};
} }

View File

@@ -1,3 +1,4 @@
use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@@ -37,7 +38,13 @@ pub async fn ext_dylib(path: &Path, ctx: Ctx) -> Result<ExtPort, libloading::Err
use orchid_base::logging::log; use orchid_base::logging::log;
let mut lines = BufReader::new(read_log).lines(); let mut lines = BufReader::new(read_log).lines();
while let Some(line) = lines.next().await { while let Some(line) = lines.next().await {
writeln!(log("stderr"), "{log_path} err> {}", line.expect("Readline implies this")).await; match line {
Ok(line) => writeln!(log("stderr"), "{log_path} err> {line}").await,
Err(e) => match e.kind() {
io::ErrorKind::BrokenPipe | io::ErrorKind::UnexpectedEof => break,
_ => panic!("Error while reading stderr {e}"),
},
}
} }
}); });
let library = load_dylib(path)?; let library = load_dylib(path)?;
@@ -45,10 +52,10 @@ pub async fn ext_dylib(path: &Path, ctx: Ctx) -> Result<ExtPort, libloading::Err
unsafe { library.get("orchid_extension_main") }?; unsafe { library.get("orchid_extension_main") }?;
let data = Box::into_raw(Box::new(ctx)) as *const (); let data = Box::into_raw(Box::new(ctx)) as *const ();
extern "C" fn drop(data: *const ()) { std::mem::drop(unsafe { Box::from_raw(data as *mut Ctx) }) } extern "C" fn drop(data: *const ()) { std::mem::drop(unsafe { Box::from_raw(data as *mut Ctx) }) }
extern "C" fn spawn(data: *const (), vt: api::binary::FutureVT) { extern "C" fn spawn(data: *const (), vt: api::binary::FutureBin) {
let _ = unsafe { (data as *mut Ctx).as_mut().unwrap().spawn(vt_to_future(vt)) }; let _ = unsafe { (data as *mut Ctx).as_mut().unwrap().spawn(vt_to_future(vt)) };
} }
let spawner = api::binary::Spawner { data, drop, spawn }; let spawner = api::binary::SpawnerBin { data, drop, spawn };
let cx = api::binary::ExtensionContext { input, output, log, spawner }; let cx = api::binary::ExtensionContext { input, output, log, spawner };
unsafe { (entrypoint)(cx) }; unsafe { (entrypoint)(cx) };
Ok(ExtPort { input: Box::pin(write_input), output: Box::pin(read_output) }) Ok(ExtPort { input: Box::pin(write_input), output: Box::pin(read_output) })

View File

@@ -19,7 +19,9 @@ use orchid_base::interner::{IStr, IStrv, es, ev, is, iv};
use orchid_base::location::Pos; use orchid_base::location::Pos;
use orchid_base::logging::log; use orchid_base::logging::log;
use orchid_base::name::Sym; use orchid_base::name::Sym;
use orchid_base::reqnot::{Client, ClientExt, MsgReaderExt, ReqHandleExt, ReqReaderExt, io_comm}; use orchid_base::reqnot::{
Client, ClientExt, CommCtx, MsgReaderExt, ReqHandleExt, ReqReaderExt, io_comm,
};
use orchid_base::stash::{stash, with_stash}; use orchid_base::stash::{stash, with_stash};
use orchid_base::tree::AtomRepr; use orchid_base::tree::AtomRepr;
@@ -46,6 +48,7 @@ pub struct ReqPair<R: Request>(R, Sender<R::Response>);
pub struct ExtensionData { pub struct ExtensionData {
name: String, name: String,
ctx: Ctx, ctx: Ctx,
comm_cx: Option<CommCtx>,
join_ext: Option<Box<dyn JoinHandle>>, join_ext: Option<Box<dyn JoinHandle>>,
client: Rc<dyn Client>, client: Rc<dyn Client>,
systems: Vec<SystemCtor>, systems: Vec<SystemCtor>,
@@ -58,8 +61,10 @@ impl Drop for ExtensionData {
fn drop(&mut self) { fn drop(&mut self) {
let client = self.client.clone(); let client = self.client.clone();
let join_ext = self.join_ext.take().expect("Only called once in Drop"); let join_ext = self.join_ext.take().expect("Only called once in Drop");
let comm_cx = self.comm_cx.take().expect("Only used here");
stash(async move { stash(async move {
client.notify(api::HostExtNotif::Exit).await.unwrap(); client.notify(api::HostExtNotif::Exit).await.unwrap();
comm_cx.exit().await.unwrap();
join_ext.join().await; join_ext.join().await;
}) })
} }
@@ -76,7 +81,7 @@ impl Extension {
let header2 = header.clone(); let header2 = header.clone();
Ok(Self(Rc::new_cyclic(|weak: &Weak<ExtensionData>| { Ok(Self(Rc::new_cyclic(|weak: &Weak<ExtensionData>| {
// context not needed because exit is extension-initiated // context not needed because exit is extension-initiated
let (client, _, comm) = io_comm(Rc::new(Mutex::new(init.input)), Mutex::new(init.output)); let (client, comm_cx, comm) = io_comm(init.input, init.output);
let weak2 = weak; let weak2 = weak;
let weak = weak.clone(); let weak = weak.clone();
let ctx2 = ctx.clone(); let ctx2 = ctx.clone();
@@ -274,6 +279,7 @@ impl Extension {
ExtensionData { ExtensionData {
name: header2.name.clone(), name: header2.name.clone(),
ctx: ctx2, ctx: ctx2,
comm_cx: Some(comm_cx),
systems: (header.systems.iter().cloned()) systems: (header.systems.iter().cloned())
.map(|decl| SystemCtor { decl, ext: WeakExtension(weak2.clone()) }) .map(|decl| SystemCtor { decl, ext: WeakExtension(weak2.clone()) })
.collect(), .collect(),

View File

@@ -4,11 +4,12 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[[bin]] [[bin]]
name = "orchid-std" name = "orchid-std-dbg"
path = "src/main.rs" path = "src/main.rs"
[lib] [lib]
crate-type = ["cdylib", "lib"] crate-type = ["cdylib", "lib"]
name = "orchid_std"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]

View File

@@ -11,12 +11,11 @@ pub use std::tuple::{HomoTpl, Tpl, Tuple, UntypedTuple};
pub use macros::macro_system::MacroSystem; pub use macros::macro_system::MacroSystem;
pub use macros::mactree::{MacTok, MacTree}; pub use macros::mactree::{MacTok, MacTree};
use orchid_api as api; use orchid_api as api;
use orchid_extension::binary::orchid_extension_main_body; use orchid_extension::dylib_main;
use orchid_extension::entrypoint::ExtensionBuilder; use orchid_extension::entrypoint::ExtensionBuilder;
pub extern "C" fn orchid_extension_main(cx: api::binary::ExtensionContext) { pub fn builder() -> ExtensionBuilder {
orchid_extension_main_body( ExtensionBuilder::new("orchid-std::main").system(StdSystem).system(MacroSystem)
cx,
ExtensionBuilder::new("orchid-std::main").system(StdSystem).system(MacroSystem),
);
} }
dylib_main! { builder() }

View File

@@ -1,8 +1,6 @@
use orchid_extension::entrypoint::ExtensionBuilder; use orchid_extension::tokio_main;
use orchid_extension::tokio::tokio_main; use orchid_std::builder;
use orchid_std::{MacroSystem, StdSystem};
#[tokio::main(flavor = "current_thread")] tokio_main! {
pub async fn main() { builder()
tokio_main(ExtensionBuilder::new("orchid-std::main").system(StdSystem).system(MacroSystem)).await
} }

View File

@@ -1,4 +1,5 @@
use orchid_base::logging::Logger; use orchid_base::logging::Logger;
use orchid_host::dylib::ext_dylib;
use tokio::time::Instant; use tokio::time::Instant;
pub mod parse_folder; pub mod parse_folder;
@@ -9,7 +10,7 @@ use std::process::{Command, ExitCode};
use std::rc::Rc; use std::rc::Rc;
use async_fn_stream::try_stream; use async_fn_stream::try_stream;
use camino::Utf8PathBuf; use camino::{Utf8Path, Utf8PathBuf};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use futures::future::LocalBoxFuture; use futures::future::LocalBoxFuture;
use futures::{FutureExt, Stream, TryStreamExt, io}; use futures::{FutureExt, Stream, TryStreamExt, io};
@@ -100,10 +101,32 @@ fn get_all_extensions<'a>(
args: &'a Args, args: &'a Args,
ctx: &'a Ctx, ctx: &'a Ctx,
) -> impl Stream<Item = io::Result<Extension>> + 'a { ) -> impl Stream<Item = io::Result<Extension>> + 'a {
fn not_found_error(ext_path: &Utf8Path) -> io::Error {
io::Error::new(
std::io::ErrorKind::NotFound,
format!("None of the file candidates for {ext_path} were found"),
)
}
try_stream(async |mut cx| { try_stream(async |mut cx| {
for ext_path in args.extension.iter() { for ext_path in args.extension.iter() {
let exe = if cfg!(windows) { ext_path.with_extension("exe") } else { ext_path.clone() }; let init = if cfg!(windows) {
let init = ext_command(Command::new(exe.as_os_str()), ctx.clone()).await?; if ext_path.with_extension("dll").exists() {
let dylib =
ext_dylib(ext_path.with_extension("dll").as_std_path(), ctx.clone()).await.unwrap();
eprintln!("Loaded DLL {ext_path}.dll");
dylib
} else if ext_path.with_extension("exe").exists() {
ext_command(Command::new(ext_path.with_extension("exe").as_os_str()), ctx.clone()).await?
} else {
return Err(not_found_error(ext_path));
}
} else if ext_path.with_extension("so").exists() {
ext_dylib(ext_path.with_extension("so").as_std_path(), ctx.clone()).await.unwrap()
} else if ext_path.exists() {
ext_command(Command::new(ext_path.as_os_str()), ctx.clone()).await?
} else {
return Err(not_found_error(ext_path));
};
cx.emit(Extension::new(init, ctx.clone()).await?).await; cx.emit(Extension::new(init, ctx.clone()).await?).await;
} }
Ok(cx) Ok(cx)

View File

@@ -1,5 +1,4 @@
# meta # meta
format_code_in_doc_comments = true
unstable_features = true unstable_features = true
style_edition = "2024" style_edition = "2024"

View File

@@ -27,6 +27,7 @@ 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
@@ -37,11 +38,11 @@ pub fn pipe(size: usize) -> (Writer, Reader) {
state: _, state: _,
} = *unsafe { Box::from_raw(val as *mut AsyncRingbuffer) }; } = *unsafe { Box::from_raw(val as *mut AsyncRingbuffer) };
if !writer_dropped || !reader_dropped { if !writer_dropped || !reader_dropped {
eprintln!("Pipe dropped in err before reader or writer");
abort() abort()
} }
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 {
@@ -52,6 +53,7 @@ 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,
@@ -108,18 +110,21 @@ 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 drop_writer(&mut self) { fn drop_writer(&mut self) {
self.read_waker.invoke();
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.reader_dropped = true; self.reader_dropped = true;
if self.writer_dropped { if self.writer_dropped {
(self.drop)(self.state) (self.drop)(self.state)
@@ -134,6 +139,15 @@ impl AsyncRingbuffer {
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()));
@@ -157,6 +171,36 @@ impl AsyncRingbuffer {
} }
fn is_full(&self) -> bool { (self.write_idx + 1) % self.size == self.read_idx } fn is_full(&self) -> bool { (self.write_idx + 1) % self.size == self.read_idx }
fn is_empty(&self) -> bool { self.write_idx == self.read_idx } fn is_empty(&self) -> bool { self.write_idx == self.read_idx }
fn buf_free(&self) -> usize {
let Self { read_idx, write_idx, size, .. } = self;
if write_idx < read_idx { *read_idx - write_idx - 1 } else { size - write_idx + read_idx }
}
fn wrapping_write_unchecked(&mut self, buf: &[u8]) -> usize {
unsafe {
let Self { read_idx, write_idx, size, .. } = *self;
if write_idx < read_idx {
// Non-wrapping backside write w < r <= s
let count = buf.len().min(read_idx - write_idx - 1);
self.non_wrapping_write_unchecked(&buf[0..count]);
count
} else if write_idx + buf.len() < size {
// Non-wrapping frontside write r <= w + b < s
self.non_wrapping_write_unchecked(&buf[0..buf.len()]);
buf.len()
} else if read_idx == 0 {
// Frontside write up to origin r=0 < s < w + b
self.non_wrapping_write_unchecked(&buf[0..size - write_idx - 1]);
size - write_idx - 1
} else {
let (end, start) = buf.split_at(size - write_idx);
// Wrapping write r < s < w + b
self.non_wrapping_write_unchecked(end);
let start_count = start.len().min(read_idx - 1);
self.non_wrapping_write_unchecked(&start[0..start_count]);
end.len() + start_count
}
}
}
} }
fn already_closed_error() -> io::Error { fn already_closed_error() -> io::Error {
@@ -166,6 +210,12 @@ fn broken_pipe_error() -> io::Error {
io::Error::new(io::ErrorKind::BrokenPipe, "Pipe already closed from other end") io::Error::new(io::ErrorKind::BrokenPipe, "Pipe already closed from other end")
} }
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum SyncWriteError {
BufferFull,
AlreadyClosed,
}
/// A binary safe [AsyncWrite] implementor writing to a ringbuffer created by /// A binary safe [AsyncWrite] implementor writing to a ringbuffer created by
/// [pipe]. /// [pipe].
#[repr(C)] #[repr(C)]
@@ -177,6 +227,17 @@ impl Writer {
None => Err(already_closed_error()), None => Err(already_closed_error()),
} }
} }
pub fn try_write_all(self: Pin<&mut Self>, data: &[u8]) -> Result<(), SyncWriteError> {
unsafe {
let state = self.get_state().map_err(|_| SyncWriteError::AlreadyClosed)?;
if state.buf_free() <= data.len() {
return Err(SyncWriteError::BufferFull);
}
state.wrapping_write_unchecked(data);
state.write_waker.invoke();
Ok(())
}
}
} }
impl AsyncWrite for Writer { impl AsyncWrite for Writer {
fn poll_close(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> { fn poll_close(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
@@ -194,7 +255,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.writer_wait(cx.waker()) } if data.is_empty() { Poll::Ready(Ok(())) } else { data.flush_wait(cx.waker()) }
} }
} }
fn poll_write( fn poll_write(
@@ -204,33 +265,13 @@ impl AsyncWrite for Writer {
) -> Poll<io::Result<usize>> { ) -> Poll<io::Result<usize>> {
unsafe { unsafe {
let data = self.as_mut().get_state()?; let data = self.as_mut().get_state()?;
let AsyncRingbuffer { write_idx, read_idx, size, .. } = *data;
if !buf.is_empty() && data.is_empty() { if !buf.is_empty() && data.is_empty() {
data.read_waker.invoke(); data.read_waker.invoke();
} }
if !buf.is_empty() && data.is_full() { if !buf.is_empty() && data.is_full() {
// Writer is blocked
data.writer_wait(cx.waker()) data.writer_wait(cx.waker())
} else if write_idx < read_idx {
// Non-wrapping backside write w < r <= s
let count = buf.len().min(read_idx - write_idx - 1);
data.non_wrapping_write_unchecked(&buf[0..count]);
Poll::Ready(Ok(count))
} else if data.write_idx + buf.len() < size {
// Non-wrapping frontside write r <= w + b < s
data.non_wrapping_write_unchecked(&buf[0..buf.len()]);
Poll::Ready(Ok(buf.len()))
} else if read_idx == 0 {
// Frontside write up to origin r=0 < s < w + b
data.non_wrapping_write_unchecked(&buf[0..size - write_idx - 1]);
Poll::Ready(Ok(size - write_idx - 1))
} else { } else {
let (end, start) = buf.split_at(size - write_idx); Poll::Ready(Ok(data.wrapping_write_unchecked(buf)))
// Wrapping write r < s < w + b
data.non_wrapping_write_unchecked(end);
let start_count = start.len().min(read_idx - 1);
data.non_wrapping_write_unchecked(&start[0..start_count]);
Poll::Ready(Ok(end.len() + start_count))
} }
} }
} }
@@ -261,7 +302,7 @@ impl AsyncRead for Reader {
if !buf.is_empty() && data.is_full() { if !buf.is_empty() && data.is_full() {
data.write_waker.invoke(); data.write_waker.invoke();
} }
if !buf.is_empty() && data.is_empty() { let poll = if !buf.is_empty() && data.is_empty() {
// Nothing to read, waiting... // Nothing to read, waiting...
data.reader_wait(cx.waker()) data.reader_wait(cx.waker())
} else if read_idx < write_idx { } else if read_idx < write_idx {
@@ -280,7 +321,11 @@ impl AsyncRead for Reader {
let start_count = start.len().min(write_idx); let start_count = start.len().min(write_idx);
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() {
data.flush_waker.invoke();
} }
poll
} }
} }
} }