Compare commits
6 Commits
main
...
kv/experim
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e503caa2b9 | ||
|
|
77f1ffdf04 | ||
|
|
a70bc8b50c | ||
|
|
e67b1d1411 | ||
|
|
fad0f694f4 | ||
|
|
74b8914334 |
@ -67,5 +67,6 @@ members = [
|
||||
"test/static",
|
||||
"test/electron",
|
||||
"test/dynamic/native",
|
||||
"test/napi"
|
||||
"test/napi",
|
||||
"test/tokio",
|
||||
]
|
||||
|
||||
@ -201,4 +201,76 @@ generate!(extern "C" {
|
||||
escapee: Value,
|
||||
result: *mut Value,
|
||||
) -> Status;
|
||||
|
||||
fn create_reference(
|
||||
env: Env,
|
||||
value: Value,
|
||||
initial_ref_count: u32,
|
||||
result: *mut Ref,
|
||||
) -> Status;
|
||||
|
||||
fn reference_ref(env: Env, reference: Ref, result: *mut u32) -> Status;
|
||||
|
||||
fn reference_unref(env: Env, reference: Ref, result: *mut u32) -> Status;
|
||||
|
||||
fn get_reference_value(
|
||||
env: Env,
|
||||
reference: Ref,
|
||||
result: *mut Value,
|
||||
) -> Status;
|
||||
|
||||
fn create_threadsafe_function(
|
||||
env: Env,
|
||||
func: Value,
|
||||
async_resource: Value,
|
||||
async_resource_name: Value,
|
||||
max_queue_size: usize,
|
||||
initial_thread_count: usize,
|
||||
thread_finalize_data: *mut c_void,
|
||||
thread_finalize_cb: Finalize,
|
||||
context: *mut c_void,
|
||||
call_js_cb: ThreadsafeFunctionCallJs,
|
||||
result: *mut ThreadsafeFunction,
|
||||
) -> Status;
|
||||
|
||||
fn call_threadsafe_function(
|
||||
func: ThreadsafeFunction,
|
||||
data: *mut c_void,
|
||||
is_blocking: ThreadsafeFunctionCallMode,
|
||||
) -> Status;
|
||||
|
||||
fn release_threadsafe_function(
|
||||
func: ThreadsafeFunction,
|
||||
mode: ThreadsafeFunctionReleaseMode,
|
||||
) -> Status;
|
||||
|
||||
fn ref_threadsafe_function(
|
||||
env: Env,
|
||||
func: ThreadsafeFunction,
|
||||
) -> Status;
|
||||
|
||||
fn unref_threadsafe_function(
|
||||
env: Env,
|
||||
func: ThreadsafeFunction,
|
||||
) -> Status;
|
||||
|
||||
fn create_promise(
|
||||
env: Env,
|
||||
deferred: *mut Deferred,
|
||||
promise: *mut Value,
|
||||
) -> Status;
|
||||
|
||||
fn resolve_deferred(
|
||||
env: Env,
|
||||
deferred: Deferred,
|
||||
resolution: Value,
|
||||
) -> Status;
|
||||
|
||||
fn reject_deferred(
|
||||
env: Env,
|
||||
deferred: Deferred,
|
||||
rejection: Value,
|
||||
) -> Status;
|
||||
|
||||
fn is_promise(env: Env, value: Value, is_promise: *mut bool) -> Status;
|
||||
});
|
||||
|
||||
@ -22,6 +22,8 @@ pub struct CallbackInfo__ {
|
||||
_unused: [u8; 0],
|
||||
}
|
||||
|
||||
pub type CallbackInfo = *mut CallbackInfo__;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct EscapableHandleScope__ {
|
||||
@ -34,9 +36,32 @@ pub type EscapableHandleScope = *mut EscapableHandleScope__;
|
||||
pub struct HandleScope__ {
|
||||
_unused: [u8; 0],
|
||||
}
|
||||
|
||||
pub type HandleScope = *mut HandleScope__;
|
||||
|
||||
pub type CallbackInfo = *mut CallbackInfo__;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Ref__ {
|
||||
_unused: [u8; 0],
|
||||
}
|
||||
|
||||
pub type Ref = *mut Ref__;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ThreadsafeFunction__ {
|
||||
_unused: [u8; 0],
|
||||
}
|
||||
|
||||
pub type ThreadsafeFunction = *mut ThreadsafeFunction__;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Deferred__ {
|
||||
_unused: [u8; 0],
|
||||
}
|
||||
|
||||
pub type Deferred = *mut Deferred__;
|
||||
|
||||
pub(crate) type Callback = Option<
|
||||
unsafe extern "C" fn(env: Env, info: CallbackInfo) -> Value,
|
||||
@ -50,10 +75,19 @@ pub(crate) type Finalize = Option<
|
||||
),
|
||||
>;
|
||||
|
||||
pub type ThreadsafeFunctionCallJs = Option<
|
||||
unsafe extern "C" fn(
|
||||
env: Env,
|
||||
js_callback: Value,
|
||||
context: *mut c_void,
|
||||
data: *mut c_void,
|
||||
),
|
||||
>;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) enum Status {
|
||||
pub enum Status {
|
||||
Ok = 0,
|
||||
InvalidArg = 1,
|
||||
ObjectExpected = 2,
|
||||
@ -110,6 +144,21 @@ pub enum KeyConversion {
|
||||
NumbersToStrings = 1,
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ThreadsafeFunctionCallMode {
|
||||
NonBlocking = 0,
|
||||
Blocking = 1,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ThreadsafeFunctionReleaseMode {
|
||||
Release = 0,
|
||||
Abort = 1,
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct KeyFilter(pub ::std::os::raw::c_uint);
|
||||
|
||||
@ -16,6 +16,9 @@ pub mod string;
|
||||
pub mod tag;
|
||||
pub mod task;
|
||||
pub mod handler;
|
||||
pub mod reference;
|
||||
pub mod tsfn;
|
||||
pub mod promise;
|
||||
|
||||
mod bindings;
|
||||
pub use bindings::*;
|
||||
|
||||
153
crates/neon-runtime/src/napi/promise.rs
Normal file
153
crates/neon-runtime/src/napi/promise.rs
Normal file
@ -0,0 +1,153 @@
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
use crate::napi::bindings as napi;
|
||||
use crate::raw::{Env, Local};
|
||||
|
||||
use super::CallbackInfo;
|
||||
|
||||
pub unsafe fn new(env: Env) -> (napi::Deferred, Local) {
|
||||
let mut deferred = MaybeUninit::uninit();
|
||||
let mut promise = MaybeUninit::uninit();
|
||||
|
||||
assert_eq!(
|
||||
napi::create_promise(env, deferred.as_mut_ptr(), promise.as_mut_ptr()),
|
||||
napi::Status::Ok,
|
||||
);
|
||||
|
||||
(deferred.assume_init(), promise.assume_init())
|
||||
}
|
||||
|
||||
pub unsafe fn resolve(env: Env, deferred: napi::Deferred, value: Local) {
|
||||
assert_eq!(napi::resolve_deferred(env, deferred, value), napi::Status::Ok);
|
||||
}
|
||||
|
||||
pub unsafe fn reject(env: Env, deferred: napi::Deferred, value: Local) {
|
||||
assert_eq!(napi::reject_deferred(env, deferred, value), napi::Status::Ok);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn callback_wrapper<F>(
|
||||
env: Env,
|
||||
info: CallbackInfo,
|
||||
) -> Local
|
||||
where
|
||||
F: FnOnce(Env, Local) + Send + 'static,
|
||||
{
|
||||
let mut argc = 1;
|
||||
let mut argv = [std::ptr::null_mut()];
|
||||
let mut this = MaybeUninit::uninit();
|
||||
let mut data = MaybeUninit::uninit();
|
||||
|
||||
assert_eq!(
|
||||
napi::get_cb_info(
|
||||
env,
|
||||
info,
|
||||
&mut argc,
|
||||
argv.as_mut_ptr(),
|
||||
this.as_mut_ptr(),
|
||||
data.as_mut_ptr(),
|
||||
),
|
||||
napi::Status::Ok,
|
||||
);
|
||||
|
||||
debug_assert_eq!(argc, 1);
|
||||
|
||||
let cb = Box::from_raw(data.assume_init() as *mut F);
|
||||
|
||||
cb(env, argv[0]);
|
||||
|
||||
let mut undefined = MaybeUninit::uninit();
|
||||
|
||||
assert_eq!(
|
||||
napi::get_undefined(env, undefined.as_mut_ptr()),
|
||||
napi::Status::Ok,
|
||||
);
|
||||
|
||||
undefined.assume_init()
|
||||
}
|
||||
|
||||
unsafe fn future_callback<F>(env: Env, f: F) -> Local
|
||||
where
|
||||
F: FnOnce(Env, Local) + Send + 'static,
|
||||
{
|
||||
let mut local = MaybeUninit::uninit();
|
||||
|
||||
assert_eq!(
|
||||
napi::create_function(
|
||||
env,
|
||||
std::ptr::null(),
|
||||
0,
|
||||
Some(callback_wrapper::<F>),
|
||||
Box::into_raw(Box::new(f)) as *mut _,
|
||||
local.as_mut_ptr(),
|
||||
),
|
||||
napi::Status::Ok,
|
||||
);
|
||||
|
||||
local.assume_init()
|
||||
}
|
||||
|
||||
unsafe fn get_key(env: Env, o: Local, k: &str) -> Local {
|
||||
let mut key = MaybeUninit::uninit();
|
||||
|
||||
assert_eq!(
|
||||
napi::create_string_utf8(
|
||||
env,
|
||||
k.as_ptr() as *const _,
|
||||
k.len(),
|
||||
key.as_mut_ptr(),
|
||||
),
|
||||
napi::Status::Ok,
|
||||
);
|
||||
|
||||
let mut prop = MaybeUninit::uninit();
|
||||
|
||||
assert_eq!(
|
||||
napi::get_property(env, o, key.assume_init(), prop.as_mut_ptr()),
|
||||
napi::Status::Ok,
|
||||
);
|
||||
|
||||
prop.assume_init()
|
||||
}
|
||||
|
||||
unsafe fn call_promise_method(
|
||||
env: Env,
|
||||
promise: Local,
|
||||
method: &str,
|
||||
arg: Local,
|
||||
) {
|
||||
let mut result = MaybeUninit::uninit();
|
||||
let argv = [arg];
|
||||
|
||||
assert_eq!(
|
||||
napi::call_function(
|
||||
env,
|
||||
promise,
|
||||
get_key(env, promise, method),
|
||||
1,
|
||||
argv.as_ptr(),
|
||||
result.as_mut_ptr(),
|
||||
),
|
||||
napi::Status::Ok,
|
||||
);
|
||||
}
|
||||
|
||||
pub unsafe fn adapter<Resolve, Reject>(
|
||||
env: Env,
|
||||
maybe_promise: Local,
|
||||
resolve: Resolve,
|
||||
reject: Reject,
|
||||
)
|
||||
where
|
||||
Resolve: FnOnce(Env, Local) + Send + 'static,
|
||||
Reject: FnOnce(Env, Local) + Send + 'static,
|
||||
{
|
||||
let (deferred, promise) = new(env);
|
||||
|
||||
self::resolve(env, deferred, maybe_promise);
|
||||
|
||||
let resolve = future_callback(env, resolve);
|
||||
let reject = future_callback(env, reject);
|
||||
|
||||
call_promise_method(env, promise, "then", resolve);
|
||||
call_promise_method(env, promise, "catch", reject);
|
||||
}
|
||||
49
crates/neon-runtime/src/napi/reference.rs
Normal file
49
crates/neon-runtime/src/napi/reference.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
use crate::napi::bindings as napi;
|
||||
|
||||
use crate::raw::{Local, Env};
|
||||
|
||||
pub unsafe fn new(env: Env, value: Local) -> napi::Ref {
|
||||
let mut result = MaybeUninit::uninit();
|
||||
|
||||
assert_eq!(
|
||||
napi::create_reference(env, value, 1, result.as_mut_ptr()),
|
||||
napi::Status::Ok,
|
||||
);
|
||||
|
||||
result.assume_init()
|
||||
}
|
||||
|
||||
pub unsafe fn reference(env: Env, value: napi::Ref) -> usize {
|
||||
let mut result = MaybeUninit::uninit();
|
||||
|
||||
assert_eq!(
|
||||
napi::reference_ref(env, value, result.as_mut_ptr()),
|
||||
napi::Status::Ok,
|
||||
);
|
||||
|
||||
result.assume_init() as usize
|
||||
}
|
||||
|
||||
pub unsafe fn unreference(env: Env, value: napi::Ref) -> usize {
|
||||
let mut result = MaybeUninit::uninit();
|
||||
|
||||
assert_eq!(
|
||||
napi::reference_unref(env, value, result.as_mut_ptr()),
|
||||
napi::Status::Ok,
|
||||
);
|
||||
|
||||
result.assume_init() as usize
|
||||
}
|
||||
|
||||
pub unsafe fn get(env: Env, value: napi::Ref) -> Local {
|
||||
let mut result = MaybeUninit::uninit();
|
||||
|
||||
assert_eq!(
|
||||
napi::get_reference_value(env, value, result.as_mut_ptr()),
|
||||
napi::Status::Ok,
|
||||
);
|
||||
|
||||
result.assume_init()
|
||||
}
|
||||
@ -64,3 +64,9 @@ pub unsafe extern "C" fn is_arraybuffer(env: Env, val: Local) -> bool {
|
||||
assert_eq!(napi::is_arraybuffer(env, val, &mut result as *mut _), napi::Status::Ok);
|
||||
result
|
||||
}
|
||||
|
||||
pub unsafe fn is_promise(env: Env, val: Local) -> bool {
|
||||
let mut result = false;
|
||||
assert_eq!(napi::is_promise(env, val, &mut result as *mut _), napi::Status::Ok);
|
||||
result
|
||||
}
|
||||
|
||||
187
crates/neon-runtime/src/napi/tsfn.rs
Normal file
187
crates/neon-runtime/src/napi/tsfn.rs
Normal file
@ -0,0 +1,187 @@
|
||||
use std::ffi::c_void;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
use crate::napi::bindings as napi;
|
||||
use crate::raw::{Local, Env};
|
||||
|
||||
unsafe fn string(env: Env, s: impl AsRef<str>) -> Local {
|
||||
let s = s.as_ref();
|
||||
let mut result = MaybeUninit::uninit();
|
||||
|
||||
assert_eq!(
|
||||
napi::create_string_utf8(
|
||||
env,
|
||||
s.as_bytes().as_ptr() as *const _,
|
||||
s.len(),
|
||||
result.as_mut_ptr(),
|
||||
),
|
||||
napi::Status::Ok,
|
||||
);
|
||||
|
||||
result.assume_init()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Tsfn(napi::ThreadsafeFunction);
|
||||
|
||||
unsafe impl Send for Tsfn {}
|
||||
unsafe impl Sync for Tsfn {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ThreadsafeFunction<T> {
|
||||
tsfn: Tsfn,
|
||||
callback: fn(Env, T),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Callback<T> {
|
||||
callback: fn(Env, T),
|
||||
data: T,
|
||||
}
|
||||
|
||||
pub struct CallError<T> {
|
||||
kind: napi::Status,
|
||||
data: T,
|
||||
}
|
||||
|
||||
impl<T> CallError<T> {
|
||||
pub fn kind(&self) -> napi::Status {
|
||||
self.kind
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> T {
|
||||
self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + 'static> ThreadsafeFunction<T> {
|
||||
// Caller must maintain that `Env` is valid for the current thread
|
||||
pub unsafe fn new(
|
||||
env: Env,
|
||||
callback: fn(Env, T),
|
||||
) -> Self {
|
||||
Self::with_capacity(env, 0, callback)
|
||||
}
|
||||
|
||||
pub unsafe fn with_capacity(
|
||||
env: Env,
|
||||
max_queue_size: usize,
|
||||
callback: fn(Env, T),
|
||||
) -> Self {
|
||||
let mut result = MaybeUninit::uninit();
|
||||
|
||||
assert_eq!(
|
||||
napi::create_threadsafe_function(
|
||||
env,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
string(env, "neon threadsafe function"),
|
||||
max_queue_size,
|
||||
// Always set the reference count to 1. Prefer using
|
||||
// Rust `Arc` to maintain the struct.
|
||||
1,
|
||||
std::ptr::null_mut(),
|
||||
None,
|
||||
std::ptr::null_mut(),
|
||||
Some(Self::callback),
|
||||
result.as_mut_ptr(),
|
||||
),
|
||||
napi::Status::Ok,
|
||||
);
|
||||
|
||||
Self {
|
||||
tsfn: Tsfn(result.assume_init()),
|
||||
callback,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call(
|
||||
&self,
|
||||
data: T,
|
||||
is_blocking: Option<napi::ThreadsafeFunctionCallMode>,
|
||||
) -> Result<(), CallError<T>> {
|
||||
let is_blocking = is_blocking
|
||||
.unwrap_or(napi::ThreadsafeFunctionCallMode::Blocking);
|
||||
|
||||
let callback = Box::into_raw(Box::new(Callback {
|
||||
callback: self.callback,
|
||||
data,
|
||||
}));
|
||||
|
||||
let status = unsafe {
|
||||
napi::call_threadsafe_function(
|
||||
self.tsfn.0,
|
||||
callback as *mut _,
|
||||
is_blocking,
|
||||
)
|
||||
};
|
||||
|
||||
if status == napi::Status::Ok {
|
||||
Ok(())
|
||||
} else {
|
||||
// If the call failed, the callback won't execute
|
||||
let callback = unsafe { Box::from_raw(callback) };
|
||||
|
||||
Err(CallError {
|
||||
kind: status,
|
||||
data: callback.data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn reference(&mut self, env: Env) {
|
||||
assert_eq!(
|
||||
napi::ref_threadsafe_function(
|
||||
env,
|
||||
self.tsfn.0,
|
||||
),
|
||||
napi::Status::Ok,
|
||||
);
|
||||
}
|
||||
|
||||
pub unsafe fn unref(&mut self, env: Env) {
|
||||
assert_eq!(
|
||||
napi::unref_threadsafe_function(
|
||||
env,
|
||||
self.tsfn.0,
|
||||
),
|
||||
napi::Status::Ok,
|
||||
);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn callback(
|
||||
env: Env,
|
||||
_js_callback: napi::Value,
|
||||
_context: *mut c_void,
|
||||
data: *mut c_void,
|
||||
) {
|
||||
// Event loop may be terminated
|
||||
if data.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Callback {
|
||||
callback,
|
||||
data,
|
||||
} = *Box::from_raw(data as *mut Callback<T>);
|
||||
|
||||
// Event loop has terminated
|
||||
if env.is_null() {
|
||||
eprintln!("This is surprising");
|
||||
return;
|
||||
}
|
||||
|
||||
callback(env, data);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for ThreadsafeFunction<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
napi::release_threadsafe_function(
|
||||
self.tsfn.0,
|
||||
napi::ThreadsafeFunctionReleaseMode::Release,
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -14,7 +14,9 @@ use borrow::{Ref, RefMut, Borrow, BorrowMut};
|
||||
use borrow::internal::Ledger;
|
||||
use context::internal::Env;
|
||||
use handle::{Managed, Handle};
|
||||
use types::{JsValue, Value, JsObject, JsArray, JsFunction, JsBoolean, JsNumber, JsString, StringResult, JsNull, JsUndefined};
|
||||
#[cfg(feature = "napi-runtime")]
|
||||
use sync::EventQueue;
|
||||
use types::{JsValue, Value, JsObject, JsArray, JsFunction, JsBoolean, JsNumber, JsString, StringResult, JsNull, JsUndefined, JsPromise, Deferred};
|
||||
#[cfg(feature = "napi-runtime")]
|
||||
use types::boxed::{Finalize, JsBox};
|
||||
use types::binary::{JsArrayBuffer, JsBuffer};
|
||||
@ -379,6 +381,17 @@ pub trait Context<'a>: ContextInternal<'a> {
|
||||
fn boxed<U: Finalize + Send + 'static>(&mut self, v: U) -> Handle<'a, JsBox<U>> {
|
||||
JsBox::new(self, v)
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi-runtime")]
|
||||
/// Creates an unbounded queue of events to be executed on a JavaScript thread
|
||||
fn event_queue(&mut self) -> EventQueue {
|
||||
EventQueue::new(self)
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi-runtime")]
|
||||
fn promise(&mut self) -> (Handle<'a, JsPromise>, Deferred) {
|
||||
JsPromise::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A view of the JS engine in the context of top-level initialization of a Neon module.
|
||||
@ -596,6 +609,13 @@ impl<'a> TaskContext<'a> {
|
||||
f(TaskContext { scope })
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi-runtime")]
|
||||
pub(crate) fn with_context<T, F: for<'b> FnOnce(TaskContext<'b>) -> T>(env: Env, f: F) -> T {
|
||||
Scope::with(env, |scope| {
|
||||
f(TaskContext { scope })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ContextInternal<'a> for TaskContext<'a> {
|
||||
|
||||
@ -23,6 +23,8 @@ pub mod task;
|
||||
pub mod event;
|
||||
pub mod meta;
|
||||
pub mod prelude;
|
||||
#[cfg(feature = "napi-runtime")]
|
||||
pub mod sync;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod macro_internal;
|
||||
|
||||
@ -90,6 +90,7 @@ mod traits {
|
||||
use context::Context;
|
||||
use context::internal::Env;
|
||||
use result::{NeonResult, JsResult, Throw};
|
||||
use sync::Root;
|
||||
|
||||
/// A property key in a JavaScript object.
|
||||
pub trait PropertyKey {
|
||||
@ -204,6 +205,10 @@ mod traits {
|
||||
Err(Throw)
|
||||
}
|
||||
}
|
||||
|
||||
fn root<'a, C: Context<'a>>(&self, cx: &mut C) -> Root<Self> {
|
||||
Root::new(cx, self)
|
||||
}
|
||||
}
|
||||
|
||||
/// The trait of types that can be a function's `this` binding.
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
//! A convenience module that re-exports the most commonly-used Neon APIs.
|
||||
|
||||
pub use handle::Handle;
|
||||
pub use types::{JsBuffer, JsArrayBuffer, BinaryData, JsError, Value, JsValue, JsUndefined, JsNull, JsBoolean, JsString, JsNumber, JsObject, JsArray, JsFunction};
|
||||
pub use types::{JsBuffer, JsArrayBuffer, BinaryData, JsError, Value, JsValue, JsUndefined, JsNull, JsBoolean, JsString, JsNumber, JsObject, JsArray, JsFunction, JsPromise};
|
||||
pub use object::{Object, Class};
|
||||
pub use borrow::{Borrow, BorrowMut};
|
||||
pub use context::{CallKind, Context, ModuleContext, ExecuteContext, ComputeContext, CallContext, FunctionContext, MethodContext, TaskContext};
|
||||
@ -12,3 +12,5 @@ pub use event::EventHandler;
|
||||
pub use crate::{register_module, declare_types};
|
||||
#[cfg(feature = "napi-runtime")]
|
||||
pub use types::boxed::{Finalize, JsBox};
|
||||
#[cfg(feature = "napi-runtime")]
|
||||
pub use sync::{EventQueue, Root};
|
||||
|
||||
120
src/sync/event_queue.rs
Normal file
120
src/sync/event_queue.rs
Normal file
@ -0,0 +1,120 @@
|
||||
use neon_runtime::raw::Env;
|
||||
use neon_runtime::tsfn::ThreadsafeFunction;
|
||||
|
||||
use context::{Context, TaskContext};
|
||||
use result::JsResult;
|
||||
use types::Value;
|
||||
|
||||
type Callback = Box<dyn FnOnce(Env) + Send + 'static>;
|
||||
|
||||
/// Queue for scheduling Rust closures to execute on tge JavaScript main thread
|
||||
pub struct EventQueue {
|
||||
tsfn: ThreadsafeFunction<Callback>,
|
||||
has_ref: bool,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for EventQueue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("EventQueue")
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
};
|
||||
|
||||
Self {
|
||||
tsfn,
|
||||
has_ref: 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;
|
||||
|
||||
unsafe {
|
||||
self.tsfn.unref(cx.env().to_raw())
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
unsafe {
|
||||
self.tsfn.reference(cx.env().to_raw())
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Schedules a closure to execute on the JavaScript thread that created this EventQueue
|
||||
/// Panics if there is a libuv error
|
||||
pub fn send<F, T>(&self, f: F)
|
||||
where
|
||||
F: FnOnce(TaskContext) -> JsResult<T> + Send + 'static,
|
||||
T: Value,
|
||||
{
|
||||
self.try_send(f).unwrap()
|
||||
}
|
||||
|
||||
/// Schedules a closure to execute on the JavaScript thread that created this EventQueue
|
||||
/// Returns an `Error` if the task could not be scheduled.
|
||||
pub fn try_send<F, T>(&self, f: F) -> Result<(), EventQueueError>
|
||||
where
|
||||
F: FnOnce(TaskContext) -> JsResult<T> + Send + 'static,
|
||||
T: Value,
|
||||
{
|
||||
let callback = Box::new(move |env| {
|
||||
let env = unsafe { std::mem::transmute(env) };
|
||||
|
||||
TaskContext::with_context(env, move |cx| {
|
||||
let _ = f(cx);
|
||||
});
|
||||
});
|
||||
|
||||
self.tsfn
|
||||
.call(callback, None)
|
||||
.map_err(|_| EventQueueError)
|
||||
}
|
||||
|
||||
/// Returns a boolean indicating if this `EventQueue` will prevent the Node event
|
||||
/// queue from exiting.
|
||||
pub fn has_ref(&self) -> bool {
|
||||
self.has_ref
|
||||
}
|
||||
|
||||
// Monomorphized trampoline funciton for calling the user provided closure
|
||||
fn callback(env: Env, callback: Callback) {
|
||||
callback(env)
|
||||
}
|
||||
}
|
||||
|
||||
/// Error indicating that a closure was unable to be scheduled to execute on the event queue
|
||||
pub struct EventQueueError;
|
||||
|
||||
impl std::fmt::Display for EventQueueError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "EventQueueError")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for EventQueueError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for EventQueueError {}
|
||||
186
src/sync/mod.rs
Normal file
186
src/sync/mod.rs
Normal file
@ -0,0 +1,186 @@
|
||||
//! `neon::sync` provides utilities for building multi-threaded Neon modules.
|
||||
//!
|
||||
//! ## `Root<T>`
|
||||
//! In Neon, typically references to JavaScript values are bound by the lifetime
|
||||
//! of a [`Context`](../context/trait.Context.html).
|
||||
//! [`Root<T>`](struct.Root.html) allows Rust to maintain a reference to a
|
||||
//! JavaScript object. The JavaScript object cannot be garbage collected
|
||||
//! until the `Root<T>` is dropped.
|
||||
//!
|
||||
//! The following example demonstrates how `Root` can be used to create a
|
||||
//! globally accesible callback.
|
||||
//!
|
||||
//! ```
|
||||
//! use neon::prelude::*;
|
||||
//! # struct OnceCell<T>(Option<T>);
|
||||
//! # impl<T> OnceCell<T> {
|
||||
//! # const fn new() -> Self { Self(None) }
|
||||
//! # fn set(&self, v: T) -> Result<(), T> { todo!() }
|
||||
//! # fn get(&self) -> Option<T> { todo!() }
|
||||
//! # }
|
||||
//!
|
||||
//! static CALLBACK: OnceCell<Root<JsFunction>> = OnceCell::new();
|
||||
//!
|
||||
//! fn init(mut cx: FunctionContext) -> JsResult<JsUndefined> {
|
||||
//! let callback = cx.argument::<JsFunction>(0)?.root(&mut cx);
|
||||
//!
|
||||
//! CALLBACK.set(callback).unwrap();
|
||||
//!
|
||||
//! Ok(cx.undefined())
|
||||
//! }
|
||||
//!
|
||||
//! fn invoke(mut cx: FunctionContext) -> JsResult<JsValue> {
|
||||
//! let this = cx.undefined();
|
||||
//! let arg = cx.argument::<JsString>(0)?;
|
||||
//! let callback = CALLBACK.get().unwrap().to_inner(&mut cx);
|
||||
//!
|
||||
//! callback.call(&mut cx, this, vec![arg])
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Drop Safety
|
||||
//!
|
||||
//! `Root<T>` may only be dropped from the main JavaScript thread. To prevent
|
||||
//! accidental leaks of JavaScript objects, `Root<T>` provides a `Drop`
|
||||
//! implementation that panics if `drop` or `into_inner` is not called. Users
|
||||
//! must be careful to ensure that `Root<T>` are properly disposed.
|
||||
//!
|
||||
//! The [`Finalize`](../prelude/trait.Finalize.html) trait provides an ergonomic
|
||||
//! way to ensure `Root<T>` contained in a [`JsBox`](../types/struct.JsBox.html)
|
||||
//! are dropped safely.
|
||||
//!
|
||||
//! In the following example, the callback will be safely dropped when the
|
||||
//! client is garbage collected.
|
||||
//!
|
||||
//! ```
|
||||
//! use neon::prelude::*;
|
||||
//!
|
||||
//! struct Client {
|
||||
//! callback: Root<JsFunction>,
|
||||
//! }
|
||||
//!
|
||||
//! impl Finalize for Client {
|
||||
//! fn finalize<'a, C: Context<'a>>(self, cx: &mut C) {
|
||||
//! self.callback.drop(cx);
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn create_server(mut cx: FunctionContext) -> JsResult<JsBox<Client>> {
|
||||
//! let callback = cx.argument::<JsFunction>(0)?.root(&mut cx);
|
||||
//! let server = cx.boxed(Client { callback });
|
||||
//!
|
||||
//! Ok(server)
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## `EventQueue`
|
||||
//!
|
||||
//! Most Neon functions may only be called from the JavaScript main thread.
|
||||
//! [`EventQueue`](struct.EventQueue.html) provides a method of
|
||||
//! synchronization by allowing any thread to schedule a closure to execute
|
||||
//! on the main thread.
|
||||
//!
|
||||
//! In the previous example using [`Root<T>`](#roott), a persistent reference to a
|
||||
//! JavaScript callback was created. In this example, the reference is held
|
||||
//! while work is performed on another thread and called when it has completed.
|
||||
//!
|
||||
//! ```
|
||||
//! use neon::prelude::*;
|
||||
//! # fn long_running_task() -> f64 { 42.0 }
|
||||
//!
|
||||
//! fn perform_async(mut cx: FunctionContext) -> JsResult<JsUndefined> {
|
||||
//! let callback = cx.argument::<JsFunction>(0)?.root(&mut cx);
|
||||
//! let queue = cx.event_queue();
|
||||
//!
|
||||
//! // Spawn a background thread to perform our task
|
||||
//! std::thread::spawn(move || {
|
||||
//! // Perform any number of computations on a separate thread without
|
||||
//! // blocking the JavaScript queue.
|
||||
//! let result = long_running_task();
|
||||
//!
|
||||
//! // Once complete, an event can be scheduled back on the main
|
||||
//! // JavaScript thread.
|
||||
//! queue.send(move |mut cx| {
|
||||
//! // Neon functions can be called and the event queue is blocked
|
||||
//! // inside this closure.
|
||||
//! let callback = callback.into_inner(&mut cx);
|
||||
//! let this = cx.undefined();
|
||||
//! let arg = cx.number(result);
|
||||
//!
|
||||
//! callback.call(&mut cx, this, vec![arg])
|
||||
//! });
|
||||
//! });
|
||||
//!
|
||||
//! // When this function returns, the event queue is no longer blocked
|
||||
//! // but, the thread may still be executing in the background.
|
||||
//! Ok(cx.undefined())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### `Arc<EventQueue>`
|
||||
//!
|
||||
//! `EventQueue` are somewhat expensive to create. Ideally, code will re-use an
|
||||
//! `EventQueue` for scheduling similar events instead of creating a new queue
|
||||
//! for each event. `EventQueue` is `Sync` and can be called from any thread,
|
||||
//! but, is not `Clone`. It can be useful to wrap an `EventQueue` in an
|
||||
//! [`Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) to ensure it
|
||||
//! is not dropped while it might still be used.
|
||||
//!
|
||||
//! The following example uses `EventQueue` to callback to JavaScript after
|
||||
//! performing a Rust async operation.
|
||||
//!
|
||||
//! ```edition2018
|
||||
//! use neon::prelude::*;
|
||||
//!
|
||||
//! use std::sync::Arc;
|
||||
//! # struct Runtime;
|
||||
//! # impl Runtime { fn spawn<F>(&self, _: F) {} }
|
||||
//! # impl Client { fn new<T>(_: T) -> Self { todo!() } }
|
||||
//! # impl Finalize for Client {}
|
||||
//!
|
||||
//! struct Client {
|
||||
//! runtime: Runtime,
|
||||
//! queue: Arc<EventQueue>,
|
||||
//! }
|
||||
//!
|
||||
//! async fn get_user_name(id: f64) -> String {
|
||||
//! String::from("Username")
|
||||
//! }
|
||||
//!
|
||||
//! fn create_client(mut cx: FunctionContext) -> JsResult<JsBox<Client>> {
|
||||
//! let queue = cx.event_queue();
|
||||
//! let client = Client::new(queue);
|
||||
//!
|
||||
//! Ok(cx.boxed(client))
|
||||
//! }
|
||||
//!
|
||||
//! fn get_user_name_js(mut cx: FunctionContext) -> JsResult<JsUndefined> {
|
||||
//! let client = cx.argument::<JsBox<Client>>(0)?;
|
||||
//! let user_id = cx.argument::<JsNumber>(1)?.value(&mut cx);
|
||||
//! let callback = cx.argument::<JsFunction>(2)?.root(&mut cx);
|
||||
//! let queue = Arc::clone(&client.queue);
|
||||
//!
|
||||
//! client.runtime.spawn(async move {
|
||||
//! let username = get_user_name(user_id).await;
|
||||
//!
|
||||
//! queue.send(move |mut cx| {
|
||||
//! let this = cx.undefined();
|
||||
//! let callback = callback.into_inner(&mut cx);
|
||||
//! let args = vec![
|
||||
//! cx.null().upcast::<JsValue>(),
|
||||
//! cx.string(username).upcast(),
|
||||
//! ];
|
||||
//!
|
||||
//! callback.call(&mut cx, this, args)
|
||||
//! });
|
||||
//! });
|
||||
//!
|
||||
//! Ok(cx.undefined())
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
mod event_queue;
|
||||
mod root;
|
||||
|
||||
pub use self::event_queue::EventQueue;
|
||||
pub use self::root::Root;
|
||||
156
src/sync/root.rs
Normal file
156
src/sync/root.rs
Normal file
@ -0,0 +1,156 @@
|
||||
use std::ffi::c_void;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::ManuallyDrop;
|
||||
|
||||
use neon_runtime::reference;
|
||||
|
||||
use context::Context;
|
||||
use handle::Handle;
|
||||
use object::Object;
|
||||
use types::boxed::Finalize;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone)]
|
||||
struct NapiRef(*mut c_void);
|
||||
|
||||
// Provides unsafe `Send` and `Sync` on a `PhantomData`
|
||||
struct UnsafePhantom<T>(PhantomData<T>);
|
||||
|
||||
impl<T> UnsafePhantom<T> {
|
||||
// Safety: Caller must ensure `T` is `Send` and `Sync`
|
||||
unsafe fn new() -> Self {
|
||||
UnsafePhantom(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
/// `Root<T>` holds a reference to a `JavaScript` object and prevents it from
|
||||
/// being garbage collected. `Root<T>` may be sent across threads, but the
|
||||
/// referenced objected may only be accessed on the JavaScript thread that
|
||||
/// created it.
|
||||
#[repr(transparent)]
|
||||
pub struct Root<T> {
|
||||
internal: NapiRef,
|
||||
_phantom: UnsafePhantom<T>,
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for Root<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Root<{}>", std::any::type_name::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
// `napi_ref` are intended to be `Send` and `Sync`.
|
||||
unsafe impl Send for NapiRef {}
|
||||
unsafe impl Sync for NapiRef {}
|
||||
|
||||
// Treat `T` as though it were `Send` and `Sync`
|
||||
// Safety: Caller must ensure `T` is only used on the main thread
|
||||
unsafe impl<T> Send for UnsafePhantom<T> {}
|
||||
unsafe impl<T> Sync for UnsafePhantom<T> {}
|
||||
|
||||
impl<T: Object> Root<T> {
|
||||
/// Create a reference to a JavaScript object. The object will not be
|
||||
/// garbage collected until the `Root` is dropped. A `Root<T>` may only
|
||||
/// be dropped on the JavaScript thread that created it.
|
||||
///
|
||||
/// The caller _must_ ensure `Root::into_inner` or `Root::drop` is called
|
||||
/// to properly dispose of the `Root<T>`. If the value is dropped without
|
||||
/// calling one of these methods, it will *panic*.
|
||||
pub fn new<'a, C: Context<'a>>(cx: &mut C, value: &T) -> Self {
|
||||
let env = cx.env().to_raw();
|
||||
let internal = unsafe {
|
||||
reference::new(env, value.to_raw())
|
||||
};
|
||||
|
||||
Self {
|
||||
internal: NapiRef(internal as *mut _),
|
||||
_phantom: unsafe { UnsafePhantom::new() },
|
||||
}
|
||||
}
|
||||
|
||||
/// Clone a reference to the contained JavaScript object. This method can
|
||||
/// be considered identical to the following:
|
||||
/// ```
|
||||
/// # use neon::prelude::*;
|
||||
/// # fn my_neon_function(mut cx: FunctionContext) -> JsResult<JsUndefined> {
|
||||
/// # let root = cx.argument::<JsObject>(0)?.root(&mut cx);
|
||||
/// let inner = root.into_inner(&mut cx);
|
||||
/// let cloned = inner.root(&mut cx);
|
||||
/// let root = inner.root(&mut cx);
|
||||
/// # Ok(cx.undefined())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn clone<'a, C: Context<'a>>(&self, cx: &mut C) -> Self {
|
||||
let env = cx.env();
|
||||
let internal = self.internal.0 as *mut _;
|
||||
|
||||
unsafe {
|
||||
reference::reference(env.to_raw(), internal);
|
||||
};
|
||||
|
||||
Self {
|
||||
internal: self.internal.clone(),
|
||||
_phantom: unsafe { UnsafePhantom::new() },
|
||||
}
|
||||
}
|
||||
|
||||
/// Safely drop a `Root<T>` without returning the referenced JavaScript
|
||||
/// 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 _;
|
||||
|
||||
unsafe {
|
||||
reference::unreference(env, internal);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 local = unsafe {
|
||||
reference::get(env.to_raw(), internal)
|
||||
};
|
||||
|
||||
unsafe {
|
||||
reference::unreference(env.to_raw(), internal);
|
||||
}
|
||||
|
||||
Handle::new_internal(T::from_raw(env, local))
|
||||
}
|
||||
|
||||
/// Access the inner JavaScript object without consuming the `Root`
|
||||
/// This method aliases the reference without changing the reference count. It
|
||||
/// 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 _)
|
||||
};
|
||||
|
||||
Handle::new_internal(T::from_raw(env, local))
|
||||
}
|
||||
}
|
||||
|
||||
// Allows putting `Root<T>` directly in a container that implements `Finalize`
|
||||
// For example, `Vec<Root<T>>` or `JsBox`.
|
||||
impl <T: Object> Finalize for Root<T> {
|
||||
fn finalize<'a, C: Context<'a>>(self, cx: &mut C) {
|
||||
self.drop(cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Root<T> {
|
||||
fn drop(&mut self) {
|
||||
// Destructors are called during stack unwinding, prevent a double
|
||||
// panic and instead prefer to leak.
|
||||
if std::thread::panicking() {
|
||||
eprintln!("Warning: neon::sync::Root leaked during a panic");
|
||||
} else {
|
||||
panic!("Must call `into_inner` or `drop` on `Root` \
|
||||
https://docs.rs/neon/latest/neon/sync/index.html#drop-safety");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -137,6 +137,12 @@ pub struct JsBox<T: Send + 'static> {
|
||||
raw_data: *const T,
|
||||
}
|
||||
|
||||
impl<T: Send + 'static> std::fmt::Debug for JsBox<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "JsBox<{}>", std::any::type_name::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to use a `napi_value` as a `napi_external` to unwrap a `BoxAny>
|
||||
/// Safety: `local` must be a `napi_value` that is valid for the lifetime `'a`.
|
||||
unsafe fn maybe_external_deref<'a>(env: Env, local: raw::Local) -> Option<&'a BoxAny> {
|
||||
|
||||
@ -4,6 +4,8 @@ pub(crate) mod binary;
|
||||
#[cfg(feature = "napi-runtime")]
|
||||
pub(crate) mod boxed;
|
||||
pub(crate) mod error;
|
||||
#[cfg(feature = "napi-runtime")]
|
||||
pub(crate) mod promise;
|
||||
|
||||
pub(crate) mod internal;
|
||||
pub(crate) mod utf8;
|
||||
@ -29,6 +31,8 @@ pub use self::binary::{JsBuffer, JsArrayBuffer, BinaryData, BinaryViewType};
|
||||
#[cfg(feature = "napi-runtime")]
|
||||
pub use self::boxed::JsBox;
|
||||
pub use self::error::JsError;
|
||||
#[cfg(feature = "napi-runtime")]
|
||||
pub use self::promise::{JsPromise, Deferred, JsPromiseFuture};
|
||||
|
||||
pub(crate) fn build<'a, T: Managed, F: FnOnce(&mut raw::Local) -> bool>(env: Env, init: F) -> JsResult<'a, T> {
|
||||
unsafe {
|
||||
@ -65,6 +69,24 @@ pub trait Value: ValueInternal {
|
||||
fn as_value<'a, C: Context<'a>>(self, _: &mut C) -> Handle<'a, JsValue> {
|
||||
JsValue::new_internal(self.to_raw())
|
||||
}
|
||||
|
||||
fn to_future_adapter<'a, C, F, R>(
|
||||
self,
|
||||
cx: &mut C,
|
||||
callback: F,
|
||||
) -> JsPromiseFuture<F, R>
|
||||
where
|
||||
C: Context<'a>,
|
||||
R: Send + 'static,
|
||||
F: for<'c> FnOnce(
|
||||
crate::context::TaskContext<'c>,
|
||||
Result<Handle<'c, JsValue>, Handle<'c, JsValue>>,
|
||||
) -> R + Send + 'static,
|
||||
{
|
||||
let maybe_promise = self.as_value(cx);
|
||||
|
||||
JsPromiseFuture::new(cx, maybe_promise, callback)
|
||||
}
|
||||
}
|
||||
|
||||
/// A JavaScript value of any type.
|
||||
|
||||
183
src/types/promise.rs
Normal file
183
src/types/promise.rs
Normal file
@ -0,0 +1,183 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use neon_runtime::raw;
|
||||
|
||||
use crate::context::{Context, TaskContext};
|
||||
use crate::context::internal::Env;
|
||||
use crate::handle::{Managed, Handle};
|
||||
use crate::types::internal::ValueInternal;
|
||||
use crate::types::{Object, Value};
|
||||
|
||||
use super::JsValue;
|
||||
|
||||
/// A JavaScript Promise object
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct JsPromise(raw::Local);
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Deferred(*mut std::ffi::c_void);
|
||||
|
||||
unsafe impl Send for Deferred {}
|
||||
|
||||
impl Deferred {
|
||||
pub fn resolve<'a, C: Context<'a>, T: Value>(
|
||||
self,
|
||||
cx: &mut C, value: Handle<T>,
|
||||
) {
|
||||
unsafe {
|
||||
neon_runtime::promise::resolve(
|
||||
cx.env().to_raw(),
|
||||
self.0 as *mut _,
|
||||
value.to_raw(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reject<'a, C: Context<'a>, T: Value>(
|
||||
self,
|
||||
cx: &mut C, value: Handle<T>,
|
||||
) {
|
||||
unsafe {
|
||||
neon_runtime::promise::reject(
|
||||
cx.env().to_raw(),
|
||||
self.0 as *mut _,
|
||||
value.to_raw(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JsPromise {
|
||||
pub fn new<'a, C: Context<'a>>(cx: &mut C) -> (Handle<'a, JsPromise>, Deferred) {
|
||||
let (deferred, local) = unsafe {
|
||||
neon_runtime::promise::new(cx.env().to_raw())
|
||||
};
|
||||
|
||||
let promise = Handle::new_internal(JsPromise(local));
|
||||
let deferred = Deferred(deferred as *mut _);
|
||||
|
||||
(promise, deferred)
|
||||
}
|
||||
}
|
||||
|
||||
impl Value for JsPromise { }
|
||||
|
||||
impl Managed for JsPromise {
|
||||
fn to_raw(self) -> raw::Local { self.0 }
|
||||
|
||||
fn from_raw(_: Env, h: raw::Local) -> Self { JsPromise(h) }
|
||||
}
|
||||
|
||||
impl ValueInternal for JsPromise {
|
||||
fn name() -> String { "Promise".to_string() }
|
||||
|
||||
fn is_typeof<Other: Value>(env: Env, other: Other) -> bool {
|
||||
unsafe { neon_runtime::tag::is_promise(env.to_raw(), other.to_raw()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Object for JsPromise { }
|
||||
|
||||
// FIXME: This should be a state machine enum
|
||||
struct JsPromiseFutureInner<F, R> {
|
||||
callback: Option<F>,
|
||||
result: Option<R>,
|
||||
waker: Option<std::task::Waker>,
|
||||
}
|
||||
|
||||
pub struct JsPromiseFuture<F, R> {
|
||||
inner: Arc<Mutex<JsPromiseFutureInner<F, R>>>,
|
||||
}
|
||||
|
||||
impl<F, R> std::future::Future for JsPromiseFuture<F, R>
|
||||
where
|
||||
R: Send + 'static,
|
||||
F: for<'c> FnOnce(
|
||||
TaskContext<'c>,
|
||||
Result<Handle<'c, JsValue>, Handle<'c, JsValue>>,
|
||||
) -> R + Send + 'static,
|
||||
{
|
||||
type Output = R;
|
||||
|
||||
fn poll(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Self::Output> {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
if let Some(result) = inner.result.take() {
|
||||
std::task::Poll::Ready(result)
|
||||
} else {
|
||||
inner.waker = Some(cx.waker().clone());
|
||||
std::task::Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, R> JsPromiseFuture<F, R>
|
||||
where
|
||||
R: Send + 'static,
|
||||
F: for<'c> FnOnce(
|
||||
TaskContext<'c>,
|
||||
Result<Handle<'c, JsValue>, Handle<'c, JsValue>>,
|
||||
) -> R + Send + 'static,
|
||||
{
|
||||
pub fn new<'a, C, T>(
|
||||
cx: &mut C,
|
||||
maybe_promise: Handle<T>,
|
||||
callback: F,
|
||||
) -> Self
|
||||
where
|
||||
C: Context<'a>,
|
||||
T: Value,
|
||||
{
|
||||
// Support promise-like objects by creating a new promise and
|
||||
// immediately resolving.
|
||||
let (promise, deferred) = cx.promise();
|
||||
|
||||
deferred.resolve(cx, maybe_promise);
|
||||
|
||||
let inner = Arc::new(Mutex::new(JsPromiseFutureInner {
|
||||
callback: Some(callback),
|
||||
result: None,
|
||||
waker: None,
|
||||
}));
|
||||
|
||||
let make_callback = |success| {
|
||||
let inner = inner.clone();
|
||||
|
||||
move |env, value| {
|
||||
let env = unsafe { std::mem::transmute(env) };
|
||||
|
||||
TaskContext::with_context(env, move |cx| {
|
||||
let mut inner = inner.lock().unwrap();
|
||||
let callback = inner.callback.take().unwrap();
|
||||
let value = JsValue::new_internal(value);
|
||||
let value = if success {
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(value)
|
||||
};
|
||||
|
||||
inner.result = Some(callback(cx, value));
|
||||
|
||||
if let Some(waker) = inner.waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
neon_runtime::promise::adapter(
|
||||
cx.env().to_raw(),
|
||||
promise.to_raw(),
|
||||
make_callback(true),
|
||||
make_callback(false),
|
||||
)
|
||||
}
|
||||
|
||||
JsPromiseFuture { inner }
|
||||
}
|
||||
}
|
||||
87
test/napi/lib/sync.js
Normal file
87
test/napi/lib/sync.js
Normal file
@ -0,0 +1,87 @@
|
||||
const addon = require('..');
|
||||
const assert = require('chai').assert;
|
||||
|
||||
(function () {
|
||||
// These tests require GC exposed to shutdown properly; skip if it is not
|
||||
return typeof global.gc === 'function' ? describe : describe.skip;
|
||||
})()('sync', function() {
|
||||
afterEach(() => {
|
||||
// Force garbage collection to shutdown `EventQueue`
|
||||
global.gc();
|
||||
});
|
||||
|
||||
it('can create and deref a root', function () {
|
||||
const expected = {};
|
||||
const result = addon.useless_root(expected);
|
||||
|
||||
assert.strictEqual(expected, result);
|
||||
});
|
||||
|
||||
it('should be able to callback from another thread', function (cb) {
|
||||
addon.thread_callback(cb);
|
||||
});
|
||||
|
||||
it('should be able to callback from multiple threads', function (cb) {
|
||||
const n = 4;
|
||||
const set = new Set([...new Array(n)].map((_, i) => i));
|
||||
|
||||
addon.multi_threaded_callback(n, function (x) {
|
||||
if (!set.delete(x)) {
|
||||
cb(new Error(`Unexpected callback value: ${x}`));
|
||||
}
|
||||
|
||||
if (set.size === 0) {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to use an async greeter', function (cb) {
|
||||
const greeter = addon.greeter_new('Hello, World!', function (greeting) {
|
||||
if (greeting === 'Hello, World!') {
|
||||
cb();
|
||||
} else {
|
||||
new Error('Greeting did not match');
|
||||
}
|
||||
});
|
||||
|
||||
addon.greeter_greet(greeter);
|
||||
});
|
||||
|
||||
it('should run callback on drop', function (cb) {
|
||||
// IIFE to allow GC
|
||||
(function () {
|
||||
addon.greeter_new('Hello, World!', function () {}, function () {
|
||||
// No assert needed; test will timeout
|
||||
cb();
|
||||
})
|
||||
})();
|
||||
|
||||
global.gc();
|
||||
});
|
||||
|
||||
it('should be able to unref event queue', function () {
|
||||
// If the EventQueue is not unreferenced, the test runner will not cleanly exit
|
||||
addon.leak_event_queue();
|
||||
});
|
||||
|
||||
it('should be able to resolve a promise', function () {
|
||||
let expected = {};
|
||||
|
||||
return addon.resolve_promise(expected).then(function (actual) {
|
||||
assert.strictEqual(actual, expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to reject a promise', function () {
|
||||
let expected = {};
|
||||
|
||||
return addon.reject_promise(expected)
|
||||
.then(function () {
|
||||
throw new Error('Expected to reject');
|
||||
})
|
||||
.catch(function (actual) {
|
||||
assert.strictEqual(actual, expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -6,7 +6,7 @@
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"install": "cargo build -p napi --release",
|
||||
"test": "mocha --timeout 5000 --recursive lib"
|
||||
"test": "mocha --expose-gc --timeout 5000 --recursive lib"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.2.0",
|
||||
|
||||
150
test/napi/src/js/sync.rs
Normal file
150
test/napi/src/js/sync.rs
Normal file
@ -0,0 +1,150 @@
|
||||
use std::cell::RefCell;
|
||||
use std::sync::Arc;
|
||||
|
||||
use neon::prelude::*;
|
||||
|
||||
pub fn useless_root(mut cx: FunctionContext) -> JsResult<JsObject> {
|
||||
let object = cx.argument::<JsObject>(0)?;
|
||||
let root = object.root(&mut cx);
|
||||
let object = root.into_inner(&mut cx);
|
||||
|
||||
Ok(object)
|
||||
}
|
||||
|
||||
pub fn thread_callback(mut cx: FunctionContext) -> JsResult<JsUndefined> {
|
||||
let callback = cx.argument::<JsFunction>(0)?.root(&mut cx);
|
||||
let queue = cx.event_queue();
|
||||
|
||||
std::thread::spawn(move || queue.send(move |mut cx| {
|
||||
let callback = callback.into_inner(&mut cx);
|
||||
let this = cx.undefined();
|
||||
let args = Vec::<Handle<JsValue>>::new();
|
||||
|
||||
callback.call(&mut cx, this, args)
|
||||
}));
|
||||
|
||||
Ok(cx.undefined())
|
||||
}
|
||||
|
||||
pub fn multi_threaded_callback(mut cx: FunctionContext) -> JsResult<JsUndefined> {
|
||||
let n = cx.argument::<JsNumber>(0)?.value(&mut cx);
|
||||
let callback = cx.argument::<JsFunction>(1)?.root(&mut cx);
|
||||
let queue = Arc::new(cx.event_queue());
|
||||
|
||||
for i in 0..(n as usize) {
|
||||
let callback = callback.clone(&mut cx);
|
||||
let queue = Arc::clone(&queue);
|
||||
|
||||
std::thread::spawn(move || queue.send(move |mut cx| {
|
||||
let callback = callback.into_inner(&mut cx);
|
||||
let this = cx.undefined();
|
||||
let args = vec![cx.number(i as f64)];
|
||||
|
||||
callback.call(&mut cx, this, args)
|
||||
}));
|
||||
}
|
||||
|
||||
callback.drop(&mut cx);
|
||||
|
||||
Ok(cx.undefined())
|
||||
}
|
||||
|
||||
type BoxedGreeter = JsBox<RefCell<AsyncGreeter>>;
|
||||
|
||||
pub struct AsyncGreeter {
|
||||
greeting: String,
|
||||
callback: Root<JsFunction>,
|
||||
shutdown: Option<Root<JsFunction>>,
|
||||
queue: Arc<EventQueue>,
|
||||
}
|
||||
|
||||
impl AsyncGreeter {
|
||||
fn greet<'a, C: Context<'a>>(&self, mut cx: C) -> JsResult<'a, JsUndefined> {
|
||||
let greeting = self.greeting.clone();
|
||||
let callback = self.callback.clone(&mut cx);
|
||||
let queue = Arc::clone(&self.queue);
|
||||
|
||||
std::thread::spawn(move || queue.send(|mut cx| {
|
||||
let callback = callback.into_inner(&mut cx);
|
||||
let this = cx.undefined();
|
||||
let args = vec![cx.string(greeting)];
|
||||
|
||||
callback.call(&mut cx, this, args)
|
||||
}));
|
||||
|
||||
Ok(cx.undefined())
|
||||
}
|
||||
}
|
||||
|
||||
impl Finalize for AsyncGreeter {
|
||||
fn finalize<'a, C: Context<'a>>(self, cx: &mut C) {
|
||||
let Self { callback, shutdown, .. } = self;
|
||||
|
||||
if let Some(shutdown) = shutdown {
|
||||
let shutdown = shutdown.into_inner(cx);
|
||||
let this = cx.undefined();
|
||||
let args = Vec::<Handle<JsValue>>::new();
|
||||
let _ = shutdown.call(cx, this, args);
|
||||
}
|
||||
|
||||
callback.drop(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn greeter_new(mut cx: FunctionContext) -> JsResult<BoxedGreeter> {
|
||||
let greeting = cx.argument::<JsString>(0)?.value(&mut cx);
|
||||
let callback = cx.argument::<JsFunction>(1)?.root(&mut cx);
|
||||
let shutdown = cx.argument_opt(2);
|
||||
|
||||
let queue = cx.event_queue();
|
||||
let shutdown = shutdown
|
||||
.map(|v| v.downcast_or_throw::<JsFunction, _>(&mut cx))
|
||||
.transpose()?
|
||||
.map(|v| v.root(&mut cx));
|
||||
|
||||
let greeter = cx.boxed(RefCell::new(AsyncGreeter {
|
||||
greeting,
|
||||
callback,
|
||||
shutdown,
|
||||
queue: Arc::new(queue),
|
||||
}));
|
||||
|
||||
Ok(greeter)
|
||||
}
|
||||
|
||||
pub fn greeter_greet(mut cx: FunctionContext) -> JsResult<JsUndefined> {
|
||||
let greeter = cx.argument::<BoxedGreeter>(0)?;
|
||||
let greeter = greeter.borrow();
|
||||
|
||||
greeter.greet(cx)
|
||||
}
|
||||
|
||||
pub fn leak_event_queue(mut cx: FunctionContext) -> JsResult<JsUndefined> {
|
||||
let queue = Box::new({
|
||||
let mut queue = cx.event_queue();
|
||||
queue.unref(&mut cx);
|
||||
queue
|
||||
});
|
||||
|
||||
Box::leak(queue);
|
||||
|
||||
Ok(cx.undefined())
|
||||
}
|
||||
|
||||
pub fn resolve_promise(mut cx: FunctionContext) -> JsResult<JsPromise> {
|
||||
let value = cx.argument::<JsValue>(0)?;
|
||||
let (promise, deferred) = cx.promise();
|
||||
|
||||
deferred.resolve(&mut cx, value);
|
||||
|
||||
Ok(promise)
|
||||
}
|
||||
|
||||
pub fn reject_promise(mut cx: FunctionContext) -> JsResult<JsPromise> {
|
||||
let value = cx.argument::<JsValue>(0)?;
|
||||
let (promise, deferred) = cx.promise();
|
||||
|
||||
deferred.reject(&mut cx, value);
|
||||
|
||||
Ok(promise)
|
||||
}
|
||||
@ -10,6 +10,7 @@ mod js {
|
||||
pub mod objects;
|
||||
pub mod types;
|
||||
pub mod strings;
|
||||
pub mod sync;
|
||||
}
|
||||
|
||||
use js::arrays::*;
|
||||
@ -21,6 +22,7 @@ use js::numbers::*;
|
||||
use js::objects::*;
|
||||
use js::types::*;
|
||||
use js::strings::*;
|
||||
use js::sync::*;
|
||||
|
||||
#[neon::main]
|
||||
fn main(mut cx: ModuleContext) -> NeonResult<()> {
|
||||
@ -186,5 +188,15 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> {
|
||||
cx.export_function("ref_person_fail", ref_person_fail)?;
|
||||
cx.export_function("external_unit", external_unit)?;
|
||||
|
||||
cx.export_function("useless_root", useless_root)?;
|
||||
cx.export_function("thread_callback", thread_callback)?;
|
||||
cx.export_function("multi_threaded_callback", multi_threaded_callback)?;
|
||||
cx.export_function("greeter_new", greeter_new)?;
|
||||
cx.export_function("greeter_greet", greeter_greet)?;
|
||||
cx.export_function("leak_event_queue", leak_event_queue)?;
|
||||
|
||||
cx.export_function("resolve_promise", resolve_promise)?;
|
||||
cx.export_function("reject_promise", reject_promise)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
25
test/tokio/Cargo.toml
Normal file
25
test/tokio/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "tokio-test"
|
||||
version = "0.1.0"
|
||||
authors = ["The Neon Community <david.herman@gmail.com>"]
|
||||
license = "MIT"
|
||||
build = "build.rs"
|
||||
exclude = ["artifacts.json", "index.node"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "tokio_test"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[build-dependencies]
|
||||
neon-build = {version = "*", path = "../../crates/neon-build"}
|
||||
|
||||
[dependencies]
|
||||
once_cell = "1"
|
||||
tokio = { version = "0.3", features = ["rt-multi-thread"] }
|
||||
|
||||
[dependencies.neon]
|
||||
version = "*"
|
||||
path = "../.."
|
||||
default-features = false
|
||||
features = ["default-panic-hook", "napi-runtime", "try-catch-api"]
|
||||
7
test/tokio/build.rs
Normal file
7
test/tokio/build.rs
Normal file
@ -0,0 +1,7 @@
|
||||
extern crate neon_build;
|
||||
|
||||
fn main() {
|
||||
neon_build::setup(); // must be called in build.rs
|
||||
|
||||
// add project-specific build logic here...
|
||||
}
|
||||
6
test/tokio/index.js
Normal file
6
test/tokio/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const native = require('./index.node');
|
||||
|
||||
native.getNum(() => new Promise(resolve => setTimeout(resolve, 1000, 5)));
|
||||
|
||||
10
test/tokio/package.json
Normal file
10
test/tokio/package.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "tokio-test",
|
||||
"version": "0.1.0",
|
||||
"description": "Acceptance test suite for Neon with N-API backend",
|
||||
"author": "The Neon Community",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"install": "cargo build -p tokio-test"
|
||||
}
|
||||
}
|
||||
36
test/tokio/src/lib.rs
Normal file
36
test/tokio/src/lib.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use neon::prelude::*;
|
||||
|
||||
use once_cell::sync::OnceCell;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
static RUNTIME: OnceCell<Runtime> = OnceCell::new();
|
||||
|
||||
fn get_num(mut cx: FunctionContext) -> JsResult<JsUndefined> {
|
||||
let callback = cx.argument::<JsFunction>(0)?;
|
||||
let this = cx.undefined();
|
||||
let args = Vec::<Handle<JsValue>>::with_capacity(0);
|
||||
let promise = callback.call(&mut cx, this, args)?;
|
||||
let future = promise.to_future_adapter(&mut cx, |mut cx, v| {
|
||||
v.unwrap_or_else(|_| panic!("Promise rejected"))
|
||||
.downcast::<JsNumber, _>(&mut cx)
|
||||
.unwrap()
|
||||
.value(&mut cx)
|
||||
});
|
||||
|
||||
RUNTIME.get().unwrap().spawn(async {
|
||||
let n = future.await;
|
||||
|
||||
println!("n is {}", n);
|
||||
});
|
||||
|
||||
Ok(cx.undefined())
|
||||
}
|
||||
|
||||
#[neon::main]
|
||||
fn main(mut cx: ModuleContext) -> NeonResult<()> {
|
||||
let _ = RUNTIME.get_or_init(|| Runtime::new().unwrap());
|
||||
|
||||
cx.export_function("getNum", get_num)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user