napi_calm_down/js_values/
deferred.rs

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