1use std::ffi::c_void;
2use std::ptr;
3use std::rc::Rc;
4use std::sync::atomic::{AtomicPtr, AtomicU8, Ordering};
5
6use super::{FromNapiValue, ToNapiValue, TypeName};
7use crate::{
8 async_work, check_status, sys, Env, Error, JsError, JsObject, NapiValue, Status, Task,
9};
10
11pub struct AsyncTask<T: Task> {
12 inner: T,
13 abort_signal: Option<AbortSignal>,
14}
15
16impl<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
26impl<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>
50pub 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
56impl 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
88extern "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
139impl<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
157unsafe 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