napi/bindgen_runtime/js_values/
task.rs

1use 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
54/// <https://developer.mozilla.org/zh-CN/docs/Web/API/AbortController>
55pub 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      // Task Completed, return now
156      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      // async work is already started, so we can't cancel it
162      if status != sys::Status::napi_ok {
163        abort_controller.status.store(0, Ordering::Relaxed);
164      } else {
165        // abort function must be called from JavaScript main thread, so Relaxed Ordering is ok.
166        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}