llam 0.1.0

Safe, Go-style Rust bindings for the LLAM runtime
use crate::error::{Error, Result};
use crate::sys;
use std::os::raw::c_void;
use std::panic::{self, AssertUnwindSafe};
use std::ptr;

struct BlockingEntry<F> {
    f: Option<F>,
}

pub fn call<F, T>(f: F) -> Result<std::thread::Result<T>>
where
    F: FnOnce() -> T + Send + 'static,
    T: Send + 'static,
{
    let entry = Box::new(BlockingEntry { f: Some(f) });
    let arg = Box::into_raw(entry) as *mut c_void;
    let mut out = ptr::null_mut();
    let rc = unsafe { sys::llam_call_blocking_result(trampoline::<F, T>, arg, &mut out) };
    if rc != 0 {
        unsafe {
            drop(Box::from_raw(arg as *mut BlockingEntry<F>));
        }
        return Err(Error::last());
    }
    if out.is_null() {
        return Err(Error::from_errno(libc::EIO));
    }
    Ok(unsafe { *Box::from_raw(out as *mut std::thread::Result<T>) })
}

unsafe extern "C" fn trampoline<F, T>(arg: *mut c_void) -> *mut c_void
where
    F: FnOnce() -> T + Send + 'static,
    T: Send + 'static,
{
    let mut entry = Box::from_raw(arg as *mut BlockingEntry<F>);
    let f = entry.f.take().expect("LLAM blocking closure missing");
    Box::into_raw(Box::new(panic::catch_unwind(AssertUnwindSafe(f)))) as *mut c_void
}

pub struct BlockingRegion {
    active: bool,
}

impl BlockingRegion {
    pub fn enter() -> Result<Self> {
        let rc = unsafe { sys::llam_enter_blocking() };
        if rc == 0 {
            Ok(Self { active: true })
        } else {
            Err(Error::last())
        }
    }
}

impl Drop for BlockingRegion {
    fn drop(&mut self) {
        if self.active {
            unsafe {
                let _ = sys::llam_leave_blocking();
            }
            self.active = false;
        }
    }
}