feat(napi): implement AsyncTask with AbortSignal support
This commit is contained in:
parent
d36c303dec
commit
e74fe2fb94
17 changed files with 114 additions and 98 deletions
|
@ -11,10 +11,14 @@ impl Task for BufferLength {
|
||||||
Ok(self.0.len() + 1)
|
Ok(self.0.len() + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(self, env: Env, output: Self::Output) -> Result<Self::JsValue> {
|
fn resolve(&mut self, env: Env, output: Self::Output) -> Result<Self::JsValue> {
|
||||||
self.0.unref(env)?;
|
|
||||||
env.create_uint32(output as u32)
|
env.create_uint32(output as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn finally(&mut self, env: Env) -> Result<()> {
|
||||||
|
self.0.unref(env)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[js_function(1)]
|
#[js_function(1)]
|
||||||
|
@ -33,7 +37,7 @@ fn bench_threadsafe_function(ctx: CallContext) -> Result<JsUndefined> {
|
||||||
let tsfn = ctx.env.create_threadsafe_function(
|
let tsfn = ctx.env.create_threadsafe_function(
|
||||||
&callback,
|
&callback,
|
||||||
0,
|
0,
|
||||||
|ctx: ThreadSafeCallContext<(usize, Ref<JsBufferValue>)>| {
|
|mut ctx: ThreadSafeCallContext<(usize, Ref<JsBufferValue>)>| {
|
||||||
ctx
|
ctx
|
||||||
.env
|
.env
|
||||||
.create_uint32(ctx.value.0 as u32)
|
.create_uint32(ctx.value.0 as u32)
|
||||||
|
|
|
@ -76,13 +76,6 @@ impl TryToTokens for NapiStruct {
|
||||||
|
|
||||||
impl NapiStruct {
|
impl NapiStruct {
|
||||||
fn gen_helper_mod(&self) -> TokenStream {
|
fn gen_helper_mod(&self) -> TokenStream {
|
||||||
if crate::typegen::r#struct::TASK_STRUCTS.with(|t| {
|
|
||||||
println!("{:?}", t);
|
|
||||||
t.borrow().get(&self.name.to_string()).is_some()
|
|
||||||
}) {
|
|
||||||
return quote! {};
|
|
||||||
}
|
|
||||||
|
|
||||||
let mod_name = Ident::new(
|
let mod_name = Ident::new(
|
||||||
&format!("__napi_helper__{}", self.name.to_string()),
|
&format!("__napi_helper__{}", self.name.to_string()),
|
||||||
Span::call_site(),
|
Span::call_site(),
|
||||||
|
|
|
@ -71,7 +71,7 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
|
||||||
("null", "null"),
|
("null", "null"),
|
||||||
("symbol", "symbol"),
|
("symbol", "symbol"),
|
||||||
("external", "object"),
|
("external", "object"),
|
||||||
("AsyncTaskAbortController", "AbortController"),
|
("AbortSignal", "AbortSignal"),
|
||||||
("Function", "(...args: any[]) => any"),
|
("Function", "(...args: any[]) => any"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ macro_rules! attrgen {
|
||||||
(skip, Skip(Span)),
|
(skip, Skip(Span)),
|
||||||
(strict, Strict(Span)),
|
(strict, Strict(Span)),
|
||||||
(object, Object(Span)),
|
(object, Object(Span)),
|
||||||
|
(task, Task(Span)),
|
||||||
|
|
||||||
// impl later
|
// impl later
|
||||||
// (inspectable, Inspectable(Span)),
|
// (inspectable, Inspectable(Span)),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::os::raw::c_void;
|
use std::os::raw::c_void;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicU8, Ordering};
|
||||||
use std::{ffi::CString, rc::Rc};
|
use std::{ffi::CString, rc::Rc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -14,7 +14,7 @@ struct AsyncWork<T: Task> {
|
||||||
deferred: sys::napi_deferred,
|
deferred: sys::napi_deferred,
|
||||||
value: Result<mem::MaybeUninit<T::Output>>,
|
value: Result<mem::MaybeUninit<T::Output>>,
|
||||||
napi_async_work: sys::napi_async_work,
|
napi_async_work: sys::napi_async_work,
|
||||||
abort: Rc<AtomicBool>,
|
status: Rc<AtomicU8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AsyncWorkPromise {
|
pub struct AsyncWorkPromise {
|
||||||
|
@ -22,8 +22,11 @@ pub struct AsyncWorkPromise {
|
||||||
raw_promise: sys::napi_value,
|
raw_promise: sys::napi_value,
|
||||||
pub(crate) deferred: sys::napi_deferred,
|
pub(crate) deferred: sys::napi_deferred,
|
||||||
env: sys::napi_env,
|
env: sys::napi_env,
|
||||||
// share with AsyncWork
|
/// share with AsyncWork
|
||||||
pub(crate) abort: Rc<AtomicBool>,
|
/// 0: not started
|
||||||
|
/// 1: completed
|
||||||
|
/// 2: canceled
|
||||||
|
pub(crate) status: Rc<AtomicU8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncWorkPromise {
|
impl AsyncWorkPromise {
|
||||||
|
@ -33,7 +36,7 @@ impl AsyncWorkPromise {
|
||||||
|
|
||||||
pub fn cancel(&self) -> Result<()> {
|
pub fn cancel(&self) -> Result<()> {
|
||||||
// must be happened in the main thread, relaxed is enough
|
// must be happened in the main thread, relaxed is enough
|
||||||
self.abort.store(true, Ordering::Relaxed);
|
self.status.store(2, Ordering::Relaxed);
|
||||||
check_status!(unsafe { sys::napi_cancel_async_work(self.env, self.napi_async_work) })
|
check_status!(unsafe { sys::napi_cancel_async_work(self.env, self.napi_async_work) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,20 +44,20 @@ impl AsyncWorkPromise {
|
||||||
pub fn run<T: Task>(
|
pub fn run<T: Task>(
|
||||||
env: sys::napi_env,
|
env: sys::napi_env,
|
||||||
task: T,
|
task: T,
|
||||||
abort_status: Option<Rc<AtomicBool>>,
|
abort_status: Option<Rc<AtomicU8>>,
|
||||||
) -> Result<AsyncWorkPromise> {
|
) -> Result<AsyncWorkPromise> {
|
||||||
let mut raw_resource = ptr::null_mut();
|
let mut raw_resource = ptr::null_mut();
|
||||||
check_status!(unsafe { sys::napi_create_object(env, &mut raw_resource) })?;
|
check_status!(unsafe { sys::napi_create_object(env, &mut raw_resource) })?;
|
||||||
let mut raw_promise = ptr::null_mut();
|
let mut raw_promise = ptr::null_mut();
|
||||||
let mut deferred = ptr::null_mut();
|
let mut deferred = ptr::null_mut();
|
||||||
check_status!(unsafe { sys::napi_create_promise(env, &mut deferred, &mut raw_promise) })?;
|
check_status!(unsafe { sys::napi_create_promise(env, &mut deferred, &mut raw_promise) })?;
|
||||||
let task_abort = abort_status.unwrap_or_else(|| Rc::new(AtomicBool::new(false)));
|
let task_status = abort_status.unwrap_or_else(|| Rc::new(AtomicU8::new(0)));
|
||||||
let result = Box::leak(Box::new(AsyncWork {
|
let result = Box::leak(Box::new(AsyncWork {
|
||||||
inner_task: task,
|
inner_task: task,
|
||||||
deferred,
|
deferred,
|
||||||
value: Ok(mem::MaybeUninit::zeroed()),
|
value: Ok(mem::MaybeUninit::zeroed()),
|
||||||
napi_async_work: ptr::null_mut(),
|
napi_async_work: ptr::null_mut(),
|
||||||
abort: task_abort.clone(),
|
status: task_status.clone(),
|
||||||
}));
|
}));
|
||||||
check_status!(unsafe {
|
check_status!(unsafe {
|
||||||
sys::napi_create_async_work(
|
sys::napi_create_async_work(
|
||||||
|
@ -76,7 +79,7 @@ pub fn run<T: Task>(
|
||||||
raw_promise,
|
raw_promise,
|
||||||
deferred,
|
deferred,
|
||||||
env,
|
env,
|
||||||
abort: task_abort,
|
status: task_status,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,16 +91,11 @@ unsafe impl<T: Task> Sync for AsyncWork<T> {}
|
||||||
/// So it actually could do nothing here, because `execute` function is called in the other thread mostly.
|
/// So it actually could do nothing here, because `execute` function is called in the other thread mostly.
|
||||||
unsafe extern "C" fn execute<T: Task>(_env: sys::napi_env, data: *mut c_void) {
|
unsafe extern "C" fn execute<T: Task>(_env: sys::napi_env, data: *mut c_void) {
|
||||||
let mut work = Box::from_raw(data as *mut AsyncWork<T>);
|
let mut work = Box::from_raw(data as *mut AsyncWork<T>);
|
||||||
if work.abort.load(Ordering::Relaxed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let _ = mem::replace(
|
let _ = mem::replace(
|
||||||
&mut work.value,
|
&mut work.value,
|
||||||
work.inner_task.compute().map(mem::MaybeUninit::new),
|
work.inner_task.compute().map(mem::MaybeUninit::new),
|
||||||
);
|
);
|
||||||
if !work.abort.load(Ordering::Relaxed) {
|
|
||||||
Box::leak(work);
|
Box::leak(work);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn complete<T: Task>(
|
unsafe extern "C" fn complete<T: Task>(
|
||||||
|
@ -116,6 +114,7 @@ unsafe extern "C" fn complete<T: Task>(
|
||||||
}
|
}
|
||||||
Err(e) => work.inner_task.reject(Env::from_raw(env), e),
|
Err(e) => work.inner_task.reject(Env::from_raw(env), e),
|
||||||
};
|
};
|
||||||
|
if status != sys::Status::napi_cancelled && work.status.load(Ordering::Relaxed) != 2 {
|
||||||
match check_status!(status)
|
match check_status!(status)
|
||||||
.and_then(move |_| value)
|
.and_then(move |_| value)
|
||||||
.and_then(|v| ToNapiValue::to_napi_value(env, v))
|
.and_then(|v| ToNapiValue::to_napi_value(env, v))
|
||||||
|
@ -129,9 +128,14 @@ unsafe extern "C" fn complete<T: Task>(
|
||||||
debug_assert!(status == sys::Status::napi_ok, "Reject promise failed");
|
debug_assert!(status == sys::Status::napi_ok, "Reject promise failed");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
if let Err(e) = work.inner_task.finally(Env::from_raw(env)) {
|
||||||
|
debug_assert!(false, "Panic in Task finally fn: {:?}", e);
|
||||||
|
}
|
||||||
let delete_status = sys::napi_delete_async_work(env, napi_async_work);
|
let delete_status = sys::napi_delete_async_work(env, napi_async_work);
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
delete_status == sys::Status::napi_ok,
|
delete_status == sys::Status::napi_ok,
|
||||||
"Delete async work failed"
|
"Delete async work failed"
|
||||||
);
|
);
|
||||||
|
work.status.store(1, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
|
use std::sync::atomic::{AtomicPtr, AtomicU8, Ordering};
|
||||||
|
|
||||||
use super::{FromNapiValue, ToNapiValue, TypeName};
|
use super::{FromNapiValue, ToNapiValue, TypeName};
|
||||||
use crate::{async_work, check_status, Env, Error, JsError, JsObject, NapiValue, Status, Task};
|
use crate::{async_work, check_status, Env, Error, JsError, JsObject, NapiValue, Status, Task};
|
||||||
|
|
||||||
pub struct AsyncTask<T: Task> {
|
pub struct AsyncTask<T: Task> {
|
||||||
inner: T,
|
inner: T,
|
||||||
abort_controller: Option<AsyncTaskAbortController>,
|
abort_signal: Option<AbortSignal>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Task> TypeName for T {
|
impl<T: Task> TypeName for T {
|
||||||
|
@ -25,41 +25,40 @@ impl<T: Task> AsyncTask<T> {
|
||||||
pub fn new(task: T) -> Self {
|
pub fn new(task: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: task,
|
inner: task,
|
||||||
abort_controller: None,
|
abort_signal: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_abort_controller(task: T, abort_controller: AsyncTaskAbortController) -> Self {
|
pub fn with_signal(task: T, signal: AbortSignal) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: task,
|
inner: task,
|
||||||
abort_controller: Some(abort_controller),
|
abort_signal: Some(signal),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://developer.mozilla.org/zh-CN/docs/Web/API/AbortController
|
/// https://developer.mozilla.org/zh-CN/docs/Web/API/AbortController
|
||||||
pub struct AsyncTaskAbortController {
|
pub struct AbortSignal {
|
||||||
raw_work: Rc<AtomicPtr<napi_sys::napi_async_work__>>,
|
raw_work: Rc<AtomicPtr<napi_sys::napi_async_work__>>,
|
||||||
raw_deferred: Rc<AtomicPtr<napi_sys::napi_deferred__>>,
|
raw_deferred: Rc<AtomicPtr<napi_sys::napi_deferred__>>,
|
||||||
abort: Rc<AtomicBool>,
|
status: Rc<AtomicU8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromNapiValue for AsyncTaskAbortController {
|
impl FromNapiValue for AbortSignal {
|
||||||
unsafe fn from_napi_value(
|
unsafe fn from_napi_value(
|
||||||
env: napi_sys::napi_env,
|
env: napi_sys::napi_env,
|
||||||
napi_val: napi_sys::napi_value,
|
napi_val: napi_sys::napi_value,
|
||||||
) -> crate::Result<Self> {
|
) -> crate::Result<Self> {
|
||||||
let raw_abort_controller = JsObject::from_raw_unchecked(env, napi_val);
|
let mut signal = JsObject::from_raw_unchecked(env, napi_val);
|
||||||
let mut signal = raw_abort_controller.get_named_property::<JsObject>("signal")?;
|
|
||||||
let async_work_inner: Rc<AtomicPtr<napi_sys::napi_async_work__>> =
|
let async_work_inner: Rc<AtomicPtr<napi_sys::napi_async_work__>> =
|
||||||
Rc::new(AtomicPtr::new(ptr::null_mut()));
|
Rc::new(AtomicPtr::new(ptr::null_mut()));
|
||||||
let raw_promise: Rc<AtomicPtr<napi_sys::napi_deferred__>> =
|
let raw_promise: Rc<AtomicPtr<napi_sys::napi_deferred__>> =
|
||||||
Rc::new(AtomicPtr::new(ptr::null_mut()));
|
Rc::new(AtomicPtr::new(ptr::null_mut()));
|
||||||
let abort_status = Rc::new(AtomicBool::new(false));
|
let task_status = Rc::new(AtomicU8::new(0));
|
||||||
let abort_controller = AsyncTaskAbortController {
|
let abort_controller = AbortSignal {
|
||||||
raw_work: async_work_inner.clone(),
|
raw_work: async_work_inner.clone(),
|
||||||
raw_deferred: raw_promise.clone(),
|
raw_deferred: raw_promise.clone(),
|
||||||
abort: abort_status.clone(),
|
status: task_status.clone(),
|
||||||
};
|
};
|
||||||
let js_env = Env::from_raw(env);
|
let js_env = Env::from_raw(env);
|
||||||
check_status!(napi_sys::napi_wrap(
|
check_status!(napi_sys::napi_wrap(
|
||||||
|
@ -71,10 +70,10 @@ impl FromNapiValue for AsyncTaskAbortController {
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
))?;
|
))?;
|
||||||
signal.set_named_property("onabort", js_env.create_function("onabort", on_abort)?)?;
|
signal.set_named_property("onabort", js_env.create_function("onabort", on_abort)?)?;
|
||||||
Ok(AsyncTaskAbortController {
|
Ok(AbortSignal {
|
||||||
raw_work: async_work_inner,
|
raw_work: async_work_inner,
|
||||||
raw_deferred: raw_promise,
|
raw_deferred: raw_promise,
|
||||||
abort: abort_status,
|
status: task_status,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,13 +106,16 @@ extern "C" fn on_abort(
|
||||||
"{}",
|
"{}",
|
||||||
"Unwrap async_task from AbortSignal failed"
|
"Unwrap async_task from AbortSignal failed"
|
||||||
);
|
);
|
||||||
let abort_controller = Box::leak(Box::from_raw(async_task as *mut AsyncTaskAbortController));
|
let abort_controller = Box::leak(Box::from_raw(async_task as *mut AbortSignal));
|
||||||
|
// Task Completed, return now
|
||||||
|
if abort_controller.status.load(Ordering::Relaxed) == 1 {
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
let raw_async_work = abort_controller.raw_work.load(Ordering::Relaxed);
|
let raw_async_work = abort_controller.raw_work.load(Ordering::Relaxed);
|
||||||
let deferred = abort_controller.raw_deferred.load(Ordering::Relaxed);
|
let deferred = abort_controller.raw_deferred.load(Ordering::Relaxed);
|
||||||
// abort function must be called from JavaScript main thread, so Relaxed Ordering is ok.
|
|
||||||
abort_controller.abort.store(true, Ordering::Relaxed);
|
|
||||||
napi_sys::napi_cancel_async_work(env, raw_async_work);
|
napi_sys::napi_cancel_async_work(env, raw_async_work);
|
||||||
// napi_sys::napi_delete_async_work(env, raw_async_work);
|
// abort function must be called from JavaScript main thread, so Relaxed Ordering is ok.
|
||||||
|
abort_controller.status.store(2, Ordering::Relaxed);
|
||||||
let abort_error = Error::new(Status::Cancelled, "AbortError".to_owned());
|
let abort_error = Error::new(Status::Cancelled, "AbortError".to_owned());
|
||||||
let reject_status =
|
let reject_status =
|
||||||
napi_sys::napi_reject_deferred(env, deferred, JsError::from(abort_error).into_value(env));
|
napi_sys::napi_reject_deferred(env, deferred, JsError::from(abort_error).into_value(env));
|
||||||
|
@ -123,10 +125,8 @@ extern "C" fn on_abort(
|
||||||
"{}",
|
"{}",
|
||||||
"Reject AbortError failed"
|
"Reject AbortError failed"
|
||||||
);
|
);
|
||||||
let mut undefined = ptr::null_mut();
|
|
||||||
napi_sys::napi_get_undefined(env, &mut undefined);
|
|
||||||
undefined
|
|
||||||
}
|
}
|
||||||
|
ptr::null_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Task> ToNapiValue for AsyncTask<T> {
|
impl<T: Task> ToNapiValue for AsyncTask<T> {
|
||||||
|
@ -134,8 +134,8 @@ impl<T: Task> ToNapiValue for AsyncTask<T> {
|
||||||
env: napi_sys::napi_env,
|
env: napi_sys::napi_env,
|
||||||
val: Self,
|
val: Self,
|
||||||
) -> crate::Result<napi_sys::napi_value> {
|
) -> crate::Result<napi_sys::napi_value> {
|
||||||
if let Some(abort_controller) = val.abort_controller {
|
if let Some(abort_controller) = val.abort_signal {
|
||||||
let async_promise = async_work::run(env, val.inner, Some(abort_controller.abort.clone()))?;
|
let async_promise = async_work::run(env, val.inner, Some(abort_controller.status.clone()))?;
|
||||||
abort_controller
|
abort_controller
|
||||||
.raw_work
|
.raw_work
|
||||||
.store(async_promise.napi_async_work, Ordering::Relaxed);
|
.store(async_promise.napi_async_work, Ordering::Relaxed);
|
||||||
|
@ -155,5 +155,5 @@ unsafe extern "C" fn async_task_abort_controller_finalize(
|
||||||
finalize_data: *mut c_void,
|
finalize_data: *mut c_void,
|
||||||
_finalize_hint: *mut c_void,
|
_finalize_hint: *mut c_void,
|
||||||
) {
|
) {
|
||||||
Box::from_raw(finalize_data as *mut AsyncTaskAbortController);
|
Box::from_raw(finalize_data as *mut AbortSignal);
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,11 @@ unsafe extern "C" fn napi_register_module_v1(
|
||||||
for (rust_name, (js_name, props)) in to_register_classes.take().into_iter() {
|
for (rust_name, (js_name, props)) in to_register_classes.take().into_iter() {
|
||||||
unsafe {
|
unsafe {
|
||||||
let (ctor, props): (Vec<_>, Vec<_>) = props.into_iter().partition(|prop| prop.is_ctor);
|
let (ctor, props): (Vec<_>, Vec<_>) = props.into_iter().partition(|prop| prop.is_ctor);
|
||||||
// one or more?
|
// one or more or zero?
|
||||||
|
// zero is for `#[napi(task)]`
|
||||||
|
if ctor.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let ctor = ctor[0].raw().method.unwrap();
|
let ctor = ctor[0].raw().method.unwrap();
|
||||||
let raw_props: Vec<_> = props.iter().map(|prop| prop.raw()).collect();
|
let raw_props: Vec<_> = props.iter().map(|prop| prop.raw()).collect();
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,8 @@ use std::mem;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
use super::Value;
|
use super::{Value, ValueType};
|
||||||
#[cfg(feature = "serde-json")]
|
use crate::{bindgen_runtime::TypeName, check_status, sys, JsUnknown, NapiValue, Ref, Result};
|
||||||
use super::ValueType;
|
|
||||||
use crate::bindgen_runtime::TypeName;
|
|
||||||
use crate::check_status;
|
|
||||||
use crate::{sys, JsUnknown, NapiValue, Ref, Result};
|
|
||||||
|
|
||||||
pub struct JsBuffer(pub(crate) Value);
|
pub struct JsBuffer(pub(crate) Value);
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ impl<T> Ref<T> {
|
||||||
Ok(self.count)
|
Ok(self.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unref(mut self, env: Env) -> Result<u32> {
|
pub fn unref(&mut self, env: Env) -> Result<u32> {
|
||||||
check_status!(unsafe { sys::napi_reference_unref(env.0, self.raw_ref, &mut self.count) })?;
|
check_status!(unsafe { sys::napi_reference_unref(env.0, self.raw_ref, &mut self.count) })?;
|
||||||
|
|
||||||
if self.count == 0 {
|
if self.count == 0 {
|
||||||
|
|
|
@ -11,13 +11,15 @@ pub trait Task: Send + Sized {
|
||||||
fn compute(&mut self) -> Result<Self::Output>;
|
fn compute(&mut self) -> Result<Self::Output>;
|
||||||
|
|
||||||
/// Into this method if `compute` return `Ok`
|
/// Into this method if `compute` return `Ok`
|
||||||
fn resolve(self, env: Env, output: Self::Output) -> Result<Self::JsValue>;
|
fn resolve(&mut self, env: Env, output: Self::Output) -> Result<Self::JsValue>;
|
||||||
|
|
||||||
/// Into this method if `compute` return `Err`
|
/// Into this method if `compute` return `Err`
|
||||||
fn reject(self, _env: Env, err: Error) -> Result<Self::JsValue> {
|
fn reject(&mut self, _env: Env, err: Error) -> Result<Self::JsValue> {
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// after resolve or reject
|
// after resolve or reject
|
||||||
fn finally() {}
|
fn finally(&mut self, _env: Env) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,15 +154,16 @@ pub fn test_tsfn_with_ref(ctx: CallContext) -> Result<JsUndefined> {
|
||||||
let callback = ctx.get::<JsFunction>(0)?;
|
let callback = ctx.get::<JsFunction>(0)?;
|
||||||
let options = ctx.get::<JsObject>(1)?;
|
let options = ctx.get::<JsObject>(1)?;
|
||||||
let options_ref = ctx.env.create_reference(options)?;
|
let options_ref = ctx.env.create_reference(options)?;
|
||||||
let tsfn =
|
let tsfn = ctx.env.create_threadsafe_function(
|
||||||
ctx
|
&callback,
|
||||||
.env
|
0,
|
||||||
.create_threadsafe_function(&callback, 0, |ctx: ThreadSafeCallContext<Ref<()>>| {
|
|mut ctx: ThreadSafeCallContext<Ref<()>>| {
|
||||||
ctx
|
ctx
|
||||||
.env
|
.env
|
||||||
.get_reference_value_unchecked::<JsObject>(&ctx.value)
|
.get_reference_value_unchecked::<JsObject>(&ctx.value)
|
||||||
.and_then(|obj| ctx.value.unref(ctx.env).map(|_| vec![obj]))
|
.and_then(|obj| ctx.value.unref(ctx.env).map(|_| vec![obj]))
|
||||||
})?;
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
tsfn.call(Ok(options_ref), ThreadsafeFunctionCallMode::Blocking);
|
tsfn.call(Ok(options_ref), ThreadsafeFunctionCallMode::Blocking);
|
||||||
|
|
|
@ -22,7 +22,7 @@ impl Task for ComputeFib {
|
||||||
Ok(fibonacci_native(self.n))
|
Ok(fibonacci_native(self.n))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(self, env: Env, output: Self::Output) -> Result<Self::JsValue> {
|
fn resolve(&mut self, env: Env, output: Self::Output) -> Result<Self::JsValue> {
|
||||||
env.create_uint32(output)
|
env.create_uint32(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,15 +63,18 @@ impl Task for CountBufferLength {
|
||||||
Ok((&self.data).len())
|
Ok((&self.data).len())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(self, env: Env, output: Self::Output) -> Result<Self::JsValue> {
|
fn resolve(&mut self, env: Env, output: Self::Output) -> Result<Self::JsValue> {
|
||||||
self.data.unref(env)?;
|
|
||||||
env.create_uint32(output as _)
|
env.create_uint32(output as _)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reject(self, env: Env, err: Error) -> Result<Self::JsValue> {
|
fn reject(&mut self, env: Env, err: Error) -> Result<Self::JsValue> {
|
||||||
self.data.unref(env)?;
|
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn finally(&mut self, env: Env) -> Result<()> {
|
||||||
|
self.data.unref(env)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[js_function(1)]
|
#[js_function(1)]
|
||||||
|
|
|
@ -43,7 +43,7 @@ Generated by [AVA](https://avajs.dev).
|
||||||
export function concatUtf16(s: string): string␊
|
export function concatUtf16(s: string): string␊
|
||||||
export function concatLatin1(s: string): string␊
|
export function concatLatin1(s: string): string␊
|
||||||
export function withoutAbortController(a: number, b: number): Promise<number>␊
|
export function withoutAbortController(a: number, b: number): Promise<number>␊
|
||||||
export function withAbortController(a: number, b: number, ctrl: AbortController): Promise<number>␊
|
export function withAbortController(a: number, b: number, signal: AbortSignal): Promise<number>␊
|
||||||
export function getBuffer(): Buffer␊
|
export function getBuffer(): Buffer␊
|
||||||
export class Animal {␊
|
export class Animal {␊
|
||||||
readonly kind: Kind␊
|
readonly kind: Kind␊
|
||||||
|
|
Binary file not shown.
|
@ -167,9 +167,11 @@ test('async task without abort controller', async (t) => {
|
||||||
t.is(await withoutAbortController(1, 2), 3)
|
t.is(await withoutAbortController(1, 2), 3)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('async task with abort controller', async (t) => {
|
const MaybeTest = typeof AbortController !== 'undefined' ? test : test.skip
|
||||||
|
|
||||||
|
MaybeTest('async task with abort controller', async (t) => {
|
||||||
const ctrl = new AbortController()
|
const ctrl = new AbortController()
|
||||||
const promise = withAbortController(1, 2, ctrl)
|
const promise = withAbortController(1, 2, ctrl.signal)
|
||||||
try {
|
try {
|
||||||
ctrl.abort()
|
ctrl.abort()
|
||||||
await promise
|
await promise
|
||||||
|
@ -178,3 +180,9 @@ test('async task with abort controller', async (t) => {
|
||||||
t.is((err as Error).message, 'AbortError')
|
t.is((err as Error).message, 'AbortError')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
MaybeTest('abort resolved task', async (t) => {
|
||||||
|
const ctrl = new AbortController()
|
||||||
|
await withAbortController(1, 2, ctrl.signal).then(() => ctrl.abort())
|
||||||
|
t.pass('should not throw')
|
||||||
|
})
|
||||||
|
|
2
examples/napi/index.d.ts
vendored
2
examples/napi/index.d.ts
vendored
|
@ -33,7 +33,7 @@ export function concatStr(mutS: string): string
|
||||||
export function concatUtf16(s: string): string
|
export function concatUtf16(s: string): string
|
||||||
export function concatLatin1(s: string): string
|
export function concatLatin1(s: string): string
|
||||||
export function withoutAbortController(a: number, b: number): Promise<number>
|
export function withoutAbortController(a: number, b: number): Promise<number>
|
||||||
export function withAbortController(a: number, b: number, ctrl: AbortController): Promise<number>
|
export function withAbortController(a: number, b: number, signal: AbortSignal): Promise<number>
|
||||||
export function getBuffer(): Buffer
|
export function getBuffer(): Buffer
|
||||||
export class Animal {
|
export class Animal {
|
||||||
readonly kind: Kind
|
readonly kind: Kind
|
||||||
|
|
|
@ -5,17 +5,17 @@ use napi::Task;
|
||||||
|
|
||||||
struct DelaySum(u32, u32);
|
struct DelaySum(u32, u32);
|
||||||
|
|
||||||
#[napi]
|
#[napi(task)]
|
||||||
impl Task for DelaySum {
|
impl Task for DelaySum {
|
||||||
type Output = u32;
|
type Output = u32;
|
||||||
type JsValue = u32;
|
type JsValue = u32;
|
||||||
|
|
||||||
fn compute(&mut self) -> Result<Self::Output> {
|
fn compute(&mut self) -> Result<Self::Output> {
|
||||||
sleep(std::time::Duration::from_secs(1));
|
sleep(std::time::Duration::from_millis(100));
|
||||||
Ok(self.0 + self.1)
|
Ok(self.0 + self.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(self, _env: napi::Env, output: Self::Output) -> Result<Self::JsValue> {
|
fn resolve(&mut self, _env: napi::Env, output: Self::Output) -> Result<Self::JsValue> {
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,6 @@ fn without_abort_controller(a: u32, b: u32) -> AsyncTask<DelaySum> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
fn with_abort_controller(a: u32, b: u32, ctrl: AsyncTaskAbortController) -> AsyncTask<DelaySum> {
|
fn with_abort_controller(a: u32, b: u32, signal: AbortSignal) -> AsyncTask<DelaySum> {
|
||||||
AsyncTask::with_abort_controller(DelaySum(a, b), ctrl)
|
AsyncTask::with_signal(DelaySum(a, b), signal)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue