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 | |