napi_h/bindgen_runtime/js_values/
task.rs

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) = val.abort_signal {
142      let async_promise = async_work::run(env, val.inner, Some(abort_controller.status.clone()))?;
143      abort_controller
144        .raw_work
145        .store(async_promise.napi_async_work, Ordering::Relaxed);
146      abort_controller
147        .raw_deferred
148        .store(async_promise.deferred, Ordering::Relaxed);
149      Ok(async_promise.promise_object().0.value)
150    } else {
151      let async_promise = async_work::run(env, val.inner, 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}