napi/bindgen_runtime/js_values/
task.rs1use std::ffi::c_void;
2use std::marker::PhantomData;
3use std::ptr;
4use std::rc::Rc;
5use std::sync::atomic::{AtomicPtr, AtomicU8, Ordering};
6
7use crate::Value;
8use crate::{
9 async_work,
10 bindgen_prelude::{FromNapiValue, JsObjectValue, ToNapiValue, TypeName, Unknown},
11 check_status, sys, Env, Error, JsError, Task, ValueType,
12};
13
14use super::Object;
15
16pub struct AsyncTask<T: Task> {
17 inner: T,
18 abort_signal: Option<AbortSignal>,
19}
20
21impl<T: Task> TypeName for T {
22 fn type_name() -> &'static str {
23 "AsyncTask"
24 }
25
26 fn value_type() -> crate::ValueType {
27 crate::ValueType::Object
28 }
29}
30
31impl<T: Task> AsyncTask<T> {
32 pub fn new(task: T) -> Self {
33 Self {
34 inner: task,
35 abort_signal: None,
36 }
37 }
38
39 pub fn with_signal(task: T, signal: AbortSignal) -> Self {
40 Self {
41 inner: task,
42 abort_signal: Some(signal),
43 }
44 }
45
46 pub fn with_optional_signal(task: T, signal: Option<AbortSignal>) -> Self {
47 Self {
48 inner: task,
49 abort_signal: signal,
50 }
51 }
52}
53
54pub struct AbortSignal {
56 raw_work: Rc<AtomicPtr<sys::napi_async_work__>>,
57 status: Rc<AtomicU8>,
58}
59
60#[repr(transparent)]
61struct AbortSignalStack(Vec<AbortSignal>);
62
63impl FromNapiValue for AbortSignal {
64 unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> crate::Result<Self> {
65 let mut signal = Object(
66 Value {
67 env,
68 value: napi_val,
69 value_type: ValueType::Object,
70 },
71 PhantomData,
72 );
73 let async_work_inner: Rc<AtomicPtr<sys::napi_async_work__>> =
74 Rc::new(AtomicPtr::new(ptr::null_mut()));
75 let task_status = Rc::new(AtomicU8::new(0));
76 let abort_signal = AbortSignal {
77 raw_work: async_work_inner.clone(),
78 status: task_status.clone(),
79 };
80 let js_env = Env::from_raw(env);
81
82 let mut stack;
83 let mut maybe_stack = ptr::null_mut();
84 let unwrap_status = unsafe { sys::napi_remove_wrap(env, signal.0.value, &mut maybe_stack) };
85 if unwrap_status == sys::Status::napi_ok {
86 stack = unsafe { Box::from_raw(maybe_stack as *mut AbortSignalStack) };
87 stack.0.push(abort_signal);
88 } else {
89 stack = Box::new(AbortSignalStack(vec![abort_signal]));
90 }
91 let mut signal_ref = ptr::null_mut();
92 check_status!(
93 unsafe {
94 sys::napi_wrap(
95 env,
96 signal.0.value,
97 Box::into_raw(stack).cast(),
98 Some(async_task_abort_controller_finalize),
99 ptr::null_mut(),
100 &mut signal_ref,
101 )
102 },
103 "Wrap AbortSignal failed"
104 )?;
105 signal.set_named_property(
106 "onabort",
107 js_env.create_function::<(), Unknown>("onabort", on_abort)?,
108 )?;
109
110 Ok(AbortSignal {
111 raw_work: async_work_inner,
112 status: task_status,
113 })
114 }
115}
116
117extern "C" fn on_abort(
118 env: sys::napi_env,
119 callback_info: sys::napi_callback_info,
120) -> sys::napi_value {
121 match on_abort_impl(env, callback_info) {
122 Err(err) => {
123 let js_err = JsError::from(err);
124 unsafe { js_err.throw_into(env) };
125 ptr::null_mut()
126 }
127 Ok(undefined) => undefined,
128 }
129}
130
131fn on_abort_impl(
132 env: sys::napi_env,
133 callback_info: sys::napi_callback_info,
134) -> Result<sys::napi_value, Error> {
135 let mut this = ptr::null_mut();
136 unsafe {
137 check_status!(
138 sys::napi_get_cb_info(
139 env,
140 callback_info,
141 &mut 0,
142 ptr::null_mut(),
143 &mut this,
144 ptr::null_mut(),
145 ),
146 "Get callback info in AbortController abort callback failed"
147 )?;
148 let mut async_task = ptr::null_mut();
149 check_status!(
150 sys::napi_unwrap(env, this, &mut async_task),
151 "Unwrap async_task from AbortSignal failed"
152 )?;
153 let abort_controller_stack = Box::leak(Box::from_raw(async_task as *mut AbortSignalStack));
154 for abort_controller in abort_controller_stack.0.iter() {
155 if abort_controller.status.load(Ordering::Relaxed) == 1 {
157 return Ok(ptr::null_mut());
158 }
159 let raw_async_work = abort_controller.raw_work.load(Ordering::Relaxed);
160 let status = sys::napi_cancel_async_work(env, raw_async_work);
161 if status != sys::Status::napi_ok {
163 abort_controller.status.store(0, Ordering::Relaxed);
164 } else {
165 abort_controller.status.store(2, Ordering::Relaxed);
167 }
168 }
169 let mut undefined = ptr::null_mut();
170 check_status!(
171 sys::napi_get_undefined(env, &mut undefined),
172 "Get undefined in AbortSignal::on_abort callback failed"
173 )?;
174 Ok(undefined)
175 }
176}
177
178impl<T: Task> ToNapiValue for AsyncTask<T> {
179 unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> crate::Result<sys::napi_value> {
180 if let Some(abort_signal) = val.abort_signal {
181 let async_promise = async_work::run(env, val.inner, Some(abort_signal.status.clone()))?;
182 abort_signal
183 .raw_work
184 .store(async_promise.napi_async_work, Ordering::Relaxed);
185 Ok(async_promise.promise_object().inner)
186 } else {
187 let async_promise = async_work::run(env, val.inner, None)?;
188 Ok(async_promise.promise_object().inner)
189 }
190 }
191}
192
193unsafe extern "C" fn async_task_abort_controller_finalize(
194 _env: sys::napi_env,
195 finalize_data: *mut c_void,
196 _finalize_hint: *mut c_void,
197) {
198 drop(unsafe { Box::from_raw(finalize_data as *mut AbortSignalStack) });
199}