Skip to content

Commit

Permalink
Merge pull request #979 from neon-bindings/kv/unnecessary-send
Browse files Browse the repository at this point in the history
Relax Send constraints
  • Loading branch information
kjvalencik committed Apr 14, 2023
2 parents a4c17d8 + 36a57a3 commit a40c012
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 40 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/neon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ neon-macros = { version = "=1.0.0-alpha.2", path = "../neon-macros" }
aquamarine = { version = "0.1.11", optional = true }
easy-cast = { version = "0.5.1", optional = true }
doc-comment = { version = "0.3.3", optional = true }
send_wrapper = "0.6"

[dependencies.tokio]
version = "1.24.2"
Expand Down
2 changes: 1 addition & 1 deletion crates/neon/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ pub trait Context<'a>: ContextInternal<'a> {
/// Ok(point)
/// }
/// ```
fn boxed<U: Finalize + Send + 'static>(&mut self, v: U) -> Handle<'a, JsBox<U>> {
fn boxed<U: Finalize + 'static>(&mut self, v: U) -> Handle<'a, JsBox<U>> {
JsBox::new(self, v)
}

Expand Down
12 changes: 6 additions & 6 deletions crates/neon/src/event/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ where
/// of the `execute` callback
pub fn and_then<F>(self, complete: F)
where
F: FnOnce(TaskContext, O) -> NeonResult<()> + Send + 'static,
F: FnOnce(TaskContext, O) -> NeonResult<()> + 'static,
{
let env = self.cx.env();
let execute = self.execute;
Expand All @@ -65,7 +65,7 @@ where
pub fn promise<V, F>(self, complete: F) -> Handle<'a, JsPromise>
where
V: Value,
F: FnOnce(TaskContext, O) -> JsResult<V> + Send + 'static,
F: FnOnce(TaskContext, O) -> JsResult<V> + 'static,
{
let env = self.cx.env();
let (deferred, promise) = JsPromise::new(self.cx);
Expand All @@ -82,7 +82,7 @@ fn schedule<I, O, D>(env: Env, input: I, data: D)
where
I: FnOnce() -> O + Send + 'static,
O: Send + 'static,
D: FnOnce(TaskContext, O) -> NeonResult<()> + Send + 'static,
D: FnOnce(TaskContext, O) -> NeonResult<()> + 'static,
{
unsafe {
async_work::schedule(env.to_raw(), input, execute::<I, O>, complete::<O, D>, data);
Expand All @@ -100,7 +100,7 @@ where
fn complete<O, D>(env: raw::Env, output: thread::Result<O>, callback: D)
where
O: Send + 'static,
D: FnOnce(TaskContext, O) -> NeonResult<()> + Send + 'static,
D: FnOnce(TaskContext, O) -> NeonResult<()> + 'static,
{
let output = output.unwrap_or_else(|panic| {
// If a panic was caught while executing the task on the Node Worker
Expand All @@ -118,7 +118,7 @@ fn schedule_promise<I, O, D, V>(env: Env, input: I, complete: D, deferred: Defer
where
I: FnOnce() -> O + Send + 'static,
O: Send + 'static,
D: FnOnce(TaskContext, O) -> JsResult<V> + Send + 'static,
D: FnOnce(TaskContext, O) -> JsResult<V> + 'static,
V: Value,
{
unsafe {
Expand All @@ -138,7 +138,7 @@ fn complete_promise<O, D, V>(
(complete, deferred): (D, Deferred),
) where
O: Send + 'static,
D: FnOnce(TaskContext, O) -> JsResult<V> + Send + 'static,
D: FnOnce(TaskContext, O) -> JsResult<V> + 'static,
V: Value,
{
let env = env.into();
Expand Down
12 changes: 7 additions & 5 deletions crates/neon/src/sys/async_work.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ use std::{
ptr, thread,
};

use super::{bindings as napi, no_panic::FailureBoundary, raw::Env};
use super::{
bindings as napi, debug_send_wrapper::DebugSendWrapper, no_panic::FailureBoundary, raw::Env,
};

const BOUNDARY: FailureBoundary = FailureBoundary {
both: "A panic and exception occurred while executing a `neon::event::TaskBuilder` task",
Expand All @@ -40,13 +42,13 @@ pub unsafe fn schedule<I, O, D>(
) where
I: Send + 'static,
O: Send + 'static,
D: Send + 'static,
D: 'static,
{
let mut data = Box::new(Data {
state: State::Input(input),
execute,
complete,
data,
data: DebugSendWrapper::new(data),
// Work is initialized as a null pointer, but set by `create_async_work`
// `data` must not be used until this value has been set.
work: ptr::null_mut(),
Expand Down Expand Up @@ -85,7 +87,7 @@ struct Data<I, O, D> {
state: State<I, O>,
execute: Execute<I, O>,
complete: Complete<O, D>,
data: D,
data: DebugSendWrapper<D>,
work: napi::AsyncWork,
}

Expand Down Expand Up @@ -170,7 +172,7 @@ unsafe extern "C" fn call_complete<I, O, D>(env: Env, status: napi::Status, data
};

match status {
napi::Status::Ok => complete(env, output, data),
napi::Status::Ok => complete(env, output, data.take()),
napi::Status::Cancelled => {}
_ => assert_eq!(status, napi::Status::Ok),
}
Expand Down
56 changes: 56 additions & 0 deletions crates/neon/src/sys/debug_send_wrapper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//! Wrapper that ensures types are always used from the same thread
//! in debug builds. It is a zero-cost in release builds.

pub(super) use wrapper::DebugSendWrapper;

#[cfg(debug_assertions)]
mod wrapper {
use std::ops::Deref;

#[repr(transparent)]
pub struct DebugSendWrapper<T>(send_wrapper::SendWrapper<T>);

impl<T> DebugSendWrapper<T> {
pub fn new(value: T) -> Self {
Self(send_wrapper::SendWrapper::new(value))
}

pub fn take(self) -> T {
self.0.take()
}
}

impl<T> Deref for DebugSendWrapper<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
&self.0
}
}
}

#[cfg(not(debug_assertions))]
mod wrapper {
use std::ops::Deref;

#[repr(transparent)]
pub struct DebugSendWrapper<T>(T);

impl<T> DebugSendWrapper<T> {
pub fn new(value: T) -> Self {
Self(value)
}

pub fn take(self) -> T {
self.0
}
}

impl<T> Deref for DebugSendWrapper<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
&self.0
}
}
}
18 changes: 11 additions & 7 deletions crates/neon/src/sys/external.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ use std::mem::MaybeUninit;

use super::{
bindings as napi,
debug_send_wrapper::DebugSendWrapper,
raw::{Env, Local},
};

/// `finalize_external` is invoked immediately before a `napi_external` is garbage collected
extern "C" fn finalize_external<T: Send + 'static>(
extern "C" fn finalize_external<T: 'static>(
env: Env,
// Raw pointer to a `Box<T>` stored by a `napi_external`
data: *mut std::ffi::c_void,
Expand All @@ -15,10 +16,10 @@ extern "C" fn finalize_external<T: Send + 'static>(
hint: *mut std::ffi::c_void,
) {
unsafe {
let data = Box::<T>::from_raw(data as *mut _);
let data = Box::<DebugSendWrapper<T>>::from_raw(data as *mut _);
let finalizer: fn(Env, T) = std::mem::transmute(hint as *const ());

finalizer(env, *data);
finalizer(env, data.take());
}
}

Expand All @@ -27,7 +28,7 @@ extern "C" fn finalize_external<T: Send + 'static>(
/// module. Calling `deref` with an external created by another native module,
/// even another neon module, is undefined behavior.
/// <https://github.com/neon-bindings/neon/issues/591>
pub unsafe fn deref<T: Send + 'static>(env: Env, local: Local) -> Option<*const T> {
pub unsafe fn deref<T: 'static>(env: Env, local: Local) -> Option<*const T> {
let mut result = MaybeUninit::uninit();
let status = napi::typeof_value(env, local, result.as_mut_ptr());

Expand All @@ -53,12 +54,15 @@ pub unsafe fn deref<T: Send + 'static>(env: Env, local: Local) -> Option<*const

assert_eq!(status, napi::Status::Ok);

Some(result.assume_init() as *const _)
let v = result.assume_init();
let v = &**v.cast_const().cast::<DebugSendWrapper<T>>() as *const T;

Some(v)
}

/// Creates a `napi_external` from a Rust type
pub unsafe fn create<T: Send + 'static>(env: Env, v: T, finalizer: fn(Env, T)) -> Local {
let v = Box::new(v);
pub unsafe fn create<T: 'static>(env: Env, v: T, finalizer: fn(Env, T)) -> Local {
let v = Box::new(DebugSendWrapper::new(v));
let mut result = MaybeUninit::uninit();

let status = napi::create_external(
Expand Down
1 change: 1 addition & 0 deletions crates/neon/src/sys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ pub(crate) mod tsfn;
#[cfg(feature = "napi-5")]
pub(crate) mod date;

mod debug_send_wrapper;
#[cfg(feature = "napi-6")]
pub(crate) mod lifecycle;

Expand Down
3 changes: 2 additions & 1 deletion crates/neon/src/sys/no_panic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use std::{

use super::{
bindings as napi,
debug_send_wrapper::DebugSendWrapper,
error::fatal_error,
raw::{Env, Local},
};
Expand Down Expand Up @@ -256,7 +257,7 @@ unsafe fn external_from_panic(env: Env, panic: Panic) -> Local {
let mut result = MaybeUninit::uninit();
let status = napi::create_external(
env,
Box::into_raw(Box::new(panic)).cast(),
Box::into_raw(Box::new(DebugSendWrapper::new(panic))).cast(),
Some(finalize_panic),
ptr::null_mut(),
result.as_mut_ptr(),
Expand Down
35 changes: 15 additions & 20 deletions crates/neon/src/types_impl/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ use crate::{
types::{boxed::private::JsBoxInner, private::ValueInternal, Value},
};

type BoxAny = Box<dyn Any + Send + 'static>;
type BoxAny = Box<dyn Any + 'static>;

mod private {
pub struct JsBoxInner<T: Send + 'static> {
pub struct JsBoxInner<T: 'static> {
pub(super) local: crate::sys::raw::Local,
// Cached raw pointer to the data contained in the `JsBox`. This value is
// required to implement `Deref` for `JsBox`. Unlike most `Js` types, `JsBox`
Expand Down Expand Up @@ -141,15 +141,15 @@ mod private {
/// Ok(cx.string(greeting))
/// }
#[repr(transparent)]
pub struct JsBox<T: Send + 'static>(JsBoxInner<T>);
pub struct JsBox<T: 'static>(JsBoxInner<T>);

impl<T: Send + 'static> std::fmt::Debug for JsBoxInner<T> {
impl<T: 'static> std::fmt::Debug for JsBoxInner<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "JsBox<{}>", std::any::type_name::<T>())
}
}

impl<T: Send + 'static> std::fmt::Debug for JsBox<T> {
impl<T: 'static> std::fmt::Debug for JsBox<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(&self.0, f)
}
Expand All @@ -162,7 +162,7 @@ unsafe fn maybe_external_deref<'a>(env: Env, local: raw::Local) -> Option<&'a Bo
}

// Custom `Clone` implementation since `T` might not be `Clone`
impl<T: Send + 'static> Clone for JsBoxInner<T> {
impl<T: 'static> Clone for JsBoxInner<T> {
fn clone(&self) -> Self {
Self {
local: self.local,
Expand All @@ -171,21 +171,21 @@ impl<T: Send + 'static> Clone for JsBoxInner<T> {
}
}

impl<T: Send + 'static> Object for JsBox<T> {}
impl<T: 'static> Object for JsBox<T> {}

impl<T: Send + 'static> Copy for JsBoxInner<T> {}
impl<T: 'static> Copy for JsBoxInner<T> {}

impl<T: Send + 'static> Value for JsBox<T> {}
impl<T: 'static> Value for JsBox<T> {}

unsafe impl<T: Send + 'static> TransparentNoCopyWrapper for JsBox<T> {
unsafe impl<T: 'static> TransparentNoCopyWrapper for JsBox<T> {
type Inner = JsBoxInner<T>;

fn into_inner(self) -> Self::Inner {
self.0
}
}

impl<T: Send + 'static> ValueInternal for JsBox<T> {
impl<T: 'static> ValueInternal for JsBox<T> {
fn name() -> String {
any::type_name::<Self>().to_string()
}
Expand Down Expand Up @@ -219,30 +219,25 @@ impl<T: Send + 'static> ValueInternal for JsBox<T> {
}
}

/// Values contained by a `JsBox` must be `Finalize + Send + 'static`
/// Values contained by a `JsBox` must be `Finalize + 'static`
///
/// ### `Finalize`
///
/// The `sys::prelude::Finalize` trait provides a `finalize` method that will be called
/// immediately before the `JsBox` is garbage collected.
///
/// ### `Send`
///
/// `JsBox` may be moved across threads. It is important to guarantee that the
/// contents is also safe to move across threads.
///
/// ### `'static'
///
/// The lifetime of a `JsBox` is managed by the JavaScript garbage collector. Since Rust
/// is unable to verify the lifetime of the contents, references must be valid for the
/// entire duration of the program. This does not mean that the `JsBox` will be valid
/// until the application terminates, only that its lifetime is indefinite.
impl<T: Finalize + Send + 'static> JsBox<T> {
impl<T: Finalize + 'static> JsBox<T> {
/// Constructs a new `JsBox` containing `value`.
pub fn new<'a, C>(cx: &mut C, value: T) -> Handle<'a, JsBox<T>>
where
C: Context<'a>,
T: Send + 'static,
T: 'static,
{
// This function will execute immediately before the `JsBox` is garbage collected.
// It unwraps the `napi_external`, downcasts the `BoxAny` and moves the type
Expand All @@ -264,7 +259,7 @@ impl<T: Finalize + Send + 'static> JsBox<T> {
}
}

impl<T: Send + 'static> Deref for JsBox<T> {
impl<T: 'static> Deref for JsBox<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
Expand Down

0 comments on commit a40c012

Please sign in to comment.