Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d0dbd1328 | ||
|
|
1d483d122b | ||
|
|
5ec4d1d42f | ||
|
|
8839407535 | ||
|
|
86d33b330d | ||
|
|
51a17a7386 | ||
|
|
8b260721a2 | ||
|
|
dd0af64272 | ||
|
|
b5a672eabb | ||
|
|
b30d2919fb | ||
|
|
13da056159 |
@ -22,6 +22,7 @@ Neon owes its existence to the contributions of these fine people.
|
||||
* [Himself65](https://github.com/Himself65)
|
||||
* [Maciej Hirsz](https://github.com/maciejhirsz)
|
||||
* [Amal Hussein](https://github.com/nomadtechie)
|
||||
* [Fedor Indutny](https://github.com/indutny)
|
||||
* [Usagi Ito](https://github.com/usagi)
|
||||
* [Jeroen (jrd-rocks)](https://github.com/jrd-rocks)
|
||||
* [Keegan (mhsjlw)](https://github.com/mhsjlw)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "neon"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
authors = ["Dave Herman <david.herman@gmail.com>"]
|
||||
description = "A safe abstraction layer for Node.js."
|
||||
readme = "README.md"
|
||||
@ -12,7 +12,7 @@ build = "build.rs"
|
||||
edition = "2018"
|
||||
|
||||
[build-dependencies]
|
||||
neon-build = { version = "=0.8.2", path = "crates/neon-build" }
|
||||
neon-build = { version = "=0.8.3", path = "crates/neon-build" }
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.4.0"
|
||||
@ -25,8 +25,8 @@ failure = "0.1.5" # used for a doc example
|
||||
cslice = "0.2"
|
||||
semver = "0.9.0"
|
||||
smallvec = "1.4.2"
|
||||
neon-runtime = { version = "=0.8.2", path = "crates/neon-runtime" }
|
||||
neon-macros = { version = "=0.8.2", path = "crates/neon-macros", optional = true }
|
||||
neon-runtime = { version = "=0.8.3", path = "crates/neon-runtime" }
|
||||
neon-macros = { version = "=0.8.3", path = "crates/neon-macros", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["legacy-runtime"]
|
||||
|
||||
@ -41,7 +41,7 @@ As a rule, you should choose the **oldest version of N-API that has the APIs you
|
||||
|
||||
```toml
|
||||
[dependencies.neon]
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
default-features = false
|
||||
features = ["napi-4"]
|
||||
```
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
# Version 0.8.3
|
||||
|
||||
* Fix crash caused by non-thread safety in napi_threadsafefunction on early termination (https://github.com/neon-bindings/neon/pull/744)
|
||||
* Fix memory leak in `Root` (https://github.com/neon-bindings/neon/pull/750)
|
||||
|
||||
# Version 0.8.2
|
||||
|
||||
* More docs improvements
|
||||
|
||||
2
cli/package-lock.json
generated
2
cli/package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "neon-cli",
|
||||
"version": "0.8.2",
|
||||
"version": "0.8.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "neon-cli",
|
||||
"version": "0.8.2",
|
||||
"version": "0.8.3",
|
||||
"description": "Build and load native Rust/Neon modules.",
|
||||
"author": "Dave Herman <david.herman@gmail.com>",
|
||||
"repository": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "neon-build"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
authors = ["Dave Herman <david.herman@gmail.com>"]
|
||||
description = "Build logic required for Neon projects."
|
||||
repository = "https://github.com/neon-bindings/neon"
|
||||
@ -9,4 +9,4 @@ edition = "2018"
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
neon-sys = { version = "=0.8.2", path = "../neon-sys", optional = true }
|
||||
neon-sys = { version = "=0.8.3", path = "../neon-sys", optional = true }
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "neon-macros"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
authors = ["Dave Herman <david.herman@gmail.com>"]
|
||||
description = "Procedural macros supporting Neon"
|
||||
repository = "https://github.com/neon-bindings/neon"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "neon-runtime"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
authors = ["Dave Herman <david.herman@gmail.com>"]
|
||||
description = "Bindings to the Node.js native addon API, used by the Neon implementation."
|
||||
repository = "https://github.com/neon-bindings/neon"
|
||||
@ -10,7 +10,7 @@ edition = "2018"
|
||||
[dependencies]
|
||||
cfg-if = "1.0.0"
|
||||
libloading = { version = "0.6.5", optional = true }
|
||||
neon-sys = { version = "=0.8.2", path = "../neon-sys", optional = true }
|
||||
neon-sys = { version = "=0.8.3", path = "../neon-sys", optional = true }
|
||||
smallvec = "1.4.2"
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@ -173,6 +173,8 @@ mod napi1 {
|
||||
|
||||
fn reference_unref(env: Env, reference: Ref, result: *mut u32) -> Status;
|
||||
|
||||
fn delete_reference(env: Env, reference: Ref) -> Status;
|
||||
|
||||
fn get_reference_value(env: Env, reference: Ref, result: *mut Value) -> Status;
|
||||
|
||||
fn strict_equals(env: Env, lhs: Value, rhs: Value, result: *mut bool) -> Status;
|
||||
|
||||
@ -26,7 +26,7 @@ pub unsafe fn reference(env: Env, value: napi::Ref) -> usize {
|
||||
result.assume_init() as usize
|
||||
}
|
||||
|
||||
pub unsafe fn unreference(env: Env, value: napi::Ref) -> usize {
|
||||
pub unsafe fn unreference(env: Env, value: napi::Ref) {
|
||||
let mut result = MaybeUninit::uninit();
|
||||
|
||||
assert_eq!(
|
||||
@ -34,7 +34,9 @@ pub unsafe fn unreference(env: Env, value: napi::Ref) -> usize {
|
||||
napi::Status::Ok,
|
||||
);
|
||||
|
||||
result.assume_init() as usize
|
||||
if result.assume_init() == 0 {
|
||||
assert_eq!(napi::delete_reference(env, value), napi::Status::Ok);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get(env: Env, value: napi::Ref) -> Local {
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
use std::ffi::c_void;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::napi::bindings as napi;
|
||||
use crate::raw::{Env, Local};
|
||||
@ -34,6 +35,7 @@ unsafe impl Sync for Tsfn {}
|
||||
/// function for scheduling tasks to execute on a JavaScript thread.
|
||||
pub struct ThreadsafeFunction<T> {
|
||||
tsfn: Tsfn,
|
||||
is_finalized: Arc<Mutex<bool>>,
|
||||
callback: fn(Option<Env>, T),
|
||||
}
|
||||
|
||||
@ -76,6 +78,7 @@ impl<T: Send + 'static> ThreadsafeFunction<T> {
|
||||
callback: fn(Option<Env>, T),
|
||||
) -> Self {
|
||||
let mut result = MaybeUninit::uninit();
|
||||
let is_finalized = Arc::new(Mutex::new(false));
|
||||
|
||||
assert_eq!(
|
||||
napi::create_threadsafe_function(
|
||||
@ -87,8 +90,8 @@ impl<T: Send + 'static> ThreadsafeFunction<T> {
|
||||
// Always set the reference count to 1. Prefer using
|
||||
// Rust `Arc` to maintain the struct.
|
||||
1,
|
||||
std::ptr::null_mut(),
|
||||
None,
|
||||
Arc::into_raw(is_finalized.clone()) as *mut _,
|
||||
Some(Self::finalize),
|
||||
std::ptr::null_mut(),
|
||||
Some(Self::callback),
|
||||
result.as_mut_ptr(),
|
||||
@ -98,6 +101,7 @@ impl<T: Send + 'static> ThreadsafeFunction<T> {
|
||||
|
||||
Self {
|
||||
tsfn: Tsfn(result.assume_init()),
|
||||
is_finalized: is_finalized,
|
||||
callback,
|
||||
}
|
||||
}
|
||||
@ -115,12 +119,28 @@ impl<T: Send + 'static> ThreadsafeFunction<T> {
|
||||
data,
|
||||
}));
|
||||
|
||||
let status =
|
||||
unsafe { napi::call_threadsafe_function(self.tsfn.0, callback as *mut _, is_blocking) };
|
||||
// Hold the lock before entering `call_threadsafe_function` so that
|
||||
// `finalize_cb` would never complete.
|
||||
let mut is_finalized = self.is_finalized.lock().unwrap();
|
||||
|
||||
let status = {
|
||||
if *is_finalized {
|
||||
napi::Status::Closing
|
||||
} else {
|
||||
unsafe {
|
||||
napi::call_threadsafe_function(self.tsfn.0, callback as *mut _, is_blocking)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if status == napi::Status::Ok {
|
||||
Ok(())
|
||||
} else {
|
||||
// Prevent further calls to `call_threadsafe_function`
|
||||
if status == napi::Status::Closing {
|
||||
*is_finalized = true;
|
||||
}
|
||||
|
||||
// If the call failed, the callback won't execute
|
||||
let callback = unsafe { Box::from_raw(callback) };
|
||||
|
||||
@ -149,6 +169,14 @@ impl<T: Send + 'static> ThreadsafeFunction<T> {
|
||||
);
|
||||
}
|
||||
|
||||
// Provides a C ABI wrapper for a napi callback notifying us about tsfn
|
||||
// being finalized.
|
||||
unsafe extern "C" fn finalize(_env: Env, data: *mut c_void, _hint: *mut c_void) {
|
||||
let is_finalized = Arc::from_raw(data as *mut Mutex<bool>);
|
||||
|
||||
*is_finalized.lock().unwrap() = true;
|
||||
}
|
||||
|
||||
// Provides a C ABI wrapper for invoking the user supplied function pointer
|
||||
unsafe extern "C" fn callback(
|
||||
env: Env,
|
||||
@ -167,6 +195,14 @@ impl<T: Send + 'static> ThreadsafeFunction<T> {
|
||||
|
||||
impl<T> Drop for ThreadsafeFunction<T> {
|
||||
fn drop(&mut self) {
|
||||
let is_finalized = self.is_finalized.lock().unwrap();
|
||||
|
||||
// tsfn was already finalized by `Environment::CleanupHandles()` in
|
||||
// Node.js
|
||||
if *is_finalized {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
napi::release_threadsafe_function(
|
||||
self.tsfn.0,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "neon-sys"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
authors = ["David Herman <david.herman@gmail.com>"]
|
||||
description = "Exposes the low-level V8/NAN C/C++ APIs. Will be superseded by N-API."
|
||||
edition = "2018"
|
||||
|
||||
@ -154,10 +154,18 @@ use crate::context::internal::Env;
|
||||
#[cfg(all(feature = "napi-4", feature = "event-queue-api"))]
|
||||
use crate::event::EventQueue;
|
||||
use crate::handle::{Handle, Managed};
|
||||
#[cfg(all(feature = "napi-6", feature = "event-queue-api"))]
|
||||
use crate::lifecycle::InstanceData;
|
||||
#[cfg(feature = "legacy-runtime")]
|
||||
use crate::object::class::Class;
|
||||
use crate::object::{Object, This};
|
||||
use crate::result::{JsResult, NeonResult, Throw};
|
||||
#[cfg(all(
|
||||
feature = "napi-4",
|
||||
not(feature = "napi-6"),
|
||||
feature = "event-queue-api"
|
||||
))]
|
||||
use crate::trampoline::ThreadsafeTrampoline;
|
||||
use crate::types::binary::{JsArrayBuffer, JsBuffer};
|
||||
#[cfg(feature = "napi-1")]
|
||||
use crate::types::boxed::{Finalize, JsBox};
|
||||
@ -552,7 +560,17 @@ pub trait Context<'a>: ContextInternal<'a> {
|
||||
#[cfg(all(feature = "napi-4", feature = "event-queue-api"))]
|
||||
/// Creates an unbounded queue of events to be executed on a JavaScript thread
|
||||
fn queue(&mut self) -> EventQueue {
|
||||
EventQueue::new(self)
|
||||
#[cfg(feature = "napi-6")]
|
||||
let shared_trampoline = InstanceData::threadsafe_trampoline(self);
|
||||
|
||||
#[cfg(not(feature = "napi-6"))]
|
||||
let shared_trampoline = {
|
||||
let shared_trampoline = ThreadsafeTrampoline::new(self.env());
|
||||
shared_trampoline.unref(self.env().to_raw());
|
||||
shared_trampoline
|
||||
};
|
||||
|
||||
EventQueue::with_shared_trampoline(self, shared_trampoline)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
use neon_runtime::raw::Env;
|
||||
use neon_runtime::tsfn::ThreadsafeFunction;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use crate::context::internal::ContextInternal;
|
||||
use crate::context::{Context, TaskContext};
|
||||
use crate::result::NeonResult;
|
||||
|
||||
type Callback = Box<dyn FnOnce(Env) + Send + 'static>;
|
||||
use crate::trampoline::ThreadsafeTrampoline;
|
||||
|
||||
/// Queue for scheduling Rust closures to execute on the JavaScript main thread.
|
||||
///
|
||||
@ -50,8 +49,9 @@ type Callback = Box<dyn FnOnce(Env) + Send + 'static>;
|
||||
/// ```
|
||||
|
||||
pub struct EventQueue {
|
||||
tsfn: ThreadsafeFunction<Callback>,
|
||||
trampoline: Arc<RwLock<ThreadsafeTrampoline>>,
|
||||
has_ref: bool,
|
||||
has_shared_trampoline: bool,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for EventQueue {
|
||||
@ -64,20 +64,43 @@ impl EventQueue {
|
||||
/// Creates an unbounded queue for scheduling closures on the JavaScript
|
||||
/// main thread
|
||||
pub fn new<'a, C: Context<'a>>(cx: &mut C) -> Self {
|
||||
let tsfn = unsafe { ThreadsafeFunction::new(cx.env().to_raw(), Self::callback) };
|
||||
let trampoline = ThreadsafeTrampoline::new(cx.env().to_raw());
|
||||
|
||||
Self {
|
||||
tsfn,
|
||||
trampoline: Arc::new(RwLock::new(trampoline)),
|
||||
has_ref: true,
|
||||
has_shared_trampoline: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_shared_trampoline<'a, C: Context<'a>>(
|
||||
cx: &mut C,
|
||||
trampoline: Arc<RwLock<ThreadsafeTrampoline>>,
|
||||
) -> Self {
|
||||
trampoline
|
||||
.write()
|
||||
.unwrap()
|
||||
.increment_references(cx.env().to_raw());
|
||||
|
||||
Self {
|
||||
trampoline: trampoline,
|
||||
has_ref: true,
|
||||
has_shared_trampoline: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow the Node event loop to exit while this `EventQueue` exists.
|
||||
/// _Idempotent_
|
||||
pub fn unref<'a, C: Context<'a>>(&mut self, cx: &mut C) -> &mut Self {
|
||||
self.has_ref = false;
|
||||
if !self.has_ref {
|
||||
return self;
|
||||
}
|
||||
|
||||
unsafe { self.tsfn.unref(cx.env().to_raw()) }
|
||||
self.has_ref = false;
|
||||
self.trampoline
|
||||
.write()
|
||||
.unwrap()
|
||||
.decrement_references(cx.env().to_raw());
|
||||
|
||||
self
|
||||
}
|
||||
@ -85,9 +108,15 @@ impl EventQueue {
|
||||
/// Prevent the Node event loop from exiting while this `EventQueue` exists. (Default)
|
||||
/// _Idempotent_
|
||||
pub fn reference<'a, C: Context<'a>>(&mut self, cx: &mut C) -> &mut Self {
|
||||
self.has_ref = true;
|
||||
if self.has_ref {
|
||||
return self;
|
||||
}
|
||||
|
||||
unsafe { self.tsfn.reference(cx.env().to_raw()) }
|
||||
self.has_ref = true;
|
||||
self.trampoline
|
||||
.write()
|
||||
.unwrap()
|
||||
.increment_references(cx.env().to_raw());
|
||||
|
||||
self
|
||||
}
|
||||
@ -107,17 +136,8 @@ impl EventQueue {
|
||||
where
|
||||
F: FnOnce(TaskContext) -> NeonResult<()> + Send + 'static,
|
||||
{
|
||||
let callback = Box::new(move |env| {
|
||||
let env = unsafe { std::mem::transmute(env) };
|
||||
|
||||
// Note: It is sufficient to use `TaskContext`'s `InheritedHandleScope` because
|
||||
// N-API creates a `HandleScope` before calling the callback.
|
||||
TaskContext::with_context(env, move |cx| {
|
||||
let _ = f(cx);
|
||||
});
|
||||
});
|
||||
|
||||
self.tsfn.call(callback, None).map_err(|_| EventQueueError)
|
||||
let trampoline = self.trampoline.read().unwrap();
|
||||
trampoline.try_send(f).map_err(|_| EventQueueError)
|
||||
}
|
||||
|
||||
/// Returns a boolean indicating if this `EventQueue` will prevent the Node event
|
||||
@ -125,16 +145,29 @@ impl EventQueue {
|
||||
pub fn has_ref(&self) -> bool {
|
||||
self.has_ref
|
||||
}
|
||||
}
|
||||
|
||||
// Monomorphized trampoline funciton for calling the user provided closure
|
||||
fn callback(env: Option<Env>, callback: Callback) {
|
||||
if let Some(env) = env {
|
||||
callback(env);
|
||||
} else {
|
||||
crate::context::internal::IS_RUNNING.with(|v| {
|
||||
*v.borrow_mut() = false;
|
||||
});
|
||||
impl Drop for EventQueue {
|
||||
fn drop(&mut self) {
|
||||
if !self.has_ref {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we own the trampoline - it is going to be dropped as well.
|
||||
// There is no need to decrement its references.
|
||||
if !self.has_shared_trampoline {
|
||||
return;
|
||||
}
|
||||
|
||||
let trampoline = self.trampoline.clone();
|
||||
|
||||
self.send(move |cx| {
|
||||
trampoline
|
||||
.write()
|
||||
.unwrap()
|
||||
.decrement_references(cx.env().to_raw());
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use std::ffi::c_void;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::ManuallyDrop;
|
||||
#[cfg(feature = "napi-6")]
|
||||
use std::sync::Arc;
|
||||
|
||||
use neon_runtime::reference;
|
||||
@ -32,7 +32,9 @@ unsafe impl Sync for NapiRef {}
|
||||
/// A `Root<T>` may be sent across threads, but the referenced object may
|
||||
/// only be accessed on the JavaScript thread that created it.
|
||||
pub struct Root<T> {
|
||||
internal: NapiRef,
|
||||
// `Option` is used to skip `Drop` when `Root::drop` or `Root::into_inner` is used.
|
||||
// It will *always* be `Some` when a user is interacting with `Root`.
|
||||
internal: Option<NapiRef>,
|
||||
#[cfg(feature = "napi-6")]
|
||||
drop_queue: Arc<ThreadsafeFunction<NapiRef>>,
|
||||
_phantom: PhantomData<T>,
|
||||
@ -65,7 +67,7 @@ impl<T: Object> Root<T> {
|
||||
let internal = unsafe { reference::new(env, value.to_raw()) };
|
||||
|
||||
Self {
|
||||
internal: NapiRef(internal as *mut _),
|
||||
internal: Some(NapiRef(internal as *mut _)),
|
||||
#[cfg(feature = "napi-6")]
|
||||
drop_queue: InstanceData::drop_queue(cx),
|
||||
_phantom: PhantomData,
|
||||
@ -86,7 +88,7 @@ impl<T: Object> Root<T> {
|
||||
/// ```
|
||||
pub fn clone<'a, C: Context<'a>>(&self, cx: &mut C) -> Self {
|
||||
let env = cx.env();
|
||||
let internal = self.internal.0 as *mut _;
|
||||
let internal = self.as_napi_ref().0 as *mut _;
|
||||
|
||||
unsafe {
|
||||
reference::reference(env.to_raw(), internal);
|
||||
@ -104,7 +106,7 @@ impl<T: Object> Root<T> {
|
||||
/// object.
|
||||
pub fn drop<'a, C: Context<'a>>(self, cx: &mut C) {
|
||||
let env = cx.env().to_raw();
|
||||
let internal = ManuallyDrop::new(self).internal.0 as *mut _;
|
||||
let internal = self.into_napi_ref().0 as *mut _;
|
||||
|
||||
unsafe {
|
||||
reference::unreference(env, internal);
|
||||
@ -114,7 +116,7 @@ impl<T: Object> Root<T> {
|
||||
/// Return the referenced JavaScript object and allow it to be garbage collected
|
||||
pub fn into_inner<'a, C: Context<'a>>(self, cx: &mut C) -> Handle<'a, T> {
|
||||
let env = cx.env();
|
||||
let internal = ManuallyDrop::new(self).internal.0 as *mut _;
|
||||
let internal = self.into_napi_ref().0 as *mut _;
|
||||
|
||||
let local = unsafe { reference::get(env.to_raw(), internal) };
|
||||
|
||||
@ -130,10 +132,26 @@ impl<T: Object> Root<T> {
|
||||
/// can be used in place of a clone immediately followed by a call to `into_inner`.
|
||||
pub fn to_inner<'a, C: Context<'a>>(&self, cx: &mut C) -> Handle<'a, T> {
|
||||
let env = cx.env();
|
||||
let local = unsafe { reference::get(env.to_raw(), self.internal.0 as *mut _) };
|
||||
let local = unsafe { reference::get(env.to_raw(), self.as_napi_ref().0 as *mut _) };
|
||||
|
||||
Handle::new_internal(T::from_raw(env, local))
|
||||
}
|
||||
|
||||
fn as_napi_ref(&self) -> &NapiRef {
|
||||
self.internal
|
||||
.as_ref()
|
||||
// `unwrap` will not `panic` because `internal` will always be `Some`
|
||||
// until the `Root` is consumed.
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn into_napi_ref(mut self) -> NapiRef {
|
||||
self.internal
|
||||
.take()
|
||||
// `unwrap` will not `panic` because this is the only method place
|
||||
// `internal` is replaced with `None` and it consumes `self`.
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// Allows putting `Root<T>` directly in a container that implements `Finalize`
|
||||
@ -147,6 +165,11 @@ impl<T: Object> Finalize for Root<T> {
|
||||
impl<T> Drop for Root<T> {
|
||||
#[cfg(not(feature = "napi-6"))]
|
||||
fn drop(&mut self) {
|
||||
// If `None`, the `NapiRef` has already been manually dropped
|
||||
if self.internal.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Destructors are called during stack unwinding, prevent a double
|
||||
// panic and instead prefer to leak.
|
||||
if std::thread::panicking() {
|
||||
@ -165,6 +188,9 @@ impl<T> Drop for Root<T> {
|
||||
|
||||
#[cfg(feature = "napi-6")]
|
||||
fn drop(&mut self) {
|
||||
let _ = self.drop_queue.call(self.internal.clone(), None);
|
||||
// If `None`, the `NapiRef` has already been manually dropped
|
||||
if let Some(internal) = self.internal.take() {
|
||||
let _ = self.drop_queue.call(internal.clone(), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,6 +106,9 @@ pub use neon_macros::*;
|
||||
#[cfg(feature = "napi-6")]
|
||||
mod lifecycle;
|
||||
|
||||
#[cfg(all(feature = "napi-4", feature = "event-queue-api"))]
|
||||
mod trampoline;
|
||||
|
||||
#[cfg(all(feature = "legacy-runtime", feature = "napi-1"))]
|
||||
compile_error!("Cannot enable both `legacy-runtime` and `napi-*` features.\n\nTo use `napi-*`, disable `legacy-runtime` by setting `default-features` to `false` in Cargo.toml\nor with cargo's --no-default-features flag.");
|
||||
|
||||
|
||||
@ -10,6 +10,8 @@
|
||||
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
#[cfg(feature = "event-queue-api")]
|
||||
use std::sync::RwLock;
|
||||
|
||||
use neon_runtime::raw::Env;
|
||||
use neon_runtime::reference;
|
||||
@ -17,6 +19,8 @@ use neon_runtime::tsfn::ThreadsafeFunction;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::handle::root::NapiRef;
|
||||
#[cfg(feature = "event-queue-api")]
|
||||
use crate::trampoline::ThreadsafeTrampoline;
|
||||
|
||||
/// `InstanceData` holds Neon data associated with a particular instance of a
|
||||
/// native module. If a module is loaded multiple times (e.g., worker threads), this
|
||||
@ -30,6 +34,10 @@ pub(crate) struct InstanceData {
|
||||
/// given the cost of FFI, this optimization is omitted until the cost of an
|
||||
/// `Arc` is demonstrated as significant.
|
||||
drop_queue: Arc<ThreadsafeFunction<NapiRef>>,
|
||||
|
||||
/// Used in EventQueue to invoke Rust callbacks with Napi environment.
|
||||
#[cfg(feature = "event-queue-api")]
|
||||
threadsafe_trampoline: Arc<RwLock<ThreadsafeTrampoline>>,
|
||||
}
|
||||
|
||||
fn drop_napi_ref(env: Option<Env>, data: NapiRef) {
|
||||
@ -62,8 +70,18 @@ impl InstanceData {
|
||||
queue
|
||||
};
|
||||
|
||||
#[cfg(feature = "event-queue-api")]
|
||||
let threadsafe_trampoline = {
|
||||
let mut trampoline = ThreadsafeTrampoline::new(env);
|
||||
trampoline.decrement_references(env);
|
||||
trampoline
|
||||
};
|
||||
|
||||
let data = InstanceData {
|
||||
drop_queue: Arc::new(drop_queue),
|
||||
|
||||
#[cfg(feature = "event-queue-api")]
|
||||
threadsafe_trampoline: Arc::new(RwLock::new(threadsafe_trampoline)),
|
||||
};
|
||||
|
||||
unsafe { &mut *neon_runtime::lifecycle::set_instance_data(env, data) }
|
||||
@ -73,4 +91,12 @@ impl InstanceData {
|
||||
pub(crate) fn drop_queue<'a, C: Context<'a>>(cx: &mut C) -> Arc<ThreadsafeFunction<NapiRef>> {
|
||||
Arc::clone(&InstanceData::get(cx).drop_queue)
|
||||
}
|
||||
|
||||
/// Helper to return a reference to the `invoke_callback` field of `InstanceData`
|
||||
#[cfg(feature = "event-queue-api")]
|
||||
pub(crate) fn threadsafe_trampoline<'a, C: Context<'a>>(
|
||||
cx: &mut C,
|
||||
) -> Arc<RwLock<ThreadsafeTrampoline>> {
|
||||
Arc::clone(&InstanceData::get(cx).threadsafe_trampoline)
|
||||
}
|
||||
}
|
||||
|
||||
87
src/trampoline.rs
Normal file
87
src/trampoline.rs
Normal file
@ -0,0 +1,87 @@
|
||||
use neon_runtime::raw::Env;
|
||||
use neon_runtime::tsfn::{CallError, ThreadsafeFunction};
|
||||
|
||||
use crate::context::TaskContext;
|
||||
use crate::result::NeonResult;
|
||||
|
||||
pub(crate) type Callback = Box<dyn FnOnce(Env) + Send + 'static>;
|
||||
|
||||
pub(crate) struct ThreadsafeTrampoline {
|
||||
tsfn: ThreadsafeFunction<Callback>,
|
||||
ref_count: u32,
|
||||
}
|
||||
|
||||
impl ThreadsafeTrampoline {
|
||||
/// Creates an unbounded queue for scheduling closures on the JavaScript
|
||||
/// main thread
|
||||
pub(crate) fn new(env: Env) -> Self {
|
||||
let tsfn = unsafe { ThreadsafeFunction::new(env, Self::callback) };
|
||||
|
||||
Self {
|
||||
tsfn: tsfn,
|
||||
ref_count: 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedules a closure to execute on the JavaScript thread that created
|
||||
/// this ThreadsafeTrampoline.
|
||||
/// Returns an `Error` if the task could not be scheduled.
|
||||
pub(crate) fn try_send<F>(&self, f: F) -> Result<(), CallError<Callback>>
|
||||
where
|
||||
F: FnOnce(TaskContext) -> NeonResult<()> + Send + 'static,
|
||||
{
|
||||
let callback = Box::new(move |env| {
|
||||
let env = unsafe { std::mem::transmute(env) };
|
||||
|
||||
// Note: It is sufficient to use `TaskContext`'s `InheritedHandleScope` because
|
||||
// N-API creates a `HandleScope` before calling the callback.
|
||||
TaskContext::with_context(env, move |cx| {
|
||||
let _ = f(cx);
|
||||
});
|
||||
});
|
||||
|
||||
self.tsfn.call(callback, None)
|
||||
}
|
||||
|
||||
/// References a trampoline to prevent exiting the event loop until it has been dropped. (Default)
|
||||
/// Safety: `Env` must be valid for the current thread
|
||||
/// _Not idempotent_
|
||||
pub(crate) fn increment_references(&mut self, env: Env) {
|
||||
self.ref_count += 1;
|
||||
if self.ref_count != 1 {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
self.tsfn.reference(env);
|
||||
}
|
||||
}
|
||||
|
||||
/// Unreferences a trampoline to allow exiting the event loop before it has been dropped.
|
||||
/// Safety: `Env` must be valid for the current thread
|
||||
/// _Not idempotent_
|
||||
pub(crate) fn decrement_references(&mut self, env: Env) {
|
||||
assert!(
|
||||
self.ref_count > 0,
|
||||
"ThreadsafeTrampoline reference underflow"
|
||||
);
|
||||
self.ref_count -= 1;
|
||||
|
||||
if self.ref_count != 0 {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
self.tsfn.unref(env);
|
||||
}
|
||||
}
|
||||
|
||||
// Monomorphized trampoline function for calling the user provided closure
|
||||
fn callback(env: Option<Env>, callback: Callback) {
|
||||
if let Some(env) = env {
|
||||
callback(env);
|
||||
} else {
|
||||
crate::context::internal::IS_RUNNING.with(|v| {
|
||||
*v.borrow_mut() = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user