use std::{
ffi::c_void,
mem,
panic::{catch_unwind, resume_unwind, AssertUnwindSafe},
ptr, thread,
};
use super::{
bindings as napi, debug_send_wrapper::DebugSendWrapper, no_panic::FailureBoundary, raw::Env,
};
const BOUNDARY: FailureBoundary = FailureBoundary {
both: "A panic and exception occurred while executing a `neon::event::TaskBuilder` task",
exception: "An exception occurred while executing a `neon::event::TaskBuilder` task",
panic: "A panic occurred while executing a `neon::event::TaskBuilder` task",
};
type Execute<I, O> = fn(input: I) -> O;
type Complete<O, D> = fn(env: Env, output: thread::Result<O>, data: D);
pub unsafe fn schedule<I, O, D>(
env: Env,
input: I,
execute: Execute<I, O>,
complete: Complete<O, D>,
data: D,
) where
I: Send + 'static,
O: Send + 'static,
D: 'static,
{
let mut data = Box::new(Data {
state: State::Input(input),
execute,
complete,
data: DebugSendWrapper::new(data),
work: ptr::null_mut(),
});
let work = &mut data.work as *mut _;
napi::create_async_work(
env,
ptr::null_mut(),
super::string(env, "neon_async_work"),
Some(call_execute::<I, O, D>),
Some(call_complete::<I, O, D>),
Box::into_raw(data).cast(),
work,
)
.unwrap();
match napi::queue_async_work(env, *work) {
Ok(()) => {}
status => {
let _ = napi::delete_async_work(env, *work);
status.unwrap()
}
}
}
struct Data<I, O, D> {
state: State<I, O>,
execute: Execute<I, O>,
complete: Complete<O, D>,
data: DebugSendWrapper<D>,
work: napi::AsyncWork,
}
enum State<I, O> {
Input(I),
Executing,
Output(thread::Result<O>),
}
impl<I, O> State<I, O> {
fn take_execute_input(&mut self) -> Option<I> {
match mem::replace(self, Self::Executing) {
Self::Input(input) => Some(input),
_ => None,
}
}
fn into_output(self) -> Option<thread::Result<O>> {
match self {
Self::Output(output) => Some(output),
_ => None,
}
}
}
unsafe extern "C" fn call_execute<I, O, D>(_: Env, data: *mut c_void) {
let data = &mut *data.cast::<Data<I, O, D>>();
let output = catch_unwind(AssertUnwindSafe(|| {
let input = data.state.take_execute_input().unwrap();
(data.execute)(input)
}));
data.state = State::Output(output);
}
unsafe extern "C" fn call_complete<I, O, D>(env: Env, status: napi::Status, data: *mut c_void) {
let Data {
state,
complete,
data,
work,
..
} = *Box::<Data<I, O, D>>::from_raw(data.cast());
debug_assert_eq!(napi::delete_async_work(env, work), Ok(()));
BOUNDARY.catch_failure(env, None, move |env| {
let output = state.into_output().unwrap();
let env = if let Some(env) = env {
env
} else {
if let Err(panic) = output {
resume_unwind(panic);
}
return ptr::null_mut();
};
match status {
napi::Status::Ok => complete(env, output, data.take()),
napi::Status::Cancelled => {}
_ => assert_eq!(status, napi::Status::Ok),
}
ptr::null_mut()
});
}