Skip to content

Commit ee2138e

Browse files
committed
Wrapper over DeferredPtr
1 parent fc643f3 commit ee2138e

File tree

3 files changed

+74
-34
lines changed

3 files changed

+74
-34
lines changed

src/async_bridge.rs

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use napi::{Env, Error, Result, Status, sys};
1717
use napi_derive::napi;
1818

1919
use crate::errors::{ConvertedError, ConvertedResult, JsResult, with_custom_error_sync};
20+
use crate::napi_helpers::{DeferredPtr, ResolveOrReject};
2021

2122
/// JsPromise — lightweight wrapper over the promise pointer that indicates the type used to resolve the promise
2223
/// The promise can be either resolved with type T or rejected with any error value (`ConvertedError` when used with `submit_future`).
@@ -30,14 +31,14 @@ impl<T> ToNapiValue for JsPromise<T> {
3031
}
3132
}
3233

33-
type SettleCallback = Box<dyn FnOnce(Env, sys::napi_deferred) + Send>;
34+
type SettleCallback = Box<dyn FnOnce(Env, DeferredPtr) + Send>;
3435
type BridgedFuture = Pin<Box<dyn Future<Output = SettleCallback> + Send>>;
3536

3637
struct FutureEntry {
3738
future: BridgedFuture,
3839
/// Raw deferred handle — resolved/rejected in `poll_woken` on the
3940
/// main thread where we have a valid `napi_env`.
40-
deferred: sys::napi_deferred,
41+
deferred: DeferredPtr,
4142
waker: Waker,
4243
}
4344

@@ -122,12 +123,7 @@ impl FutureRegistry {
122123
}
123124
}
124125

