Traditional route appears to work
Beginnings of dylib extensions, entirely untestted
This commit is contained in:
@@ -8,22 +8,28 @@ edition = "2024"
|
||||
[dependencies]
|
||||
async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" }
|
||||
async-once-cell = "0.5.4"
|
||||
async-process = "2.4.0"
|
||||
bound = "0.6.0"
|
||||
derive_destructure = "1.0.0"
|
||||
futures = { version = "0.3.31", features = ["std"], default-features = false }
|
||||
futures-locks = "0.7.1"
|
||||
hashbrown = "0.16.0"
|
||||
hashbrown = "0.16.1"
|
||||
itertools = "0.14.0"
|
||||
lazy_static = "1.5.0"
|
||||
libloading = { version = "0.9.0", optional = true }
|
||||
memo-map = "0.3.3"
|
||||
never = "0.1.0"
|
||||
num-traits = "0.2.19"
|
||||
orchid-api = { version = "0.1.0", path = "../orchid-api" }
|
||||
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
|
||||
orchid-base = { version = "0.1.0", path = "../orchid-base" }
|
||||
ordered-float = "5.0.0"
|
||||
pastey = "0.1.1"
|
||||
ordered-float = "5.1.0"
|
||||
pastey = "0.2.1"
|
||||
substack = "1.1.1"
|
||||
test_executors = "0.3.5"
|
||||
test_executors = "0.4.1"
|
||||
tokio = { version = "1.49.0", features = ["process"], optional = true }
|
||||
tokio-util = { version = "0.7.18", features = ["compat"], optional = true }
|
||||
trait-set = "0.3.0"
|
||||
unsync-pipe = { version = "0.2.0", path = "../unsync-pipe" }
|
||||
|
||||
[features]
|
||||
tokio = ["dep:tokio", "dep:tokio-util", "dep:libloading"]
|
||||
|
||||
@@ -6,10 +6,10 @@ use std::{fmt, ops};
|
||||
use futures::future::LocalBoxFuture;
|
||||
use futures_locks::RwLock;
|
||||
use hashbrown::HashMap;
|
||||
use orchid_base::logging::Logger;
|
||||
|
||||
use crate::api;
|
||||
use crate::expr_store::ExprStore;
|
||||
use crate::logger::LoggerImpl;
|
||||
use crate::system::{System, WeakSystem};
|
||||
use crate::tree::WeakRoot;
|
||||
|
||||
@@ -24,11 +24,11 @@ pub trait Spawner {
|
||||
|
||||
pub struct CtxData {
|
||||
spawner: Rc<dyn Spawner>,
|
||||
pub msg_logs: Logger,
|
||||
pub systems: RwLock<HashMap<api::SysId, WeakSystem>>,
|
||||
pub system_id: RefCell<NonZeroU16>,
|
||||
pub exprs: ExprStore,
|
||||
pub root: RwLock<WeakRoot>,
|
||||
pub logger: LoggerImpl,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct Ctx(Rc<CtxData>);
|
||||
@@ -46,14 +46,14 @@ impl WeakCtx {
|
||||
}
|
||||
impl Ctx {
|
||||
#[must_use]
|
||||
pub fn new(msg_logs: Logger, spawner: impl Spawner + 'static) -> Self {
|
||||
pub fn new(spawner: impl Spawner + 'static, logger: LoggerImpl) -> Self {
|
||||
Self(Rc::new(CtxData {
|
||||
msg_logs,
|
||||
spawner: Rc::new(spawner),
|
||||
systems: RwLock::default(),
|
||||
system_id: RefCell::new(NonZero::new(1).unwrap()),
|
||||
exprs: ExprStore::default(),
|
||||
root: RwLock::default(),
|
||||
logger,
|
||||
}))
|
||||
}
|
||||
/// Spawn a parallel future that you can join at any later time.
|
||||
|
||||
55
orchid-host/src/dylib.rs
Normal file
55
orchid-host/src/dylib.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use libloading::Library;
|
||||
use orchid_base::binary::vt_to_future;
|
||||
|
||||
use crate::api;
|
||||
use crate::ctx::Ctx;
|
||||
use crate::extension::ExtPort;
|
||||
|
||||
static DYNAMIC_LIBRARIES: Mutex<Option<HashMap<PathBuf, Arc<Library>>>> = Mutex::new(None);
|
||||
fn load_dylib(path: &Path) -> Result<Arc<Library>, libloading::Error> {
|
||||
let mut g = DYNAMIC_LIBRARIES.lock().unwrap();
|
||||
let map = g.get_or_insert_default();
|
||||
if let Some(lib) = map.get(path) {
|
||||
Ok(lib.clone())
|
||||
} else {
|
||||
let lib = Arc::new(unsafe { Library::new(path) }?);
|
||||
map.insert(path.to_owned(), lib.clone());
|
||||
Ok(lib)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tokio")]
|
||||
pub async fn ext_dylib(path: &Path, ctx: Ctx) -> Result<ExtPort, libloading::Error> {
|
||||
use futures::io::BufReader;
|
||||
use futures::{AsyncBufReadExt, StreamExt};
|
||||
use libloading::Symbol;
|
||||
use unsync_pipe::pipe;
|
||||
|
||||
let (write_input, input) = pipe(1024);
|
||||
let (output, read_output) = pipe(1024);
|
||||
let (log, read_log) = pipe(1024);
|
||||
let log_path = path.to_string_lossy().to_string();
|
||||
let _ = ctx.spawn(async move {
|
||||
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;
|
||||
}
|
||||
});
|
||||
let library = load_dylib(path)?;
|
||||
let entrypoint: Symbol<unsafe extern "C" fn(api::binary::ExtensionContext)> =
|
||||
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) {
|
||||
let _ = unsafe { (data as *mut Ctx).as_mut().unwrap().spawn(vt_to_future(vt)) };
|
||||
}
|
||||
let spawner = api::binary::Spawner { 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) })
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use futures_locks::{RwLockWriteGuard, TryLockError};
|
||||
use orchid_base::error::OrcErrv;
|
||||
use orchid_base::format::fmt;
|
||||
use orchid_base::location::Pos;
|
||||
use orchid_base::logging::logger;
|
||||
use orchid_base::logging::log;
|
||||
|
||||
use crate::expr::{Expr, ExprKind, PathSet, Step};
|
||||
use crate::tree::Root;
|
||||
@@ -86,7 +86,7 @@ impl ExecCtx {
|
||||
while self.use_gas(1) {
|
||||
let mut kind_swap = ExprKind::Missing;
|
||||
mem::swap(&mut kind_swap, &mut self.cur);
|
||||
writeln!(logger(), "Exxecute lvl{} {}", self.stack.len(), fmt(&kind_swap).await);
|
||||
writeln!(log("debug"), "Exxecute lvl{} {}", self.stack.len(), fmt(&kind_swap).await).await;
|
||||
let (kind, op) = match kind_swap {
|
||||
ExprKind::Identity(target) => {
|
||||
let inner = self.unpack_ident(&target).await;
|
||||
|
||||
@@ -14,11 +14,10 @@ use futures::{AsyncRead, AsyncWrite, AsyncWriteExt, SinkExt, StreamExt};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use itertools::Itertools;
|
||||
use orchid_api_traits::{Decode, Encode, Request};
|
||||
use orchid_base::clone;
|
||||
use orchid_base::format::{FmtCtxImpl, Format};
|
||||
use orchid_base::interner::{IStr, IStrv, es, ev, is, iv};
|
||||
use orchid_base::location::Pos;
|
||||
use orchid_base::logging::logger;
|
||||
use orchid_base::logging::log;
|
||||
use orchid_base::name::Sym;
|
||||
use orchid_base::reqnot::{Client, ClientExt, MsgReaderExt, ReqHandleExt, ReqReaderExt, io_comm};
|
||||
use orchid_base::stash::{stash, with_stash};
|
||||
@@ -70,199 +69,213 @@ impl Drop for ExtensionData {
|
||||
pub struct Extension(Rc<ExtensionData>);
|
||||
impl Extension {
|
||||
pub async fn new(mut init: ExtPort, ctx: Ctx) -> io::Result<Self> {
|
||||
api::HostHeader { log_strategy: logger().strat(), msg_logs: ctx.msg_logs.strat() }
|
||||
.encode(init.input.as_mut())
|
||||
.await
|
||||
.unwrap();
|
||||
api::HostHeader { logger: ctx.logger.to_api() }.encode(init.input.as_mut()).await.unwrap();
|
||||
init.input.flush().await.unwrap();
|
||||
|
||||
let header = api::ExtensionHeader::decode(init.output.as_mut()).await.unwrap();
|
||||
let header2 = header.clone();
|
||||
Ok(Self(Rc::new_cyclic(|weak: &Weak<ExtensionData>| {
|
||||
// context not needed because exit is extension-initiated
|
||||
let (client, _, future) = io_comm(
|
||||
Rc::new(Mutex::new(init.input)),
|
||||
Mutex::new(init.output),
|
||||
clone!(weak; async move |reader| {
|
||||
with_stash(async {
|
||||
let this = Extension(weak.upgrade().unwrap());
|
||||
let notif = reader.read::<api::ExtHostNotif>().await.unwrap();
|
||||
if !matches!(notif, api::ExtHostNotif::Log(_)) {
|
||||
writeln!(logger(), "Host received notif {notif:?}");
|
||||
}
|
||||
match notif {
|
||||
api::ExtHostNotif::ExprNotif(api::ExprNotif::Acquire(acq)) => {
|
||||
let target = this.0.ctx.exprs.get_expr(acq.1).expect("Invalid ticket");
|
||||
this.0.ctx.exprs.give_expr(target)
|
||||
}
|
||||
api::ExtHostNotif::ExprNotif(api::ExprNotif::Release(rel)) => {
|
||||
if this.is_own_sys(rel.0).await {
|
||||
this.0.ctx.exprs.take_expr(rel.1);
|
||||
} else {
|
||||
writeln!(this.0.ctx.msg_logs, "Not our system {:?}", rel.0)
|
||||
let (client, _, comm) = io_comm(Rc::new(Mutex::new(init.input)), Mutex::new(init.output));
|
||||
let weak2 = weak;
|
||||
let weak = weak.clone();
|
||||
let ctx2 = ctx.clone();
|
||||
let join_ext = ctx.clone().spawn(async move {
|
||||
comm
|
||||
.listen(
|
||||
async |reader| {
|
||||
with_stash(async {
|
||||
let this = Extension(weak.upgrade().unwrap());
|
||||
let notif = reader.read::<api::ExtHostNotif>().await.unwrap();
|
||||
// logging is never logged because its value will be logged anyway
|
||||
if !matches!(notif, api::ExtHostNotif::Log(_)) {
|
||||
writeln!(log("msg"), "Host received notif {notif:?}").await;
|
||||
}
|
||||
},
|
||||
api::ExtHostNotif::Log(api::Log(str)) => logger().log(str),
|
||||
api::ExtHostNotif::Sweeped(data) => {
|
||||
for i in join_all(data.strings.into_iter().map(es)).await {
|
||||
this.0.strings.borrow_mut().remove(&i);
|
||||
}
|
||||
for i in join_all(data.vecs.into_iter().map(ev)).await {
|
||||
this.0.string_vecs.borrow_mut().remove(&i);
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}).await
|
||||
}),
|
||||
{
|
||||
clone!(weak, ctx);
|
||||
async move |mut reader| {
|
||||
with_stash(async {
|
||||
let this = Self(weak.upgrade().unwrap());
|
||||
let req = reader.read_req::<api::ExtHostReq>().await.unwrap();
|
||||
let handle = reader.finish().await;
|
||||
if !matches!(req, api::ExtHostReq::ExtAtomPrint(_)) {
|
||||
writeln!(logger(), "Host received request {req:?}");
|
||||
}
|
||||
match req {
|
||||
api::ExtHostReq::Ping(ping) => handle.reply(&ping, &()).await,
|
||||
api::ExtHostReq::IntReq(intreq) => match intreq {
|
||||
api::IntReq::InternStr(s) => {
|
||||
let i = is(&s.0).await;
|
||||
this.0.strings.borrow_mut().insert(i.clone());
|
||||
handle.reply(&s, &i.to_api()).await
|
||||
match notif {
|
||||
api::ExtHostNotif::ExprNotif(api::ExprNotif::Acquire(acq)) => {
|
||||
let target = this.0.ctx.exprs.get_expr(acq.1).expect("Invalid ticket");
|
||||
this.0.ctx.exprs.give_expr(target)
|
||||
},
|
||||
api::IntReq::InternStrv(v) => {
|
||||
let tokens = join_all(v.0.iter().map(|m| es(*m))).await;
|
||||
this.0.strings.borrow_mut().extend(tokens.iter().cloned());
|
||||
let i = iv(&tokens).await;
|
||||
this.0.string_vecs.borrow_mut().insert(i.clone());
|
||||
handle.reply(&v, &i.to_api()).await
|
||||
},
|
||||
api::IntReq::ExternStr(si) => {
|
||||
let i = es(si.0).await;
|
||||
this.0.strings.borrow_mut().insert(i.clone());
|
||||
handle.reply(&si, &i.to_string()).await
|
||||
},
|
||||
api::IntReq::ExternStrv(vi) => {
|
||||
let i = ev(vi.0).await;
|
||||
this.0.strings.borrow_mut().extend(i.iter().cloned());
|
||||
this.0.string_vecs.borrow_mut().insert(i.clone());
|
||||
let markerv = i.iter().map(|t| t.to_api()).collect_vec();
|
||||
handle.reply(&vi, &markerv).await
|
||||
},
|
||||
},
|
||||
api::ExtHostReq::Fwd(ref fw @ api::Fwd(ref atom, ref key, ref body)) => {
|
||||
let sys = ctx.system_inst(atom.owner).await.expect("owner of live atom dropped");
|
||||
let client = sys.client();
|
||||
let reply =
|
||||
client.request(api::Fwded(fw.0.clone(), *key, body.clone())).await.unwrap();
|
||||
handle.reply(fw, &reply).await
|
||||
},
|
||||
api::ExtHostReq::SysFwd(ref fw @ api::SysFwd(id, ref body)) => {
|
||||
let sys = ctx.system_inst(id).await.unwrap();
|
||||
handle.reply(fw, &sys.request(body.clone()).await).await
|
||||
},
|
||||
api::ExtHostReq::SubLex(sl) => {
|
||||
let (rep_in, mut rep_out) = channel(0);
|
||||
{
|
||||
let lex_g = this.0.lex_recur.lock().await;
|
||||
let mut req_in =
|
||||
lex_g.get(&sl.id).cloned().expect("Sublex for nonexistent lexid");
|
||||
req_in.send(ReqPair(sl.clone(), rep_in)).await.unwrap();
|
||||
}
|
||||
handle.reply(&sl, &rep_out.next().await.unwrap()).await
|
||||
},
|
||||
api::ExtHostReq::ExprReq(expr_req) => match expr_req {
|
||||
api::ExprReq::Inspect(ins @ api::Inspect { target }) => {
|
||||
let expr = ctx.exprs.get_expr(target).expect("Invalid ticket");
|
||||
handle
|
||||
.reply(&ins, &api::Inspected {
|
||||
refcount: expr.strong_count() as u32,
|
||||
location: expr.pos().to_api(),
|
||||
kind: expr.to_api().await,
|
||||
})
|
||||
.await
|
||||
},
|
||||
api::ExprReq::Create(ref cre @ api::Create(ref expr)) => {
|
||||
let expr = Expr::from_api(expr, PathSetBuilder::new(), ctx.clone()).await;
|
||||
let expr_id = expr.id();
|
||||
ctx.exprs.give_expr(expr);
|
||||
handle.reply(cre, &expr_id).await
|
||||
},
|
||||
},
|
||||
api::ExtHostReq::LsModule(ref ls @ api::LsModule(_sys, path)) => {
|
||||
let reply: <api::LsModule as Request>::Response = 'reply: {
|
||||
let path = ev(path).await;
|
||||
let root = (ctx.root.read().await.upgrade())
|
||||
.expect("LSModule called when root isn't in context");
|
||||
let root_data = &*root.0.read().await;
|
||||
let mut walk_ctx = (ctx.clone(), &root_data.consts);
|
||||
let module =
|
||||
match walk(&root_data.root, false, path.iter().cloned(), &mut walk_ctx).await
|
||||
{
|
||||
Ok(module) => module,
|
||||
Err(ChildError { kind, .. }) =>
|
||||
break 'reply Err(match kind {
|
||||
ChildErrorKind::Private => panic!("Access checking was disabled"),
|
||||
ChildErrorKind::Constant => api::LsModuleError::IsConstant,
|
||||
ChildErrorKind::Missing => api::LsModuleError::InvalidPath,
|
||||
}),
|
||||
};
|
||||
let mut members = std::collections::HashMap::new();
|
||||
for (k, v) in &module.members {
|
||||
let kind = match v.kind(ctx.clone(), &root_data.consts).await {
|
||||
MemberKind::Const => api::MemberInfoKind::Constant,
|
||||
MemberKind::Module(_) => api::MemberInfoKind::Module,
|
||||
};
|
||||
members.insert(k.to_api(), api::MemberInfo { public: v.public, kind });
|
||||
api::ExtHostNotif::ExprNotif(api::ExprNotif::Release(rel)) => {
|
||||
if this.is_own_sys(rel.0).await {
|
||||
this.0.ctx.exprs.take_expr(rel.1);
|
||||
} else {
|
||||
writeln!(log("warn"), "Not our system {:?}", rel.0).await
|
||||
}
|
||||
Ok(api::ModuleInfo { members })
|
||||
};
|
||||
handle.reply(ls, &reply).await
|
||||
},
|
||||
api::ExtHostReq::ResolveNames(ref rn) => {
|
||||
let api::ResolveNames { constid, names, sys } = rn;
|
||||
let mut resolver = {
|
||||
let systems = ctx.systems.read().await;
|
||||
let weak_sys = systems.get(sys).expect("ResolveNames for invalid sys");
|
||||
let sys = weak_sys.upgrade().expect("ResolveNames after sys drop");
|
||||
sys.name_resolver(*constid).await
|
||||
};
|
||||
let responses = stream(async |mut cx| {
|
||||
for name in names {
|
||||
cx.emit(match resolver(&ev(*name).await[..]).await {
|
||||
Ok(abs) => Ok(abs.to_sym().await.to_api()),
|
||||
Err(e) => Err(e.to_api()),
|
||||
})
|
||||
.await
|
||||
},
|
||||
api::ExtHostNotif::Log(api::Log { category, message }) =>
|
||||
write!(log(&es(category).await), "{message}").await,
|
||||
api::ExtHostNotif::Sweeped(data) => {
|
||||
for i in join_all(data.strings.into_iter().map(es)).await {
|
||||
this.0.strings.borrow_mut().remove(&i);
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
.await;
|
||||
handle.reply(rn, &responses).await
|
||||
},
|
||||
api::ExtHostReq::ExtAtomPrint(ref eap @ api::ExtAtomPrint(ref atom)) => {
|
||||
let atom = AtomHand::from_api(atom, Pos::None, &mut ctx.clone()).await;
|
||||
let unit = atom.print(&FmtCtxImpl::default()).await;
|
||||
handle.reply(eap, &unit.to_api()).await
|
||||
},
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let join_ext = ctx.spawn(async {
|
||||
future.await.unwrap();
|
||||
// extension exited successfully
|
||||
for i in join_all(data.vecs.into_iter().map(ev)).await {
|
||||
this.0.string_vecs.borrow_mut().remove(&i);
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
},
|
||||
async |mut reader| {
|
||||
with_stash(async {
|
||||
let req = reader.read_req::<api::ExtHostReq>().await.unwrap();
|
||||
let handle = reader.finish().await;
|
||||
// Atom printing and interning is never reported because it generates too much
|
||||
// noise
|
||||
if !matches!(req, api::ExtHostReq::ExtAtomPrint(_))
|
||||
|| matches!(req, api::ExtHostReq::IntReq(_))
|
||||
{
|
||||
writeln!(log("msg"), "Host received request {req:?}").await;
|
||||
}
|
||||
let this = Self(weak.upgrade().unwrap());
|
||||
match req {
|
||||
api::ExtHostReq::Ping(ping) => handle.reply(&ping, &()).await,
|
||||
api::ExtHostReq::IntReq(intreq) => match intreq {
|
||||
api::IntReq::InternStr(s) => {
|
||||
let i = is(&s.0).await;
|
||||
this.0.strings.borrow_mut().insert(i.clone());
|
||||
handle.reply(&s, &i.to_api()).await
|
||||
},
|
||||
api::IntReq::InternStrv(v) => {
|
||||
let tokens = join_all(v.0.iter().map(|m| es(*m))).await;
|
||||
this.0.strings.borrow_mut().extend(tokens.iter().cloned());
|
||||
let i = iv(&tokens).await;
|
||||
this.0.string_vecs.borrow_mut().insert(i.clone());
|
||||
handle.reply(&v, &i.to_api()).await
|
||||
},
|
||||
api::IntReq::ExternStr(si) => {
|
||||
let i = es(si.0).await;
|
||||
this.0.strings.borrow_mut().insert(i.clone());
|
||||
handle.reply(&si, &i.to_string()).await
|
||||
},
|
||||
api::IntReq::ExternStrv(vi) => {
|
||||
let i = ev(vi.0).await;
|
||||
this.0.strings.borrow_mut().extend(i.iter().cloned());
|
||||
this.0.string_vecs.borrow_mut().insert(i.clone());
|
||||
let markerv = i.iter().map(|t| t.to_api()).collect_vec();
|
||||
handle.reply(&vi, &markerv).await
|
||||
},
|
||||
},
|
||||
api::ExtHostReq::Fwd(ref fw @ api::Fwd(ref atom, ref key, ref body)) => {
|
||||
let sys =
|
||||
ctx.system_inst(atom.owner).await.expect("owner of live atom dropped");
|
||||
let client = sys.client();
|
||||
let reply =
|
||||
client.request(api::Fwded(fw.0.clone(), *key, body.clone())).await.unwrap();
|
||||
handle.reply(fw, &reply).await
|
||||
},
|
||||
api::ExtHostReq::SysFwd(ref fw @ api::SysFwd(id, ref body)) => {
|
||||
let sys = ctx.system_inst(id).await.unwrap();
|
||||
handle.reply(fw, &sys.request(body.clone()).await).await
|
||||
},
|
||||
api::ExtHostReq::SubLex(sl) => {
|
||||
let (rep_in, mut rep_out) = channel(0);
|
||||
{
|
||||
let lex_g = this.0.lex_recur.lock().await;
|
||||
let mut req_in =
|
||||
lex_g.get(&sl.id).cloned().expect("Sublex for nonexistent lexid");
|
||||
req_in.send(ReqPair(sl.clone(), rep_in)).await.unwrap();
|
||||
}
|
||||
handle.reply(&sl, &rep_out.next().await.unwrap()).await
|
||||
},
|
||||
api::ExtHostReq::ExprReq(expr_req) => match expr_req {
|
||||
api::ExprReq::Inspect(ins @ api::Inspect { target }) => {
|
||||
let expr = ctx.exprs.get_expr(target).expect("Invalid ticket");
|
||||
handle
|
||||
.reply(&ins, &api::Inspected {
|
||||
refcount: expr.strong_count() as u32,
|
||||
location: expr.pos().to_api(),
|
||||
kind: expr.to_api().await,
|
||||
})
|
||||
.await
|
||||
},
|
||||
api::ExprReq::Create(ref cre @ api::Create(ref expr)) => {
|
||||
let expr = Expr::from_api(expr, PathSetBuilder::new(), ctx.clone()).await;
|
||||
let expr_id = expr.id();
|
||||
ctx.exprs.give_expr(expr);
|
||||
handle.reply(cre, &expr_id).await
|
||||
},
|
||||
},
|
||||
api::ExtHostReq::LsModule(ref ls @ api::LsModule(_sys, path)) => {
|
||||
let reply: <api::LsModule as Request>::Response = 'reply: {
|
||||
let path = ev(path).await;
|
||||
let root = (ctx.root.read().await.upgrade())
|
||||
.expect("LSModule called when root isn't in context");
|
||||
let root_data = &*root.0.read().await;
|
||||
let mut walk_ctx = (ctx.clone(), &root_data.consts);
|
||||
let module =
|
||||
match walk(&root_data.root, false, path.iter().cloned(), &mut walk_ctx)
|
||||
.await
|
||||
{
|
||||
Ok(module) => module,
|
||||
Err(ChildError { kind, .. }) =>
|
||||
break 'reply Err(match kind {
|
||||
ChildErrorKind::Private => panic!("Access checking was disabled"),
|
||||
ChildErrorKind::Constant => api::LsModuleError::IsConstant,
|
||||
ChildErrorKind::Missing => api::LsModuleError::InvalidPath,
|
||||
}),
|
||||
};
|
||||
let mut members = std::collections::HashMap::new();
|
||||
for (k, v) in &module.members {
|
||||
let kind = match v.kind(ctx.clone(), &root_data.consts).await {
|
||||
MemberKind::Const => api::MemberInfoKind::Constant,
|
||||
MemberKind::Module(_) => api::MemberInfoKind::Module,
|
||||
};
|
||||
members.insert(k.to_api(), api::MemberInfo { public: v.public, kind });
|
||||
}
|
||||
Ok(api::ModuleInfo { members })
|
||||
};
|
||||
handle.reply(ls, &reply).await
|
||||
},
|
||||
api::ExtHostReq::ResolveNames(ref rn) => {
|
||||
let api::ResolveNames { constid, names, sys } = rn;
|
||||
let mut resolver = {
|
||||
let systems = ctx.systems.read().await;
|
||||
let weak_sys = systems.get(sys).expect("ResolveNames for invalid sys");
|
||||
let sys = weak_sys.upgrade().expect("ResolveNames after sys drop");
|
||||
sys.name_resolver(*constid).await
|
||||
};
|
||||
let responses = stream(async |mut cx| {
|
||||
for name in names {
|
||||
cx.emit(match resolver(&ev(*name).await[..]).await {
|
||||
Ok(abs) => {
|
||||
let sym = abs.to_sym().await;
|
||||
this.0.string_vecs.borrow_mut().insert(sym.tok());
|
||||
Ok(sym.to_api())
|
||||
},
|
||||
Err(e) => {
|
||||
(this.0.strings.borrow_mut())
|
||||
.extend(e.iter().map(|e| e.description.clone()));
|
||||
Err(e.to_api())
|
||||
},
|
||||
})
|
||||
.await
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
.await;
|
||||
handle.reply(rn, &responses).await
|
||||
},
|
||||
api::ExtHostReq::ExtAtomPrint(ref eap @ api::ExtAtomPrint(ref atom)) => {
|
||||
let atom = AtomHand::from_api(atom, Pos::None, &mut ctx.clone()).await;
|
||||
let unit = atom.print(&FmtCtxImpl::default()).await;
|
||||
handle.reply(eap, &unit.to_api()).await
|
||||
},
|
||||
}
|
||||
})
|
||||
.await
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
ExtensionData {
|
||||
name: header.name.clone(),
|
||||
ctx: ctx.clone(),
|
||||
name: header2.name.clone(),
|
||||
ctx: ctx2,
|
||||
systems: (header.systems.iter().cloned())
|
||||
.map(|decl| SystemCtor { decl, ext: WeakExtension(weak.clone()) })
|
||||
.map(|decl| SystemCtor { decl, ext: WeakExtension(weak2.clone()) })
|
||||
.collect(),
|
||||
join_ext: Some(join_ext),
|
||||
next_pars: RefCell::new(NonZeroU64::new(1).unwrap()),
|
||||
@@ -282,7 +295,7 @@ impl Extension {
|
||||
#[must_use]
|
||||
pub async fn is_own_sys(&self, id: api::SysId) -> bool {
|
||||
let Some(sys) = self.ctx().system_inst(id).await else {
|
||||
writeln!(logger(), "Invalid system ID {id:?}");
|
||||
writeln!(log("warn"), "Invalid system ID {id:?}").await;
|
||||
return false;
|
||||
};
|
||||
Rc::ptr_eq(&self.0, &sys.ext().0)
|
||||
|
||||
@@ -3,11 +3,13 @@ use orchid_api as api;
|
||||
pub mod atom;
|
||||
pub mod ctx;
|
||||
pub mod dealias;
|
||||
pub mod dylib;
|
||||
pub mod execute;
|
||||
pub mod expr;
|
||||
pub mod expr_store;
|
||||
pub mod extension;
|
||||
pub mod lex;
|
||||
pub mod logger;
|
||||
pub mod parse;
|
||||
pub mod parsed;
|
||||
pub mod subprocess;
|
||||
|
||||
84
orchid-host/src/logger.rs
Normal file
84
orchid-host/src/logger.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use std::fmt::Arguments;
|
||||
use std::fs::File;
|
||||
use std::io::{Write, stderr};
|
||||
use std::rc::Rc;
|
||||
|
||||
use futures::future::LocalBoxFuture;
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
use orchid_base::logging::{LogWriter, Logger};
|
||||
|
||||
use crate::api;
|
||||
|
||||
pub struct LogWriterImpl(api::LogStrategy);
|
||||
impl LogWriter for LogWriterImpl {
|
||||
fn write_fmt<'a>(&'a self, fmt: Arguments<'a>) -> LocalBoxFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
match &self.0 {
|
||||
api::LogStrategy::Discard => (),
|
||||
api::LogStrategy::Default => {
|
||||
stderr().write_fmt(fmt).expect("Could not write to stderr!");
|
||||
stderr().flush().expect("Could not flush stderr")
|
||||
},
|
||||
api::LogStrategy::File { path, .. } => {
|
||||
let mut file = (File::options().write(true).create(true).truncate(false).open(path))
|
||||
.unwrap_or_else(|e| panic!("Could not open {path}: {e}"));
|
||||
file.write_fmt(fmt).unwrap_or_else(|e| panic!("Could not write to {path}: {e}"));
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct LoggerImpl {
|
||||
routing: HashMap<String, api::LogStrategy>,
|
||||
default: Option<api::LogStrategy>,
|
||||
}
|
||||
impl LoggerImpl {
|
||||
pub fn to_api(&self) -> api::Logger {
|
||||
api::Logger {
|
||||
default: self.default.clone(),
|
||||
routing: self.routing.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
default: Option<api::LogStrategy>,
|
||||
strats: impl IntoIterator<Item = (String, api::LogStrategy)>,
|
||||
) -> Self {
|
||||
Self { routing: strats.into_iter().collect(), default }
|
||||
}
|
||||
pub fn set_default(&mut self, strat: api::LogStrategy) { self.default = Some(strat) }
|
||||
pub fn clear_default(&mut self) { self.default = None }
|
||||
pub fn set_category(&mut self, category: &str, strat: api::LogStrategy) {
|
||||
self.routing.insert(category.to_string(), strat);
|
||||
}
|
||||
pub fn with_default(mut self, strat: api::LogStrategy) -> Self {
|
||||
self.set_default(strat);
|
||||
self
|
||||
}
|
||||
pub fn with_category(mut self, category: &str, strat: api::LogStrategy) -> Self {
|
||||
self.set_category(category, strat);
|
||||
self
|
||||
}
|
||||
pub async fn log(&self, category: &str, msg: impl AsRef<str>) {
|
||||
writeln!(self.writer(category), "{}", msg.as_ref()).await
|
||||
}
|
||||
pub fn has_category(&self, category: &str) -> bool { self.routing.contains_key(category) }
|
||||
pub async fn log_buf(&self, category: &str, event: impl AsRef<str>, buf: &[u8]) {
|
||||
if std::env::var("ORCHID_LOG_BUFFERS").is_ok_and(|v| !v.is_empty()) {
|
||||
let data = buf.iter().map(|b| format!("{b:02x}")).join(" ");
|
||||
writeln!(self.writer(category), "{}: [{data}]", event.as_ref()).await
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Logger for LoggerImpl {
|
||||
fn writer(&self, category: &str) -> Rc<dyn LogWriter> {
|
||||
Rc::new(LogWriterImpl(self.strat(category).clone()))
|
||||
}
|
||||
fn strat(&self, category: &str) -> api::LogStrategy {
|
||||
(self.routing.get(category).cloned().or(self.default.clone()))
|
||||
.expect("Invalid category and catchall logger not set")
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,34 @@
|
||||
use std::io;
|
||||
use std::{io, process};
|
||||
|
||||
use async_process;
|
||||
use futures::io::BufReader;
|
||||
use futures::{self, AsyncBufReadExt};
|
||||
use orchid_base::logging::logger;
|
||||
use futures::{self, AsyncBufReadExt, StreamExt};
|
||||
use orchid_base::logging::log;
|
||||
#[cfg(feature = "tokio")]
|
||||
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
use crate::extension::ExtPort;
|
||||
|
||||
#[cfg(feature = "tokio")]
|
||||
pub async fn ext_command(cmd: std::process::Command, ctx: Ctx) -> io::Result<ExtPort> {
|
||||
let mut child = async_process::Command::from(cmd)
|
||||
.stdin(async_process::Stdio::piped())
|
||||
.stdout(async_process::Stdio::piped())
|
||||
.stderr(async_process::Stdio::piped())
|
||||
let name = cmd.get_program().to_string_lossy().to_string();
|
||||
let mut child = tokio::process::Command::from(cmd)
|
||||
.stdin(process::Stdio::piped())
|
||||
.stdout(process::Stdio::piped())
|
||||
.stderr(process::Stdio::piped())
|
||||
.spawn()?;
|
||||
std::thread::spawn(|| {});
|
||||
let stdin = child.stdin.take().unwrap();
|
||||
let stdout = child.stdout.take().unwrap();
|
||||
let mut child_stderr = child.stderr.take().unwrap();
|
||||
let child_stderr = child.stderr.take().unwrap();
|
||||
let _ = ctx.spawn(Box::pin(async move {
|
||||
let _ = child;
|
||||
let mut reader = BufReader::new(&mut child_stderr);
|
||||
loop {
|
||||
let mut buf = String::new();
|
||||
if 0 == reader.read_line(&mut buf).await.unwrap() {
|
||||
break;
|
||||
}
|
||||
logger().log(buf.strip_suffix('\n').expect("Readline implies this"));
|
||||
let mut lines = BufReader::new(child_stderr.compat()).lines();
|
||||
while let Some(line) = lines.next().await {
|
||||
// route stderr with an empty category string. This is not the intended logging
|
||||
// method
|
||||
writeln!(log("stderr"), "{} err> {}", name, line.expect("Readline implies this")).await;
|
||||
}
|
||||
}));
|
||||
Ok(ExtPort { input: Box::pin(stdin), output: Box::pin(stdout) })
|
||||
Ok(ExtPort { input: Box::pin(stdin.compat_write()), output: Box::pin(stdout.compat()) })
|
||||
}
|
||||
|
||||
@@ -14,9 +14,10 @@ use orchid_base::error::{OrcRes, mk_errv_floating};
|
||||
use orchid_base::format::{FmtCtx, FmtUnit, Format};
|
||||
use orchid_base::interner::{IStr, es, is};
|
||||
use orchid_base::iter_utils::IteratorPrint;
|
||||
use orchid_base::logging::logger;
|
||||
use orchid_base::logging::log;
|
||||
use orchid_base::name::{NameLike, Sym, VName, VPath};
|
||||
use orchid_base::reqnot::{Client, ClientExt};
|
||||
use orchid_base::stash::stash;
|
||||
use ordered_float::NotNan;
|
||||
use substack::{Stackframe, Substack};
|
||||
|
||||
@@ -86,7 +87,8 @@ impl System {
|
||||
#[must_use]
|
||||
pub fn can_lex(&self, c: char) -> bool {
|
||||
let ret = char_filter_match(&self.0.lex_filter, c);
|
||||
writeln!(logger(), "{} can lex {c}: {}", self.ctor().name(), ret);
|
||||
let ctor = self.ctor();
|
||||
stash(async move { writeln!(log("debug"), "{} can lex {c}: {}", ctor.name(), ret).await });
|
||||
ret
|
||||
}
|
||||
#[must_use]
|
||||
|
||||
Reference in New Issue
Block a user