| 1 | use std::ffi::c_void; |
| 2 | use std::ptr; |
| 3 | use std::rc::Rc; |
| 4 | use std::sync::atomic::{AtomicPtr, AtomicU8, Ordering}; |
| 5 | |
| 6 | use super::{FromNapiValue, ToNapiValue, TypeName}; |
| 7 | use crate::{ |
| 8 | async_work, check_status, sys, Env, Error, JsError, JsObject, NapiValue, Status, Task, |
| 9 | }; |
| 10 | |
| 11 | pub struct AsyncTask<T: Task> { |
| 12 | inner: T, |
| 13 | abort_signal: Option<AbortSignal>, |
| 14 | } |
| 15 | |
| 16 | impl<T: Task> TypeName for T { |
| 17 | fn type_name() -> &'static str { |
| 18 | "AsyncTask" |
| 19 | } |
| 20 | |
| 21 | fn value_type() -> crate::ValueType { |
| 22 | crate::ValueType::Object |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | impl<T: Task> AsyncTask<T> { |
| 27 | pub fn new(task: T) -> Self { |
| 28 | Self { |
| 29 | inner: task, |
| 30 | abort_signal: None, |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | pub fn with_signal(task: T, signal: AbortSignal) -> Self { |
| 35 | Self { |
| 36 | inner: task, |
| 37 | abort_signal: Some(signal), |
| 38 | } |
| 39 | } |
| 40 | |
| 41 | pub fn with_optional_signal(task: T, signal: Option<AbortSignal>) -> Self { |
| 42 | Self { |
| 43 | inner: task, |
| 44 | abort_signal: signal, |
| 45 | } |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | /// <https://developer.mozilla.org/zh-CN/docs/Web/API/AbortController> |
| 50 | pub struct AbortSignal { |
| 51 | raw_work: Rc<AtomicPtr<sys::napi_async_work__>>, |
| 52 | raw_deferred: Rc<AtomicPtr<sys::napi_deferred__>>, |
| 53 | status: Rc<AtomicU8>, |
| 54 | } |
| 55 | |
| 56 | impl FromNapiValue for AbortSignal { |
| 57 | unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> crate::Result<Self> { |
| 58 | let mut signal = unsafe { JsObject::from_raw_unchecked(env, napi_val) }; |
| 59 | let async_work_inner: Rc<AtomicPtr<sys::napi_async_work__>> = |
| 60 | Rc::new(AtomicPtr::new(ptr::null_mut())); |
| 61 | let raw_promise: Rc<AtomicPtr<sys::napi_deferred__>> = Rc::new(AtomicPtr::new(ptr::null_mut())); |
| 62 | let task_status = Rc::new(AtomicU8::new(0)); |
| 63 | let abort_controller = AbortSignal { |
| 64 | raw_work: async_work_inner.clone(), |
| 65 | raw_deferred: raw_promise.clone(), |
| 66 | status: task_status.clone(), |
| 67 | }; |
| 68 | let js_env = unsafe { Env::from_raw(env) }; |
| 69 | check_status!(unsafe { |
| 70 | sys::napi_wrap( |
| 71 | env, |
| 72 | signal.0.value, |
| 73 | Box::into_raw(Box::new(abort_controller)).cast(), |
| 74 | Some(async_task_abort_controller_finalize), |
| 75 | ptr::null_mut(), |
| 76 | ptr::null_mut(), |
| 77 | ) |
| 78 | })?; |
| 79 | signal.set_named_property("onabort" , js_env.create_function("onabort" , on_abort)?)?; |
| 80 | Ok(AbortSignal { |
| 81 | raw_work: async_work_inner, |
| 82 | raw_deferred: raw_promise, |
| 83 | status: task_status, |
| 84 | }) |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | extern "C" fn on_abort( |
| 89 | env: sys::napi_env, |
| 90 | callback_info: sys::napi_callback_info, |
| 91 | ) -> sys::napi_value { |
| 92 | let mut this = ptr::null_mut(); |
| 93 | unsafe { |
| 94 | let get_cb_info_status = sys::napi_get_cb_info( |
| 95 | env, |
| 96 | callback_info, |
| 97 | &mut 0, |
| 98 | ptr::null_mut(), |
| 99 | &mut this, |
| 100 | ptr::null_mut(), |
| 101 | ); |
| 102 | debug_assert_eq!( |
| 103 | get_cb_info_status, |
| 104 | sys::Status::napi_ok, |
| 105 | " {}" , |
| 106 | "Get callback info in AbortController abort callback failed" |
| 107 | ); |
| 108 | let mut async_task = ptr::null_mut(); |
| 109 | let status = sys::napi_unwrap(env, this, &mut async_task); |
| 110 | debug_assert_eq!( |
| 111 | status, |
| 112 | sys::Status::napi_ok, |
| 113 | " {}" , |
| 114 | "Unwrap async_task from AbortSignal failed" |
| 115 | ); |
| 116 | let abort_controller = Box::leak(Box::from_raw(async_task as *mut AbortSignal)); |
| 117 | // Task Completed, return now |
| 118 | if abort_controller.status.load(Ordering::Relaxed) == 1 { |
| 119 | return ptr::null_mut(); |
| 120 | } |
| 121 | let raw_async_work = abort_controller.raw_work.load(Ordering::Relaxed); |
| 122 | let deferred = abort_controller.raw_deferred.load(Ordering::Relaxed); |
| 123 | sys::napi_cancel_async_work(env, raw_async_work); |
| 124 | // abort function must be called from JavaScript main thread, so Relaxed Ordering is ok. |
| 125 | abort_controller.status.store(2, Ordering::Relaxed); |
| 126 | let abort_error = Error::new(Status::Cancelled, "AbortError" .to_owned()); |
| 127 | let reject_status = |
| 128 | sys::napi_reject_deferred(env, deferred, JsError::from(abort_error).into_value(env)); |
| 129 | debug_assert_eq!( |
| 130 | reject_status, |
| 131 | sys::Status::napi_ok, |
| 132 | " {}" , |
| 133 | "Reject AbortError failed" |
| 134 | ); |
| 135 | } |
| 136 | ptr::null_mut() |
| 137 | } |
| 138 | |
| 139 | impl<T: Task> ToNapiValue for AsyncTask<T> { |
| 140 | unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> crate::Result<sys::napi_value> { |
| 141 | if let Some(abort_controller: AbortSignal) = val.abort_signal { |
| 142 | let async_promise: AsyncWorkPromise = async_work::run(env, task:val.inner, abort_status:Some(abort_controller.status.clone()))?; |
| 143 | abort_controller |
| 144 | .raw_work |
| 145 | .store(ptr:async_promise.napi_async_work, order:Ordering::Relaxed); |
| 146 | abort_controller |
| 147 | .raw_deferred |
| 148 | .store(ptr:async_promise.deferred, order:Ordering::Relaxed); |
| 149 | Ok(async_promise.promise_object().0.value) |
| 150 | } else { |
| 151 | let async_promise: AsyncWorkPromise = async_work::run(env, task:val.inner, abort_status:None)?; |
| 152 | Ok(async_promise.promise_object().0.value) |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | unsafe extern "C" fn async_task_abort_controller_finalize( |
| 158 | _env: sys::napi_env, |
| 159 | finalize_data: *mut c_void, |
| 160 | _finalize_hint: *mut c_void, |
| 161 | ) { |
| 162 | drop(unsafe { Box::from_raw(finalize_data as *mut AbortSignal) }); |
| 163 | } |
| 164 | |