1use std::ffi::CStr;
2use std::marker::PhantomData;
3use std::os::raw::c_void;
4use std::ptr;
5
6use crate::bindgen_runtime::ToNapiValue;
7use crate::{check_status, JsObject, Value};
8use crate::{sys, Env, Error, Result};
9#[cfg(feature = "deferred_trace")]
10use crate::{NapiRaw, NapiValue};
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)]
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 tsfn: sys::napi_threadsafe_function,
98 #[cfg(feature = "deferred_trace")]
99 trace: DeferredTrace,
100 _data: PhantomData<Data>,
101 _resolver: PhantomData<Resolver>,
102}
103
104unsafe impl<Data: ToNapiValue, Resolver: FnOnce(Env) -> Result<Data>> Send
105 for JsDeferred<Data, Resolver>
106{
107}
108
109impl<Data: ToNapiValue, Resolver: FnOnce(Env) -> Result<Data>> JsDeferred<Data, Resolver> {
110 pub(crate) fn new(env: sys::napi_env) -> Result<(Self, JsObject)> {
111 let mut raw_promise = ptr::null_mut();
112 let mut raw_deferred = ptr::null_mut();
113 check_status! {
114 unsafe { sys::napi_create_promise(env, &mut raw_deferred, &mut raw_promise) }
115 }?;
116
117 // Create a threadsafe function so we can call back into the JS thread when we are done.
118 let mut async_resource_name = ptr::null_mut();
119 let s = unsafe { CStr::from_bytes_with_nul_unchecked(b"napi_resolve_deferred\0") };
120 check_status!(
121 unsafe { sys::napi_create_string_utf8(env, s.as_ptr(), 22, &mut async_resource_name) },
122 "Create async resource name in JsDeferred failed"
123 )?;
124
125 let mut tsfn = ptr::null_mut();
126 check_status!(
127 unsafe {
128 sys::napi_create_threadsafe_function(
129 env,
130 ptr::null_mut(),
131 ptr::null_mut(),
132 async_resource_name,
133 0,
134 1,
135 ptr::null_mut(),
136 None,
137 raw_deferred.cast(),
138 Some(napi_resolve_deferred::<Data, Resolver>),
139 &mut tsfn,
140 )
141 },
142 "Create threadsafe function in JsDeferred failed"
143 )?;
144
145 let deferred = Self {
146 tsfn,
147 #[cfg(feature = "deferred_trace")]
148 trace: DeferredTrace::new(env)?,
149 _data: PhantomData,
150 _resolver: PhantomData,
151 };
152
153 let promise = JsObject(Value {
154 env,
155 value: raw_promise,
156 value_type: crate::ValueType::Object,
157 });
158
159 Ok((deferred, promise))
160 }
161
162 /// Consumes the deferred, and resolves the promise. The provided function will be called
163 /// from the JavaScript thread, and should return the resolved value.
164 pub fn resolve(self, resolver: Resolver) {
165 self.call_tsfn(Ok(resolver))
166 }
167
168 /// Consumes the deferred, and rejects the promise with the provided error.
169 pub fn reject(self, error: Error) {
170 self.call_tsfn(Err(error))
171 }
172
173 fn call_tsfn(self, result: Result<Resolver>) {
174 let data = DeferredData {
175 resolver: result,
176 #[cfg(feature = "deferred_trace")]
177 trace: self.trace,
178 };
179
180 // Call back into the JS thread via a threadsafe function. This results in napi_resolve_deferred being called.
181 let status = unsafe {
182 sys::napi_call_threadsafe_function(
183 self.tsfn,
184 Box::into_raw(Box::from(data)).cast(),
185 sys::ThreadsafeFunctionCallMode::blocking,
186 )
187 };
188 debug_assert!(
189 status == sys::Status::napi_ok,
190 "Call threadsafe function in JsDeferred failed"
191 );
192
193 let status = unsafe {
194 sys::napi_release_threadsafe_function(self.tsfn, sys::ThreadsafeFunctionReleaseMode::release)
195 };
196 debug_assert!(
197 status == sys::Status::napi_ok,
198 "Release threadsafe function in JsDeferred failed"
199 );
200 }
201}
202
203extern "C" fn napi_resolve_deferred<Data: ToNapiValue, Resolver: FnOnce(Env) -> Result<Data>>(
204 env: sys::napi_env,
205 _js_callback: sys::napi_value,
206 context: *mut c_void,
207 data: *mut c_void,
208) {
209 let deferred = context.cast();
210 let deferred_data: Box<DeferredData<Data, Resolver>> = unsafe { Box::from_raw(data.cast()) };
211 let result = deferred_data
212 .resolver
213 .and_then(|resolver| resolver(unsafe { Env::from_raw(env) }))
214 .and_then(|res| unsafe { ToNapiValue::to_napi_value(env, res) });
215
216 if let Err(e) = result.and_then(|res| {
217 check_status!(
218 unsafe { sys::napi_resolve_deferred(env, deferred, res) },
219 "Resolve deferred value failed"
220 )
221 }) {
222 #[cfg(feature = "deferred_trace")]
223 let error = deferred_data.trace.into_rejected(env, e);
224 #[cfg(not(feature = "deferred_trace"))]
225 let error = Ok::<sys::napi_value, Error>(unsafe { crate::JsError::from(e).into_value(env) });
226
227 match error {
228 Ok(error) => {
229 unsafe { sys::napi_reject_deferred(env, deferred, error) };
230 }
231 Err(err) => {
232 if cfg!(debug_assertions) {
233 println!("Failed to reject deferred: {:?}", err);
234 let mut err = ptr::null_mut();
235 let mut err_msg = ptr::null_mut();
236 unsafe {
237 sys::napi_create_string_utf8(
238 env,
239 "Rejection failed\0".as_ptr().cast(),
240 0,
241 &mut err_msg,
242 );
243 sys::napi_create_error(env, ptr::null_mut(), err_msg, &mut err);
244 sys::napi_reject_deferred(env, deferred, err);
245 }
246 }
247 }
248 }
249 }
250}
251

Provided by KDAB

Privacy Policy