napi/js_values/
deferred.rs

1use std::marker::PhantomData;
2use std::os::raw::c_void;
3use std::ptr;
4
5#[cfg(feature = "deferred_trace")]
6use crate::{bindgen_runtime::JsObjectValue, JsValue};
7use crate::{
8  bindgen_runtime::{Object, ToNapiValue},
9  check_status, sys, Env, Error, Result,
10};
11
12#[cfg(feature = "deferred_trace")]
13/// A javascript error which keeps a stack trace
14/// to the original caller in an asynchronous context.
15/// This is required as the stack trace is lost when
16/// an error is created in a different thread.
17///
18/// See this issue for more details:
19/// https://github.com/nodejs/node-addon-api/issues/595
20#[repr(transparent)]
21#[derive(Clone)]
22struct DeferredTrace(sys::napi_ref);
23
24#[cfg(feature = "deferred_trace")]
25impl DeferredTrace {
26  fn new(raw_env: sys::napi_env) -> Result<Self> {
27    let env = Env::from_raw(raw_env);
28    let reason = env.create_string("none")?;
29
30    let mut js_error = ptr::null_mut();
31    check_status!(
32      unsafe { sys::napi_create_error(raw_env, ptr::null_mut(), reason.raw(), &mut js_error) },
33      "Create error in DeferredTrace failed"
34    )?;
35
36    let mut result = ptr::null_mut();
37    check_status!(
38      unsafe { sys::napi_create_reference(raw_env, js_error, 1, &mut result) },
39      "Create reference in DeferredTrace failed"
40    )?;
41
42    Ok(Self(result))
43  }
44
45  fn into_rejected(self, raw_env: sys::napi_env, mut err: Error) -> Result<sys::napi_value> {
46    let env = Env::from_raw(raw_env);
47    let mut raw = ptr::null_mut();
48    check_status!(
49      unsafe { sys::napi_get_reference_value(raw_env, self.0, &mut raw) },
50      "Failed to get referenced value in DeferredTrace"
51    )?;
52
53    let mut obj = Object::from_raw(raw_env, raw);
54    let err_value = if !err.maybe_raw.is_null() {
55      let mut err_raw_value = std::ptr::null_mut();
56      check_status!(
57        unsafe { sys::napi_get_reference_value(raw_env, err.maybe_raw, &mut err_raw_value) },
58        "Get error reference in `to_napi_value` failed"
59      )?;
60      let err_obj = Object::from_raw(raw_env, err_raw_value);
61
62      let err_value = if err_obj.has_named_property("message")? {
63        // The error was already created inside the JS engine, just return it
64        Ok(err_obj.raw())
65      } else {
66        obj.set_named_property("message", "")?;
67        obj.set_named_property("code", "")?;
68        Ok(raw)
69      };
70      let mut ref_count = 0;
71      check_status!(
72        unsafe { sys::napi_reference_unref(raw_env, err.maybe_raw, &mut ref_count) },
73        "Unref error reference in `to_napi_value` failed"
74      )?;
75      if ref_count == 0 {
76        check_status!(
77          unsafe { sys::napi_delete_reference(raw_env, err.maybe_raw) },
78          "Delete error reference in `to_napi_value` failed"
79        )?;
80      }
81      // already unref, skip the logic in `Drop`
82      err.maybe_env = ptr::null_mut();
83      err.maybe_raw = ptr::null_mut();
84      err_value
85    } else {
86      obj.set_named_property("message", &err.reason)?;
87      obj.set_named_property(
88        "code",
89        env.create_string_from_std(format!("{}", err.status))?,
90      )?;
91      Ok(raw)
92    };
93    check_status!(
94      unsafe { sys::napi_delete_reference(raw_env, self.0) },
95      "Failed to get referenced value in DeferredTrace"
96    )?;
97    err_value
98  }
99}
100
101struct DeferredData<Data: ToNapiValue, Resolver: FnOnce(Env) -> Result<Data>> {
102  resolver: Result<Resolver>,
103  #[cfg(feature = "deferred_trace")]
104  trace: DeferredTrace,
105  tsfn: sys::napi_threadsafe_function,
106}
107
108pub struct JsDeferred<Data: ToNapiValue, Resolver: FnOnce(Env) -> Result<Data>> {
109  pub(crate) tsfn: sys::napi_threadsafe_function,
110  #[cfg(feature = "deferred_trace")]
111  trace: DeferredTrace,
112  _data: PhantomData<Data>,
113  _resolver: PhantomData<Resolver>,
114}
115
116// A trick to send the resolver into the `panic` handler
117// Do not use clone in the other place besides the `fn execute_tokio_future`
118impl<Data: ToNapiValue, Resolver: FnOnce(Env) -> Result<Data>> Clone
119  for JsDeferred<Data, Resolver>
120{
121  fn clone(&self) -> Self {
122    Self {
123      tsfn: self.tsfn,
124      #[cfg(feature = "deferred_trace")]
125      trace: self.trace.clone(),
126      _data: PhantomData,
127      _resolver: PhantomData,
128    }
129  }
130}
131
132unsafe impl<Data: ToNapiValue, Resolver: FnOnce(Env) -> Result<Data>> Send
133  for JsDeferred<Data, Resolver>
134{
135}
136
137impl<Data: ToNapiValue, Resolver: FnOnce(Env) -> Result<Data>> JsDeferred<Data, Resolver> {
138  pub(crate) fn new(env: &Env) -> Result<(Self, Object<'_>)> {
139    let (tsfn, promise) = js_deferred_new_raw(env, Some(napi_resolve_deferred::<Data, Resolver>))?;
140
141    let deferred = Self {
142      tsfn,
143      #[cfg(feature = "deferred_trace")]
144      trace: DeferredTrace::new(env.0)?,
145      _data: PhantomData,
146      _resolver: PhantomData,
147    };
148
149    Ok((deferred, promise))
150  }
151
152  /// Consumes the deferred, and resolves the promise. The provided function will be called
153  /// from the JavaScript thread, and should return the resolved value.
154  pub fn resolve(self, resolver: Resolver) {
155    self.call_tsfn(Ok(resolver))
156  }
157
158  /// Consumes the deferred, and rejects the promise with the provided error.
159  pub fn reject(self, error: Error) {
160    self.call_tsfn(Err(error))
161  }
162
163  fn call_tsfn(self, result: Result<Resolver>) {
164    let data = DeferredData {
165      resolver: result,
166      #[cfg(feature = "deferred_trace")]
167      trace: self.trace,
168      tsfn: self.tsfn,
169    };
170
171    // Call back into the JS thread via a threadsafe function. This results in napi_resolve_deferred being called.
172    let status = unsafe {
173      sys::napi_call_threadsafe_function(
174        self.tsfn,
175        Box::into_raw(Box::from(data)).cast(),
176        sys::ThreadsafeFunctionCallMode::blocking,
177      )
178    };
179    debug_assert!(
180      status == sys::Status::napi_ok,
181      "Call threadsafe function in JsDeferred failed"
182    );
183  }
184}
185
186fn js_deferred_new_raw(
187  env: &Env,
188  resolve_deferred: sys::napi_threadsafe_function_call_js,
189) -> Result<(sys::napi_threadsafe_function, Object<'_>)> {
190  let mut raw_promise = ptr::null_mut();
191  let mut raw_deferred = ptr::null_mut();
192  check_status!(
193    unsafe { sys::napi_create_promise(env.0, &mut raw_deferred, &mut raw_promise) },
194    "Create promise in JsDeferred failed"
195  )?;
196
197  // Create a threadsafe function so we can call back into the JS thread when we are done.
198  let mut async_resource_name = ptr::null_mut();
199  check_status!(
200    unsafe {
201      sys::napi_create_string_utf8(
202        env.0,
203        c"napi_resolve_deferred".as_ptr().cast(),
204        22,
205        &mut async_resource_name,
206      )
207    },
208    "Create async resource name in JsDeferred failed"
209  )?;
210
211  let mut tsfn = ptr::null_mut();
212  check_status!(
213    unsafe {
214      sys::napi_create_threadsafe_function(
215        env.0,
216        ptr::null_mut(),
217        ptr::null_mut(),
218        async_resource_name,
219        0,
220        1,
221        ptr::null_mut(),
222        None,
223        raw_deferred.cast(),
224        resolve_deferred,
225        &mut tsfn,
226      )
227    },
228    "Create threadsafe function in JsDeferred failed"
229  )?;
230
231  let promise = Object::from_raw(env.0, raw_promise);
232
233  Ok((tsfn, promise))
234}
235
236extern "C" fn napi_resolve_deferred<Data: ToNapiValue, Resolver: FnOnce(Env) -> Result<Data>>(
237  env: sys::napi_env,
238  _js_callback: sys::napi_value,
239  context: *mut c_void,
240  data: *mut c_void,
241) {
242  let deferred = context.cast();
243  let deferred_data: Box<DeferredData<Data, Resolver>> = unsafe { Box::from_raw(data.cast()) };
244  let tsfn = deferred_data.tsfn;
245  let result = deferred_data
246    .resolver
247    .and_then(|resolver| resolver(Env::from_raw(env)))
248    .and_then(|res| unsafe { ToNapiValue::to_napi_value(env, res) });
249
250  let release_tsfn_result = check_status!(
251    unsafe {
252      sys::napi_release_threadsafe_function(tsfn, sys::ThreadsafeFunctionReleaseMode::release)
253    },
254    "Release threadsafe function in JsDeferred failed"
255  );
256
257  if let Err(e) = release_tsfn_result.and(result).and_then(|res| {
258    check_status!(
259      unsafe { sys::napi_resolve_deferred(env, deferred, res) },
260      "Resolve deferred value failed"
261    )
262    .map(|_| {
263      #[cfg(feature = "deferred_trace")]
264      {
265        let _status = unsafe { sys::napi_delete_reference(env, deferred_data.trace.0) };
266        if _status != sys::Status::napi_ok && cfg!(debug_assertions) {
267          eprintln!(
268            "Failed to delete reference in deferred {}",
269            crate::Status::from(_status)
270          );
271        }
272      }
273    })
274  }) {
275    #[cfg(feature = "deferred_trace")]
276    let error = deferred_data.trace.into_rejected(env, e);
277    #[cfg(not(feature = "deferred_trace"))]
278    let error = Ok::<sys::napi_value, Error>(unsafe { crate::JsError::from(e).into_value(env) });
279
280    match error {
281      Ok(error) => {
282        unsafe { sys::napi_reject_deferred(env, deferred, error) };
283      }
284      Err(err) => {
285        if cfg!(debug_assertions) {
286          eprintln!("Failed to reject deferred: {err:?}");
287          let mut err = ptr::null_mut();
288          let mut err_msg = ptr::null_mut();
289          unsafe {
290            sys::napi_create_string_utf8(env, c"Rejection failed".as_ptr().cast(), 0, &mut err_msg);
291            sys::napi_create_error(env, ptr::null_mut(), err_msg, &mut err);
292            sys::napi_reject_deferred(env, deferred, err);
293          }
294        }
295      }
296    }
297  }
298}