use std::ffi::c_void; use std::ptr; use std::rc::Rc; use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; use super::{FromNapiValue, ToNapiValue, TypeName}; use crate::{async_work, check_status, Env, Error, JsError, JsObject, NapiValue, Status, Task}; pub struct AsyncTask { inner: T, abort_controller: Option, } impl TypeName for T { fn type_name() -> &'static str { "AsyncTask" } fn value_type() -> crate::ValueType { crate::ValueType::Object } } impl AsyncTask { pub fn new(task: T) -> Self { Self { inner: task, abort_controller: None, } } pub fn with_abort_controller(task: T, abort_controller: AsyncTaskAbortController) -> Self { Self { inner: task, abort_controller: Some(abort_controller), } } } /// https://developer.mozilla.org/zh-CN/docs/Web/API/AbortController pub struct AsyncTaskAbortController { raw_work: Rc>, raw_deferred: Rc>, abort: Rc, } impl FromNapiValue for AsyncTaskAbortController { unsafe fn from_napi_value( env: napi_sys::napi_env, napi_val: napi_sys::napi_value, ) -> crate::Result { let raw_abort_controller = JsObject::from_raw_unchecked(env, napi_val); let mut signal = raw_abort_controller.get_named_property::("signal")?; let async_work_inner: Rc> = Rc::new(AtomicPtr::new(ptr::null_mut())); let raw_promise: Rc> = Rc::new(AtomicPtr::new(ptr::null_mut())); let abort_status = Rc::new(AtomicBool::new(false)); let abort_controller = AsyncTaskAbortController { raw_work: async_work_inner.clone(), raw_deferred: raw_promise.clone(), abort: abort_status.clone(), }; let js_env = Env::from_raw(env); check_status!(napi_sys::napi_wrap( env, signal.0.value, Box::into_raw(Box::new(abort_controller)) as *mut _, Some(async_task_abort_controller_finalize), ptr::null_mut(), ptr::null_mut(), ))?; signal.set_named_property("onabort", js_env.create_function("onabort", on_abort)?)?; Ok(AsyncTaskAbortController { raw_work: async_work_inner, raw_deferred: raw_promise, abort: abort_status, }) } } extern "C" fn on_abort( env: napi_sys::napi_env, callback_info: napi_sys::napi_callback_info, ) -> napi_sys::napi_value { let mut this = ptr::null_mut(); unsafe { let get_cb_info_status = napi_sys::napi_get_cb_info( env, callback_info, &mut 0, ptr::null_mut(), &mut this, ptr::null_mut(), ); debug_assert_eq!( get_cb_info_status, napi_sys::Status::napi_ok, "{}", "Get callback info in AbortController abort callback failed" ); let mut async_task = ptr::null_mut(); let status = napi_sys::napi_unwrap(env, this, &mut async_task); debug_assert_eq!( status, napi_sys::Status::napi_ok, "{}", "Unwrap async_task from AbortSignal failed" ); let abort_controller = Box::leak(Box::from_raw(async_task as *mut AsyncTaskAbortController)); let raw_async_work = abort_controller.raw_work.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_delete_async_work(env, raw_async_work); let abort_error = Error::new(Status::Cancelled, "AbortError".to_owned()); let reject_status = napi_sys::napi_reject_deferred(env, deferred, JsError::from(abort_error).into_value(env)); debug_assert_eq!( reject_status, napi_sys::Status::napi_ok, "{}", "Reject AbortError failed" ); let mut undefined = ptr::null_mut(); napi_sys::napi_get_undefined(env, &mut undefined); undefined } } impl ToNapiValue for AsyncTask { unsafe fn to_napi_value( env: napi_sys::napi_env, val: Self, ) -> crate::Result { if let Some(abort_controller) = val.abort_controller { let async_promise = async_work::run(env, val.inner, Some(abort_controller.abort.clone()))?; abort_controller .raw_work .store(async_promise.napi_async_work, Ordering::Relaxed); abort_controller .raw_deferred .store(async_promise.deferred, Ordering::Relaxed); Ok(async_promise.promise_object().0.value) } else { let async_promise = async_work::run(env, val.inner, None)?; Ok(async_promise.promise_object().0.value) } } } unsafe extern "C" fn async_task_abort_controller_finalize( _env: napi_sys::napi_env, finalize_data: *mut c_void, _finalize_hint: *mut c_void, ) { Box::from_raw(finalize_data as *mut AsyncTaskAbortController); }