125-
fn insert(
126-
&mut self,
127-
env: &Env,
128-
future: BridgedFuture,
129-
deferred: sys::napi_deferred,
130-
) -> Result<u64> {
126+
fn insert(&mut self, env: &Env, future: BridgedFuture, deferred: DeferredPtr) -> Result<u64> {
131127
let was_empty = self.futures.is_empty();
132128

133129
let id = self.next_id;
@@ -234,7 +230,7 @@ thread_local! {
234230
static REGISTRY: RefCell<FutureRegistry> = RefCell::new(FutureRegistry::new());
235231
}
236232

237-
fn create_promise(env: &Env) -> Result<(sys::napi_deferred, sys::napi_value)> {
233+
fn create_promise(env: &Env) -> Result<(DeferredPtr, sys::napi_value)> {
238234
let mut deferred = ptr::null_mut();
239235
let mut promise = ptr::null_mut();
240236
// SAFETY: `raw_env` is taken from Env, which is guaranteed to be valid for the lifetime of the current napi call.
@@ -245,21 +241,21 @@ fn create_promise(env: &Env) -> Result<(sys::napi_deferred, sys::napi_value)> {
245241
&mut promise
246242
))?
247243
};
248-
Ok((deferred, promise))
244+
// SAFETY: deferred is assigned to valid value in napi_create_promise call, that have just succeeded.
245+
// This promise had no chance to be resolved yet.
246+
let deferred_ptr = unsafe { DeferredPtr::new(deferred) };
247+
Ok((deferred_ptr, promise))
249248
}
250249

251-
/// # Safety
252-
/// The deferred must not have been resolved or rejected yet
253-
unsafe fn reject_with_reason(env: Env, deferred: sys::napi_deferred, reason: &str) -> Result<()> {
250+
fn reject_with_reason(env: Env, deferred: DeferredPtr, reason: &str) -> Result<()> {
254251
// We can unwrap in the second place, because the only case when Cstring::new can fail is when the string contains a null byte.
255252
let c_reason = CString::new(reason).unwrap_or_else(|_| {
256253
CString::new("[Unknown error] Error message contained illegal null byte").unwrap()
257254
});
258255
let mut msg: sys::napi_value = std::ptr::null_mut();
259256
let mut error: sys::napi_value = std::ptr::null_mut();
260257

261-
// SAFETY: Env guarantees that raw pointer is a valid main-thread env and
262-
// caller ensured that `deferred` has not yet been resolved or rejected.
258+
// SAFETY: Env guarantees that raw pointer is a valid main-thread env.
263259
// Remaining arguments are created in this function and are valid for the whole duration.
264260
unsafe {
265261
check_status!(sys::napi_create_string_utf8(
@@ -274,7 +270,7 @@ unsafe fn reject_with_reason(env: Env, deferred: sys::napi_deferred, reason: &st
274270
msg,
275271
&mut error
276272
))?;
277-
check_status!(sys::napi_reject_deferred(env.raw(), deferred, error))?;
273+
deferred.resolve(env, error, ResolveOrReject::Reject)?;
278274
}
279275
Ok(())
280276
}
@@ -360,33 +356,26 @@ where
360356

361357
let boxed: BridgedFuture = Box::pin(async move {
362358
let result = fut.await;
363-
Box::new(move |env: Env, deferred| unsafe {
359+
Box::new(move |env: Env, deferred: DeferredPtr| unsafe {
364360
// SAFETY: This closure is only ever invoked from `poll_woken`, which runs
365361
// on the Node main thread inside the TSFN callback - the only place where
366362
// `env` is a valid napi_env. `deferred` is consumed exactly once here,
367363
// satisfying the napi contract that each deferred is resolved or rejected
368364
// exactly once. `to_napi_value` receives the same valid `env`.
369365
let (js_val, resolve) = match result {
370-
Ok(val) => (T::to_napi_value(env.raw(), val), true),
371-
Err(err) => (ConvertedError::to_napi_value(env.raw(), err), false),
366+
Ok(val) => (T::to_napi_value(env.raw(), val), ResolveOrReject::Resolve),
367+
Err(err) => (
368+
ConvertedError::to_napi_value(env.raw(), err),
369+
ResolveOrReject::Reject,
370+
),
371+
};
372+
373+
let status = match js_val {
374+
Ok(v) => deferred.resolve(env, v, resolve),
375+
Err(e) => reject_with_reason(env, deferred, &e.reason),
372376
};
373-
let status = js_val
374-
// First we try to accept / reject with converted value / error.
375-
.and_then(|v| {
376-
if resolve {
377-
check_status!(sys::napi_resolve_deferred(env.raw(), deferred, v))
378-
} else {
379-
check_status!(sys::napi_reject_deferred(env.raw(), deferred, v))
380-
}
381-
})
382-
// If this fails, or we failed to convert the value / error into a JS value,
383-
// we reject with a fallback reason.
384-
.or_else(|e| reject_with_reason(env, deferred, &e.reason));
385377

386378
if let Err(e) = status {
387-
// If both fail, we assume something terrible has happened. We cannot
388-
// inform JS side about the error by regular error handling, so we panic to
389-
// avoid silent failures and orphaned promises.
390379
panic!(
391380
"Failed to settle promise in TSFN callback. This may indicate either a bug in the driver or a severe runtime error.\nRoot cause:\n {}",
392381
e.reason

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ extern crate napi_derive;
55
pub mod async_bridge;
66
pub mod errors;
77
pub mod metadata;
8+
pub mod napi_helpers;
89
pub mod options;
910
pub mod paging;
1011
pub mod requests;

src/napi_helpers.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use std::{marker::PhantomData, rc::Rc};
2+
3+
use napi::{Env, Result, bindgen_prelude::check_status, sys};
4+
5+
/// Wrapper over napi_deferred pointer, that ensures safe usage of the pointer and prevents double resolve/reject.
6+
pub(crate) struct DeferredPtr {
7+
ptr: sys::napi_deferred,
8+
// We want to block DeferredPtr from being Send or Sync,
9+
// as we can use napi_deferred pointer can be used only in the main nodejs thread.
10+
_not_send_sync: PhantomData<Rc<()>>,
11+
}
12+
13+
pub(crate) enum ResolveOrReject {
14+
Resolve,
15+
Reject,
16+
}
17+
18+
impl DeferredPtr {
19+
/// # Safety
20+
/// The pointer must not have been resolved or rejected yet, and must point to a valid napi_deferred.
21+
pub(crate) unsafe fn new(ptr: sys::napi_deferred) -> Self {
22+
Self {
23+
ptr,
24+
_not_send_sync: PhantomData,
25+
}
26+
}
27+
28+
/// # Safety
29+
/// Valid pointer to value must be provided
30+
pub(crate) unsafe fn resolve(
31+
self,
32+
env: Env,
33+
value: sys::napi_value,
34+
mode: ResolveOrReject,
35+
) -> Result<()> {
36+
// We can use the napi_deferred only once, as per napi documentation,
37+
// any calls to resolve it, will free the value: https://nodejs.org/api/n-api.html#promises
38+
// While there is no specification what happens if the call fails, it's safer to assume
39+
// the pointer is no longer valid, and we are in non-recoverable state.
40+
41+
// SAFETY: Constraints of this class ensure validity of the deref pointer,
42+
// and Env ensures validity of the napi_env.
43+
// Caller ensures validity of the value pointer.
44+
if let ResolveOrReject::Resolve = mode {
45+
unsafe { check_status!(sys::napi_resolve_deferred(env.raw(), self.ptr, value)) }
46+
} else {
47+
unsafe { check_status!(sys::napi_reject_deferred(env.raw(), self.ptr, value)) }
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)