Introduced dylib extension format, cleared up shutdown sequence
This commit is contained in:
@@ -5,7 +5,7 @@ orcxdb = "xtask orcxdb"
|
||||
|
||||
[env]
|
||||
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_LOG_BUFFERS = "true"
|
||||
RUST_BACKTRACE = "1"
|
||||
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -926,6 +926,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"trait-set",
|
||||
"unsync-pipe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -120,6 +120,3 @@ fn get_ancestry(input: &DeriveInput) -> Option<Vec<pm2::TokenStream>> {
|
||||
fn is_extendable(input: &DeriveInput) -> bool {
|
||||
input.attrs.iter().any(|a| a.path().get_ident().is_some_and(|i| *i == "extendable"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wtf() { eprintln!("{}", gen_casts(&[quote!(ExtHostReq)], "e!(BogusReq))) }
|
||||
|
||||
@@ -13,7 +13,7 @@ use unsync_pipe::{Reader, Writer};
|
||||
/// interactions must reflect a single logical owner
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct OwnedWakerVT {
|
||||
pub struct OwnedWakerBin {
|
||||
pub data: *const (),
|
||||
/// `self`
|
||||
pub drop: extern "C" fn(*const ()),
|
||||
@@ -24,7 +24,7 @@ pub struct OwnedWakerVT {
|
||||
}
|
||||
|
||||
/// !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
|
||||
/// pass through the binary interface twice for no reason. An efficient
|
||||
@@ -33,10 +33,10 @@ pub struct OwnedWakerVT {
|
||||
/// it up.
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct FutureContextVT {
|
||||
pub struct FutureContextBin {
|
||||
pub data: *const (),
|
||||
/// `&self`
|
||||
pub waker: extern "C" fn(*const ()) -> OwnedWakerVT,
|
||||
pub waker: extern "C" fn(*const ()) -> OwnedWakerBin,
|
||||
}
|
||||
|
||||
/// ABI-stable `Poll<()>`
|
||||
@@ -53,24 +53,24 @@ pub enum UnitPoll {
|
||||
/// interactions must reflect a single logical owner
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct FutureVT {
|
||||
pub struct FutureBin {
|
||||
pub data: *const (),
|
||||
/// `self`
|
||||
pub drop: extern "C" fn(*const ()),
|
||||
/// `&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
|
||||
/// library boundaries
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Spawner {
|
||||
pub struct SpawnerBin {
|
||||
pub data: *const (),
|
||||
/// `self`
|
||||
pub drop: extern "C" fn(*const ()),
|
||||
/// `&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.
|
||||
@@ -80,7 +80,7 @@ pub struct Spawner {
|
||||
#[repr(C)]
|
||||
pub struct ExtensionContext {
|
||||
/// Spawns tasks associated with this extension
|
||||
pub spawner: Spawner,
|
||||
pub spawner: SpawnerBin,
|
||||
/// serialized [crate::HostExtChannel]
|
||||
pub input: Reader,
|
||||
/// serialized [crate::ExtHostChannel]
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use std::mem;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
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 = ()>>;
|
||||
|
||||
static OWNED_VTABLE: RawWakerVTable = RawWakerVTable::new(
|
||||
|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);
|
||||
// Clone must create a duplicate of the Rc, so it has to be un-leaked, cloned,
|
||||
// then leaked again.
|
||||
@@ -18,30 +19,32 @@ static OWNED_VTABLE: RawWakerVTable = RawWakerVTable::new(
|
||||
|data| {
|
||||
// Wake must awaken the task and then 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.wake)(data.data);
|
||||
mem::drop(data);
|
||||
},
|
||||
|data| {
|
||||
// Wake-by-ref must awaken the task while preserving the future, so the Rc is
|
||||
// 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| {
|
||||
// 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);
|
||||
mem::drop(data);
|
||||
},
|
||||
);
|
||||
|
||||
struct BorrowedWakerData<'a> {
|
||||
go_around: &'a mut bool,
|
||||
cx: FutureContextVT,
|
||||
cx: FutureContextBin,
|
||||
}
|
||||
static BORROWED_VTABLE: RawWakerVTable = RawWakerVTable::new(
|
||||
|data| {
|
||||
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)
|
||||
},
|
||||
|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
|
||||
/// 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 data = Box::into_raw(Box::new(wide_box));
|
||||
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()) };
|
||||
loop {
|
||||
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 {
|
||||
vt: FutureVT,
|
||||
vt: FutureBin,
|
||||
}
|
||||
impl Unpin for VirtualFuture {}
|
||||
impl Future for VirtualFuture {
|
||||
type 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 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 ()) {
|
||||
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 ()) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
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);
|
||||
match result {
|
||||
UnitPoll::Pending => Poll::Pending,
|
||||
@@ -115,4 +118,4 @@ impl Drop for VirtualFuture {
|
||||
|
||||
/// Receive a future sent across dynamic library boundaries and convert it into
|
||||
/// 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 } }
|
||||
|
||||
@@ -299,10 +299,15 @@ impl<'a> MsgWriter<'a> for IoNotifWriter {
|
||||
|
||||
pub struct CommCtx {
|
||||
exit: Sender<()>,
|
||||
o: Rc<Mutex<Pin<Box<dyn AsyncWrite>>>>,
|
||||
}
|
||||
|
||||
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
|
||||
@@ -313,13 +318,14 @@ impl CommCtx {
|
||||
/// check that the correct message families are sent in the correct directions
|
||||
/// across the channel.
|
||||
pub fn io_comm(
|
||||
o: Rc<Mutex<Pin<Box<dyn AsyncWrite>>>>,
|
||||
i: Mutex<Pin<Box<dyn AsyncRead>>>,
|
||||
o: Pin<Box<dyn AsyncWrite>>,
|
||||
i: Pin<Box<dyn AsyncRead>>,
|
||||
) -> (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 (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 {
|
||||
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;
|
||||
match u64::decode(g.as_mut()).await {
|
||||
Ok(id) => h.emit(Event::Input(id, g)).await,
|
||||
Err(e)
|
||||
if matches!(
|
||||
e.kind(),
|
||||
Err(e) => match e.kind() {
|
||||
io::ErrorKind::BrokenPipe
|
||||
| io::ErrorKind::ConnectionAborted
|
||||
| io::ErrorKind::UnexpectedEof
|
||||
) =>
|
||||
h.emit(Event::Exit).await,
|
||||
Err(e) => return Err(e),
|
||||
| io::ErrorKind::UnexpectedEof => h.emit(Event::Exit).await,
|
||||
_ => return Err(e),
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -419,10 +422,8 @@ impl IoCommServer {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
use futures::{SinkExt, StreamExt, join};
|
||||
use orchid_api_derive::{Coding, Hierarchy};
|
||||
use orchid_api_traits::Request;
|
||||
@@ -444,9 +445,8 @@ mod test {
|
||||
let (in1, out2) = pipe(1024);
|
||||
let (in2, out1) = pipe(1024);
|
||||
let (received, mut on_receive) = mpsc::channel(2);
|
||||
let (_, recv_ctx, recv_srv) =
|
||||
io_comm(Rc::new(Mutex::new(Box::pin(in2))), Mutex::new(Box::pin(out2)));
|
||||
let (sender, ..) = io_comm(Rc::new(Mutex::new(Box::pin(in1))), Mutex::new(Box::pin(out1)));
|
||||
let (_, recv_ctx, recv_srv) = io_comm(Box::pin(in2), Box::pin(out2));
|
||||
let (sender, ..) = io_comm(Box::pin(in1), Box::pin(out1));
|
||||
join!(
|
||||
async {
|
||||
recv_srv
|
||||
@@ -465,7 +465,7 @@ mod test {
|
||||
assert_eq!(on_receive.next().await, Some(TestNotif(3)));
|
||||
sender.notify(TestNotif(4)).await.unwrap();
|
||||
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 {
|
||||
let (in1, out2) = pipe(1024);
|
||||
let (in2, out1) = pipe(1024);
|
||||
let (_, srv_ctx, srv) =
|
||||
io_comm(Rc::new(Mutex::new(Box::pin(in2))), Mutex::new(Box::pin(out2)));
|
||||
let (client, client_ctx, client_srv) =
|
||||
io_comm(Rc::new(Mutex::new(Box::pin(in1))), Mutex::new(Box::pin(out1)));
|
||||
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
|
||||
@@ -513,8 +511,8 @@ mod test {
|
||||
async {
|
||||
let response = client.request(DummyRequest(5)).await.unwrap();
|
||||
assert_eq!(response, 6);
|
||||
srv_ctx.exit().await;
|
||||
client_ctx.exit().await;
|
||||
srv_ctx.exit().await.unwrap();
|
||||
client_ctx.exit().await.unwrap();
|
||||
}
|
||||
);
|
||||
}))
|
||||
@@ -527,9 +525,8 @@ mod test {
|
||||
let (input1, output1) = pipe(1024);
|
||||
let (input2, output2) = pipe(1024);
|
||||
let (reply_client, reply_context, reply_server) =
|
||||
io_comm(Rc::new(Mutex::new(Box::pin(input1))), Mutex::new(Box::pin(output2)));
|
||||
let (req_client, req_context, req_server) =
|
||||
io_comm(Rc::new(Mutex::new(Box::pin(input2))), Mutex::new(Box::pin(output1)));
|
||||
io_comm(Box::pin(input1), Box::pin(output2));
|
||||
let (req_client, req_context, req_server) = io_comm(Box::pin(input2), Box::pin(output1));
|
||||
let reply_context = RefCell::new(Some(reply_context));
|
||||
let (exit, onexit) = futures::channel::oneshot::channel::<()>();
|
||||
join!(
|
||||
@@ -539,7 +536,7 @@ mod test {
|
||||
async |hand| {
|
||||
let _notif = hand.read::<TestNotif>().await.unwrap();
|
||||
let context = reply_context.borrow_mut().take().unwrap();
|
||||
context.exit().await;
|
||||
context.exit().await?;
|
||||
Ok(())
|
||||
},
|
||||
async |mut hand| {
|
||||
|
||||
@@ -36,6 +36,7 @@ tokio = { version = "1.49.0", optional = true, features = [] }
|
||||
tokio-util = { version = "0.7.17", optional = true, features = ["compat"] }
|
||||
|
||||
trait-set = "0.3.0"
|
||||
unsync-pipe = { version = "0.2.0", path = "../unsync-pipe" }
|
||||
|
||||
[features]
|
||||
tokio = ["dep:tokio", "dep:tokio-util"]
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::ext_port::ExtPort;
|
||||
|
||||
pub type ExtCx = api::binary::ExtensionContext;
|
||||
|
||||
struct Spawner(api::binary::Spawner);
|
||||
struct Spawner(api::binary::SpawnerBin);
|
||||
impl Drop for Spawner {
|
||||
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)),
|
||||
});
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ use std::rc::Rc;
|
||||
use std::{io, mem};
|
||||
|
||||
use futures::future::{LocalBoxFuture, join_all};
|
||||
use futures::lock::Mutex;
|
||||
use futures::{AsyncRead, AsyncWrite, AsyncWriteExt, StreamExt, stream};
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
@@ -50,7 +49,7 @@ task_local::task_local! {
|
||||
fn get_client() -> Rc<dyn Client> { CLIENT.get() }
|
||||
pub async fn exit() {
|
||||
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
|
||||
@@ -139,8 +138,7 @@ impl ExtensionBuilder {
|
||||
ctx.output.as_mut().flush().await.unwrap();
|
||||
let logger1 = LoggerImpl::from_api(&host_header.logger);
|
||||
let logger2 = logger1.clone();
|
||||
let (client, comm_ctx, extension_srv) =
|
||||
io_comm(Rc::new(Mutex::new(ctx.output)), Mutex::new(ctx.input));
|
||||
let (client, comm_ctx, extension_srv) = io_comm(ctx.output, ctx.input);
|
||||
let extension_fut = extension_srv.listen(
|
||||
async |n: Box<dyn MsgReader<'_>>| {
|
||||
let notif = n.read().await.unwrap();
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::ext_port::ExtPort;
|
||||
/// value returned by [crate::system_ctor::SystemCtor::inst] to initiate
|
||||
/// shutdown.
|
||||
#[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::task::{LocalSet, spawn_local};
|
||||
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
|
||||
@@ -27,5 +27,12 @@ pub async fn tokio_main(builder: ExtensionBuilder) -> ! {
|
||||
});
|
||||
});
|
||||
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 }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
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;
|
||||
let mut lines = BufReader::new(read_log).lines();
|
||||
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)?;
|
||||
@@ -45,10 +52,10 @@ pub async fn ext_dylib(path: &Path, ctx: Ctx) -> Result<ExtPort, libloading::Err
|
||||
unsafe { library.get("orchid_extension_main") }?;
|
||||
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 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 spawner = api::binary::Spawner { data, drop, spawn };
|
||||
let spawner = api::binary::SpawnerBin { data, drop, spawn };
|
||||
let cx = api::binary::ExtensionContext { input, output, log, spawner };
|
||||
unsafe { (entrypoint)(cx) };
|
||||
Ok(ExtPort { input: Box::pin(write_input), output: Box::pin(read_output) })
|
||||
|
||||
@@ -19,7 +19,9 @@ use orchid_base::interner::{IStr, IStrv, es, ev, is, iv};
|
||||
use orchid_base::location::Pos;
|
||||
use orchid_base::logging::log;
|
||||
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::tree::AtomRepr;
|
||||
|
||||
@@ -46,6 +48,7 @@ pub struct ReqPair<R: Request>(R, Sender<R::Response>);
|
||||
pub struct ExtensionData {
|
||||
name: String,
|
||||
ctx: Ctx,
|
||||
comm_cx: Option<CommCtx>,
|
||||
join_ext: Option<Box<dyn JoinHandle>>,
|
||||
client: Rc<dyn Client>,
|
||||
systems: Vec<SystemCtor>,
|
||||
@@ -58,8 +61,10 @@ impl Drop for ExtensionData {
|
||||
fn drop(&mut self) {
|
||||
let client = self.client.clone();
|
||||
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 {
|
||||
client.notify(api::HostExtNotif::Exit).await.unwrap();
|
||||
comm_cx.exit().await.unwrap();
|
||||
join_ext.join().await;
|
||||
})
|
||||
}
|
||||
@@ -76,7 +81,7 @@ impl Extension {
|
||||
let header2 = header.clone();
|
||||
Ok(Self(Rc::new_cyclic(|weak: &Weak<ExtensionData>| {
|
||||
// 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 weak = weak.clone();
|
||||
let ctx2 = ctx.clone();
|
||||
@@ -274,6 +279,7 @@ impl Extension {
|
||||
ExtensionData {
|
||||
name: header2.name.clone(),
|
||||
ctx: ctx2,
|
||||
comm_cx: Some(comm_cx),
|
||||
systems: (header.systems.iter().cloned())
|
||||
.map(|decl| SystemCtor { decl, ext: WeakExtension(weak2.clone()) })
|
||||
.collect(),
|
||||
|
||||
@@ -4,11 +4,12 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "orchid-std"
|
||||
name = "orchid-std-dbg"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "orchid_std"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -11,12 +11,11 @@ pub use std::tuple::{HomoTpl, Tpl, Tuple, UntypedTuple};
|
||||
pub use macros::macro_system::MacroSystem;
|
||||
pub use macros::mactree::{MacTok, MacTree};
|
||||
use orchid_api as api;
|
||||
use orchid_extension::binary::orchid_extension_main_body;
|
||||
use orchid_extension::dylib_main;
|
||||
use orchid_extension::entrypoint::ExtensionBuilder;
|
||||
|
||||
pub extern "C" fn orchid_extension_main(cx: api::binary::ExtensionContext) {
|
||||
orchid_extension_main_body(
|
||||
cx,
|
||||
ExtensionBuilder::new("orchid-std::main").system(StdSystem).system(MacroSystem),
|
||||
);
|
||||
pub fn builder() -> ExtensionBuilder {
|
||||
ExtensionBuilder::new("orchid-std::main").system(StdSystem).system(MacroSystem)
|
||||
}
|
||||
|
||||
dylib_main! { builder() }
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use orchid_extension::entrypoint::ExtensionBuilder;
|
||||
use orchid_extension::tokio::tokio_main;
|
||||
use orchid_std::{MacroSystem, StdSystem};
|
||||
use orchid_extension::tokio_main;
|
||||
use orchid_std::builder;
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn main() {
|
||||
tokio_main(ExtensionBuilder::new("orchid-std::main").system(StdSystem).system(MacroSystem)).await
|
||||
tokio_main! {
|
||||
builder()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use orchid_base::logging::Logger;
|
||||
use orchid_host::dylib::ext_dylib;
|
||||
use tokio::time::Instant;
|
||||
pub mod parse_folder;
|
||||
|
||||
@@ -9,7 +10,7 @@ use std::process::{Command, ExitCode};
|
||||
use std::rc::Rc;
|
||||
|
||||
use async_fn_stream::try_stream;
|
||||
use camino::Utf8PathBuf;
|
||||
use camino::{Utf8Path, Utf8PathBuf};
|
||||
use clap::{Parser, Subcommand};
|
||||
use futures::future::LocalBoxFuture;
|
||||
use futures::{FutureExt, Stream, TryStreamExt, io};
|
||||
@@ -100,10 +101,32 @@ fn get_all_extensions<'a>(
|
||||
args: &'a Args,
|
||||
ctx: &'a Ctx,
|
||||
) -> 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| {
|
||||
for ext_path in args.extension.iter() {
|
||||
let exe = if cfg!(windows) { ext_path.with_extension("exe") } else { ext_path.clone() };
|
||||
let init = ext_command(Command::new(exe.as_os_str()), ctx.clone()).await?;
|
||||
let init = if cfg!(windows) {
|
||||
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;
|
||||
}
|
||||
Ok(cx)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# meta
|
||||
format_code_in_doc_comments = true
|
||||
unstable_features = true
|
||||
style_edition = "2024"
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ pub fn pipe(size: usize) -> (Writer, Reader) {
|
||||
size,
|
||||
mut read_waker,
|
||||
mut write_waker,
|
||||
mut flush_waker,
|
||||
reader_dropped,
|
||||
writer_dropped,
|
||||
// irrelevant if correctly dropped
|
||||
@@ -37,11 +38,11 @@ pub fn pipe(size: usize) -> (Writer, Reader) {
|
||||
state: _,
|
||||
} = *unsafe { Box::from_raw(val as *mut AsyncRingbuffer) };
|
||||
if !writer_dropped || !reader_dropped {
|
||||
eprintln!("Pipe dropped in err before reader or writer");
|
||||
abort()
|
||||
}
|
||||
read_waker.drop();
|
||||
write_waker.drop();
|
||||
flush_waker.drop();
|
||||
unsafe { dealloc(start, pipe_layout(size)) }
|
||||
}
|
||||
let state = Box::into_raw(Box::new(AsyncRingbuffer {
|
||||
@@ -52,6 +53,7 @@ pub fn pipe(size: usize) -> (Writer, Reader) {
|
||||
write_idx: 0,
|
||||
read_waker: Trigger::empty(),
|
||||
write_waker: Trigger::empty(),
|
||||
flush_waker: Trigger::empty(),
|
||||
reader_dropped: false,
|
||||
writer_dropped: false,
|
||||
drop,
|
||||
@@ -108,18 +110,21 @@ struct AsyncRingbuffer {
|
||||
write_idx: usize,
|
||||
read_waker: Trigger,
|
||||
write_waker: Trigger,
|
||||
flush_waker: Trigger,
|
||||
reader_dropped: bool,
|
||||
writer_dropped: bool,
|
||||
drop: extern "C" fn(*const ()),
|
||||
}
|
||||
impl AsyncRingbuffer {
|
||||
fn drop_writer(&mut self) {
|
||||
self.read_waker.invoke();
|
||||
self.writer_dropped = true;
|
||||
if self.reader_dropped {
|
||||
(self.drop)(self.state)
|
||||
}
|
||||
}
|
||||
fn drop_reader(&mut self) {
|
||||
self.write_waker.invoke();
|
||||
self.reader_dropped = true;
|
||||
if self.writer_dropped {
|
||||
(self.drop)(self.state)
|
||||
@@ -134,6 +139,15 @@ impl AsyncRingbuffer {
|
||||
self.write_waker = Trigger::new(waker.clone());
|
||||
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>> {
|
||||
if self.writer_dropped {
|
||||
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_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 {
|
||||
@@ -166,6 +210,12 @@ fn broken_pipe_error() -> io::Error {
|
||||
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
|
||||
/// [pipe].
|
||||
#[repr(C)]
|
||||
@@ -177,6 +227,17 @@ impl Writer {
|
||||
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 {
|
||||
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<()>> {
|
||||
unsafe {
|
||||
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(
|
||||
@@ -204,33 +265,13 @@ impl AsyncWrite for Writer {
|
||||
) -> Poll<io::Result<usize>> {
|
||||
unsafe {
|
||||
let data = self.as_mut().get_state()?;
|
||||
let AsyncRingbuffer { write_idx, read_idx, size, .. } = *data;
|
||||
if !buf.is_empty() && data.is_empty() {
|
||||
data.read_waker.invoke();
|
||||
}
|
||||
if !buf.is_empty() && data.is_full() {
|
||||
// Writer is blocked
|
||||
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 {
|
||||
let (end, start) = buf.split_at(size - write_idx);
|
||||
// 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))
|
||||
Poll::Ready(Ok(data.wrapping_write_unchecked(buf)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,7 +302,7 @@ impl AsyncRead for Reader {
|
||||
if !buf.is_empty() && data.is_full() {
|
||||
data.write_waker.invoke();
|
||||
}
|
||||
if !buf.is_empty() && data.is_empty() {
|
||||
let poll = if !buf.is_empty() && data.is_empty() {
|
||||
// Nothing to read, waiting...
|
||||
data.reader_wait(cx.waker())
|
||||
} else if read_idx < write_idx {
|
||||
@@ -280,7 +321,11 @@ impl AsyncRead for Reader {
|
||||
let start_count = start.len().min(write_idx);
|
||||
data.non_wrapping_read_unchecked(&mut start[0..start_count]);
|
||||
Poll::Ready(Ok(end.len() + start_count))
|
||||
};
|
||||
if !buf.is_empty() && data.is_empty() {
|
||||
data.flush_waker.invoke();
|
||||
}
|
||||
poll
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user