Skip to main content

napi/
async_work.rs

1use std::cell::Cell;
2use std::marker::PhantomData;
3use std::mem;
4use std::os::raw::c_void;
5use std::panic::UnwindSafe;
6use std::ptr;
7use std::rc::Rc;
8
9use crate::bindgen_runtime::JsObjectValue;
10use crate::{
11  bindgen_runtime::{PromiseRaw, ToNapiValue},
12  check_status, sys, Env, Error, JsError, Result, ScopedTask, Status,
13};
14
15struct AsyncWork<'task, T: ScopedTask<'task>> {
16  inner_task: T,
17  deferred: sys::napi_deferred,
18  value: mem::MaybeUninit<Result<T::Output>>,
19  napi_async_work: sys::napi_async_work,
20  status: Rc<Cell<u8>>,
21}
22
23pub struct AsyncWorkPromise<T> {
24  pub(crate) napi_async_work: sys::napi_async_work,
25  raw_promise: sys::napi_value,
26  env: sys::napi_env,
27  /// share with AsyncWork
28  /// 0: not started
29  /// 1: completed
30  /// 2: canceled
31  pub(crate) status: Rc<Cell<u8>>,
32  _phantom: PhantomData<T>,
33}
34
35impl<T> UnwindSafe for AsyncWorkPromise<T> {}
36impl<T> std::panic::RefUnwindSafe for AsyncWorkPromise<T> {}
37
38impl<T> AsyncWorkPromise<T> {
39  pub fn promise_object<'env>(&self) -> PromiseRaw<'env, T> {
40    PromiseRaw::new(self.env, self.raw_promise)
41  }
42
43  pub fn cancel(&mut self) -> Result<()> {
44    // must be happened in the main thread, relaxed is enough
45    self.status.set(2);
46    check_status!(
47      unsafe { sys::napi_cancel_async_work(self.env, self.napi_async_work) },
48      "Cancel async work failed"
49    )
50  }
51}
52
53pub fn run<'task, T: ScopedTask<'task>>(
54  env: sys::napi_env,
55  task: T,
56  abort_status: Option<Rc<Cell<u8>>>,
57) -> Result<AsyncWorkPromise<T::JsValue>> {
58  let mut undefined = ptr::null_mut();
59  check_status!(
60    unsafe { sys::napi_get_undefined(env, &mut undefined) },
61    "Get undefined failed in async_work::run"
62  )?;
63  let mut raw_promise = ptr::null_mut();
64  let mut deferred = ptr::null_mut();
65  check_status!(
66    unsafe { sys::napi_create_promise(env, &mut deferred, &mut raw_promise) },
67    "Create promise failed in async_work::run"
68  )?;
69  let task_status = abort_status.unwrap_or_else(|| Rc::new(Cell::new(0)));
70  let result = Box::leak(Box::new(AsyncWork {
71    inner_task: task,
72    deferred,
73    value: mem::MaybeUninit::uninit(),
74    napi_async_work: ptr::null_mut(),
75    status: task_status.clone(),
76  }));
77  check_status!(
78    unsafe {
79      sys::napi_create_async_work(
80        env,
81        raw_promise,
82        undefined,
83        Some(execute::<T>),
84        Some(complete::<T>),
85        (result as *mut AsyncWork<T>).cast(),
86        &mut result.napi_async_work,
87      )
88    },
89    "Create async work failed in async_work::run"
90  )?;
91  check_status!(
92    unsafe { sys::napi_queue_async_work(env, result.napi_async_work) },
93    "Queue async work failed in async_work::run"
94  )?;
95  Ok(AsyncWorkPromise {
96    napi_async_work: result.napi_async_work,
97    raw_promise,
98    env,
99    status: task_status,
100    _phantom: PhantomData,
101  })
102}
103
104unsafe impl<'task, T: ScopedTask<'task> + Send> Send for AsyncWork<'task, T> {}
105unsafe impl<'task, T: ScopedTask<'task> + Sync> Sync for AsyncWork<'task, T> {}
106
107/// env here is the same with the one in `CallContext`.
108/// So it actually could do nothing here, because `execute` function is called in the other thread mostly.
109unsafe extern "C" fn execute<'task, T: ScopedTask<'task>>(_env: sys::napi_env, data: *mut c_void) {
110  let work = Box::leak(unsafe { Box::from_raw(data as *mut AsyncWork<T>) });
111  let value = work.inner_task.compute();
112  work.value.write(value);
113}
114
115unsafe extern "C" fn complete<'task, T: ScopedTask<'task>>(
116  env: sys::napi_env,
117  status: sys::napi_status,
118  data: *mut c_void,
119) {
120  if let Err(e) = complete_impl::<T>(env, status, data) {
121    let js_err = JsError::from(e);
122    unsafe { js_err.throw_into(env) };
123  }
124}
125
126fn complete_impl<'task, T: ScopedTask<'task>>(
127  env: sys::napi_env,
128  status: sys::napi_status,
129  data: *mut c_void,
130) -> Result<()> {
131  let mut work = unsafe { Box::from_raw(data as *mut AsyncWork<T>) };
132  let napi_async_work = mem::replace(&mut work.napi_async_work, ptr::null_mut());
133  let deferred = mem::replace(&mut work.deferred, ptr::null_mut());
134  if status == sys::Status::napi_cancelled {
135    const ABORT_ERROR_NAME: &str = "AbortError";
136    let wrapped_env = Env::from_raw(env);
137    let mut error =
138      wrapped_env.create_error(Error::new(Status::Cancelled, ABORT_ERROR_NAME.to_owned()))?;
139    error.set_named_property("name", ABORT_ERROR_NAME)?;
140    check_status!(
141      unsafe { sys::napi_reject_deferred(env, deferred, error.0.value) },
142      "Reject AbortError failed"
143    )?;
144  } else {
145    let value_ptr = unsafe { work.value.assume_init() };
146    let value = match value_ptr {
147      Ok(output) => work.inner_task.resolve(
148        // SAFETY: `Env` is long lived
149        unsafe { std::mem::transmute::<&Env, &'task Env>(&Env::from_raw(env)) },
150        output,
151      ),
152      Err(e) => work.inner_task.reject(
153        // SAFETY: `Env` is long lived
154        unsafe { std::mem::transmute::<&Env, &'task Env>(&Env::from_raw(env)) },
155        e,
156      ),
157    };
158    if work.status.get() != 2 {
159      match check_status!(status)
160        .and_then(move |_| value)
161        .and_then(|v| unsafe { ToNapiValue::to_napi_value(env, v) })
162      {
163        Ok(v) => {
164          check_status!(
165            unsafe { sys::napi_resolve_deferred(env, deferred, v) },
166            "Resolve promise failed"
167          )?;
168        }
169        Err(e) => {
170          check_status!(
171            unsafe { sys::napi_reject_deferred(env, deferred, JsError::from(e).into_value(env)) },
172            "Reject promise failed"
173          )?;
174        }
175      };
176    }
177    work.status.set(1);
178  }
179  work.inner_task.finally(Env::from_raw(env))?;
180  check_status!(
181    unsafe { sys::napi_delete_async_work(env, napi_async_work) },
182    "Delete async work failed"
183  )?;
184  Ok(())
185}