forked from Orchid/orchid
I got very confused and started mucking about with "spawn" when in fact all I needed was the "inline" extension type in orcx that allows the interpreter to expose custom constants.
88 lines
2.9 KiB
Rust
88 lines
2.9 KiB
Rust
use std::io;
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::{Arc, Mutex};
|
|
use std::time::Duration;
|
|
|
|
use futures::io::BufReader;
|
|
use futures::{AsyncBufReadExt, StreamExt};
|
|
use hashbrown::HashMap;
|
|
use libloading::{Library, Symbol};
|
|
use orchid_base::{log, on_drop, vt_to_future};
|
|
use unsync_pipe::pipe;
|
|
|
|
use crate::api;
|
|
use crate::ctx::Ctx;
|
|
use crate::extension::ExtPort;
|
|
use crate::task_set::TaskSet;
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
pub async fn ext_dylib(path: &Path, ctx: Ctx) -> Result<ExtPort, libloading::Error> {
|
|
let (write_input, input) = pipe(1024);
|
|
let (output, read_output) = pipe(1024);
|
|
let (write_log, read_log) = pipe(1024);
|
|
let log_path = path.to_string_lossy().to_string();
|
|
let _ = ctx.spawn(Duration::ZERO, async move {
|
|
let mut lines = BufReader::new(read_log).lines();
|
|
while let Some(line) = lines.next().await {
|
|
match line {
|
|
Ok(line) => writeln!(log("stderr"), "dylib {log_path} err> {line}").await,
|
|
Err(e) => match e.kind() {
|
|
io::ErrorKind::BrokenPipe | io::ErrorKind::UnexpectedEof => break,
|
|
_ => panic!("Error while reading stderr {e}"),
|
|
},
|
|
}
|
|
}
|
|
});
|
|
let tasks = TaskSet::default();
|
|
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(SpawnerState { ctx, tasks: tasks.clone() })) as *const ();
|
|
let spawner = api::binary::SpawnerBin { data, drop, spawn };
|
|
let cx = api::binary::ExtensionContext { input, output, log: write_log, spawner };
|
|
unsafe { (entrypoint)(cx) };
|
|
Ok(ExtPort {
|
|
input: Box::pin(write_input),
|
|
output: Box::pin(read_output),
|
|
drop_trigger: Box::new(on_drop(move || tasks.abort_all())),
|
|
})
|
|
}
|
|
|
|
struct SpawnerState {
|
|
ctx: Ctx,
|
|
tasks: TaskSet,
|
|
}
|
|
extern "C" fn drop(data: *const ()) {
|
|
let state = unsafe { Box::from_raw(data as *mut SpawnerState) };
|
|
state.tasks.abort_all();
|
|
}
|
|
extern "C" fn spawn(data: *const (), delay: u64, vt: api::binary::FutureBin) {
|
|
let future = vt_to_future(vt);
|
|
// SAFETY: this is technically a Box but it can be used directly as a &mut
|
|
let state = unsafe { (data as *mut SpawnerState).as_mut() }.unwrap();
|
|
state.tasks.with(|store| {
|
|
store.add_with(|id| {
|
|
state.ctx.spawn(Duration::from_millis(delay), async move {
|
|
future.await;
|
|
// SAFETY: We know this is live because the drop handle that frees it also
|
|
// aborts the future
|
|
let state = unsafe { (data as *mut SpawnerState).as_mut() }.unwrap();
|
|
state.tasks.with(|store| store.remove(id));
|
|
})
|
|
});
|
|
});
|
|
}
|