1 | use std::ffi::CStr; |
2 | use std::marker::PhantomData; |
3 | use std::os::raw::c_void; |
4 | use std::ptr; |
5 | |
6 | use crate::bindgen_runtime::ToNapiValue; |
7 | use crate::{check_status, JsObject, Value}; |
8 | use crate::{sys, Env, Error, Result}; |
9 | #[cfg (feature = "deferred_trace" )] |
10 | use 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)] |
21 | struct DeferredTrace(sys::napi_ref); |
22 | |
23 | #[cfg (feature = "deferred_trace" )] |
24 | impl 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 | |
90 | struct DeferredData<Data: ToNapiValue, Resolver: FnOnce(Env) -> Result<Data>> { |
91 | resolver: Result<Resolver>, |
92 | #[cfg (feature = "deferred_trace" )] |
93 | trace: DeferredTrace, |
94 | } |
95 | |
96 | pub 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 | |
104 | unsafe impl<Data: ToNapiValue, Resolver: FnOnce(Env) -> Result<Data>> Send |
105 | for JsDeferred<Data, Resolver> |
106 | { |
107 | } |
108 | |
109 | impl<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 | |
203 | extern "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 | |