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>>> = Mutex::new(None); fn load_dylib(path: &Path) -> Result, 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 { 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 { 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)); }) }); }); }