Compare commits

...

6 Commits

Author SHA1 Message Date
K.J. Valencik
e503caa2b9
Example with adapter 2020-12-10 21:21:55 -05:00
K.J. Valencik
77f1ffdf04
Promise Future adapter 2020-12-10 20:55:57 -05:00
K.J. Valencik
a70bc8b50c
Promises 2020-12-10 15:58:25 -05:00
K.J. Valencik
e67b1d1411
Fixes after switching to dynamic loading 2020-12-10 15:14:12 -05:00
K.J. Valencik
fad0f694f4
Update ref/unref to match RFC 2020-12-10 14:40:25 -05:00
K.J. Valencik
74b8914334
Threadsafe Handles 2020-12-10 14:40:24 -05:00
27 changed files with 1561 additions and 6 deletions

View File

@ -67,5 +67,6 @@ members = [
"test/static",
"test/electron",
"test/dynamic/native",
"test/napi"
"test/napi",
"test/tokio",
]

View File

@ -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;
});

View File

@ -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);

View File

@ -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::*;

View 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);
}

View 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()
}

View File

@ -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
}

View 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,
);
};
}
}

View File

@ -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> {

View File

@ -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;

View File

@ -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.

View File

@ -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
View 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
View 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
View 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");
}
}
}

View File

@ -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> {

View File

@ -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
View 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
View 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);
});
});
});

View File

@ -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
View 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)
}

View File

@ -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
View 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
View 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
View 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
View 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
View 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(())
}