Merge pull request #86 from napi-rs/libuv

Use libuv to run rust future
This commit is contained in:
LongYinan 2020-07-07 23:16:06 +08:00 committed by GitHub
commit b8e9463dd2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 468 additions and 60 deletions

View file

@ -1,19 +1,20 @@
name: Linux-NAPI4
name: Linux-N-API-3
on: [push, pull_request]
jobs:
build_and_test:
name: stable - x86_64-unknown-linux-gnu - node@12.10
name: stable - x86_64-unknown-linux-gnu - node@10.0
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v1
with:
node-version: 12.10
run: |
wget https://nodejs.org/dist/v10.0.0/node-v10.0.0-linux-x64.tar.xz
tar xf node-v10.0.0-linux-x64.tar.xz
echo "::add-path::$(pwd)/node-v10.0.0-linux-x64/bin"
- name: Install stable
uses: actions-rs/toolchain@v1
@ -57,7 +58,7 @@ jobs:
- name: Unit tests
run: |
yarn --ignore-engines
yarn add ava@2 --dev --ignore-engines
yarn --cwd ./test_module build
yarn test
env:

View file

@ -30,7 +30,7 @@ jobs:
- name: Install llvm
run: choco install -y llvm
- name: set environment variables
uses: allenevans/set-env@v1.0.0
uses: allenevans/set-env@v1.1.0
with:
LIBCLANG_PATH: 'C:\Program Files\LLVM\bin'
- name: Generate Cargo.lock

View file

@ -83,14 +83,14 @@ fn setup_napi_feature() {
let napi_version_number = napi_version.trim().parse::<u32>().unwrap();
if napi_version_number < 4 {
if napi_version_number < 2 {
panic!("current napi version is too low");
}
if napi_version_number == 4 {
if napi_version_number == 2 {
println!("cargo:rustc-cfg=napi{}", napi_version_number);
} else {
for version in 4..napi_version_number {
for version in 2..(napi_version_number + 1) {
println!("cargo:rustc-cfg=napi{}", version);
}
}

View file

@ -9,6 +9,12 @@ repository = "https://github.com/Brooooooklyn/napi-rs"
keywords = ["NodeJS", "FFI", "NAPI", "n-api"]
edition = "2018"
[features]
libuv = ["futures"]
[dependencies]
futures = { version = "0.3", optional = true }
[target.'cfg(windows)'.build-dependencies]
flate2 = "1.0"
reqwest = { version = "0.10", features = ["native-tls", "blocking"] }

View file

@ -10,16 +10,25 @@ use crate::error::check_status;
use crate::js_values::*;
use crate::{sys, AsyncWork, Error, NodeVersion, Result, Status};
#[cfg(all(feature = "libuv", napi4))]
use crate::promise;
#[cfg(all(feature = "libuv", napi4))]
use crate::uv;
#[cfg(all(feature = "libuv", napi4))]
use std::future::Future;
pub type Callback = extern "C" fn(sys::napi_env, sys::napi_callback_info) -> sys::napi_value;
#[derive(Clone, Copy, Debug)]
pub struct Env(pub(crate) sys::napi_env);
impl Env {
#[inline]
pub fn from_raw(env: sys::napi_env) -> Self {
Env(env)
}
#[inline]
pub fn get_undefined(&self) -> Result<JsUndefined> {
let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_get_undefined(self.0, &mut raw_value) };
@ -27,6 +36,7 @@ impl Env {
Ok(JsUndefined::from_raw_unchecked(self.0, raw_value))
}
#[inline]
pub fn get_null(&self) -> Result<JsNull> {
let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_get_null(self.0, &mut raw_value) };
@ -34,6 +44,7 @@ impl Env {
Ok(JsNull::from_raw_unchecked(self.0, raw_value))
}
#[inline]
pub fn get_boolean(&self, value: bool) -> Result<JsBoolean> {
let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_get_boolean(self.0, value, &mut raw_value) };
@ -41,6 +52,7 @@ impl Env {
Ok(JsBoolean::from_raw_unchecked(self.0, raw_value))
}
#[inline]
pub fn create_int32(&self, int: i32) -> Result<JsNumber> {
let mut raw_value = ptr::null_mut();
let status =
@ -49,6 +61,7 @@ impl Env {
Ok(JsNumber::from_raw_unchecked(self.0, raw_value))
}
#[inline]
pub fn create_int64(&self, int: i64) -> Result<JsNumber> {
let mut raw_value = ptr::null_mut();
let status =
@ -57,6 +70,7 @@ impl Env {
Ok(JsNumber::from_raw_unchecked(self.0, raw_value))
}
#[inline]
pub fn create_uint32(&self, number: u32) -> Result<JsNumber> {
let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_create_uint32(self.0, number, &mut raw_value) };
@ -64,6 +78,7 @@ impl Env {
Ok(JsNumber::from_raw_unchecked(self.0, raw_value))
}
#[inline]
pub fn create_double(&self, double: f64) -> Result<JsNumber> {
let mut raw_value = ptr::null_mut();
let status =
@ -72,20 +87,35 @@ impl Env {
Ok(JsNumber::from_raw_unchecked(self.0, raw_value))
}
#[inline]
pub fn create_string(&self, s: &str) -> Result<JsString> {
self.create_string_from_chars(s.as_ptr() as *const _, s.len() as u64)
}
#[inline]
pub fn create_string_from_std(&self, s: String) -> Result<JsString> {
self.create_string_from_chars(s.as_ptr() as *const _, s.len() as u64)
}
#[inline]
pub fn create_string_from_vec_u8(&self, bytes: Vec<u8>) -> Result<JsString> {
self.create_string_from_chars(bytes.as_ptr() as *const _, bytes.len() as u64)
}
#[inline]
pub fn create_string_from_vec_i8(&self, bytes: Vec<i8>) -> Result<JsString> {
self.create_string_from_chars(bytes.as_ptr(), bytes.len() as u64)
}
#[inline]
fn create_string_from_chars(&self, data_ptr: *const c_char, len: u64) -> Result<JsString> {
let mut raw_value = ptr::null_mut();
let status = unsafe {
sys::napi_create_string_utf8(
self.0,
s.as_ptr() as *const c_char,
s.len() as u64,
&mut raw_value,
)
};
let status = unsafe { sys::napi_create_string_utf8(self.0, data_ptr, len, &mut raw_value) };
check_status(status)?;
Ok(JsString::from_raw_unchecked(self.0, raw_value))
}
#[inline]
pub fn create_string_utf16(&self, chars: &[u16]) -> Result<JsString> {
let mut raw_value = ptr::null_mut();
let status = unsafe {
@ -95,12 +125,14 @@ impl Env {
Ok(JsString::from_raw_unchecked(self.0, raw_value))
}
#[inline]
pub fn create_symbol_from_js_string(&self, description: JsString) -> Result<JsSymbol> {
let mut result = ptr::null_mut();
check_status(unsafe { sys::napi_create_symbol(self.0, description.0.value, &mut result) })?;
Ok(JsSymbol::from_raw_unchecked(self.0, result))
}
#[inline]
pub fn create_symbol(&self, description: Option<&str>) -> Result<JsSymbol> {
let mut result = ptr::null_mut();
check_status(unsafe {
@ -116,6 +148,7 @@ impl Env {
Ok(JsSymbol::from_raw_unchecked(self.0, result))
}
#[inline]
pub fn create_object(&self) -> Result<JsObject> {
let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_create_object(self.0, &mut raw_value) };
@ -123,6 +156,7 @@ impl Env {
Ok(JsObject::from_raw_unchecked(self.0, raw_value))
}
#[inline]
pub fn create_array_with_length(&self, length: usize) -> Result<JsObject> {
let mut raw_value = ptr::null_mut();
let status =
@ -131,6 +165,7 @@ impl Env {
Ok(JsObject::from_raw_unchecked(self.0, raw_value))
}
#[inline]
pub fn create_buffer(&self, length: u64) -> Result<JsBuffer> {
let mut raw_value = ptr::null_mut();
let mut data = Vec::with_capacity(length as usize);
@ -145,6 +180,7 @@ impl Env {
Ok(buffer)
}
#[inline]
pub fn create_buffer_with_data(&self, data: Vec<u8>) -> Result<JsBuffer> {
let length = data.len() as u64;
let mut raw_value = ptr::null_mut();
@ -155,15 +191,15 @@ impl Env {
length,
data_ptr as *mut c_void,
Some(drop_buffer),
&length as *const _ as *mut c_void,
Box::leak(Box::new(length)) as *mut u64 as *mut _,
&mut raw_value,
)
};
check_status(status)?;
let mut changed = 0;
let ajust_external_memory_status =
let adjust_external_memory_status =
unsafe { sys::napi_adjust_external_memory(self.0, length as i64, &mut changed) };
check_status(ajust_external_memory_status)?;
check_status(adjust_external_memory_status)?;
mem::forget(data);
let mut buffer = JsBuffer::from_raw_unchecked(self.0, raw_value);
buffer.data = data_ptr as *const u8;
@ -171,6 +207,7 @@ impl Env {
Ok(buffer)
}
#[inline]
pub fn create_arraybuffer(&self, length: u64) -> Result<JsArrayBuffer> {
let mut raw_value = ptr::null_mut();
let mut data = Vec::with_capacity(length as usize);
@ -185,6 +222,7 @@ impl Env {
Ok(array_buffer)
}
#[inline]
pub fn create_arraybuffer_with_data(&self, data: Vec<u8>) -> Result<JsArrayBuffer> {
let length = data.len() as u64;
let mut raw_value = ptr::null_mut();
@ -201,9 +239,9 @@ impl Env {
};
check_status(status)?;
let mut changed = 0;
let ajust_external_memory_status =
let adjust_external_memory_status =
unsafe { sys::napi_adjust_external_memory(self.0, length as i64, &mut changed) };
check_status(ajust_external_memory_status)?;
check_status(adjust_external_memory_status)?;
mem::forget(data);
let mut array_buffer = JsArrayBuffer::from_raw_unchecked(self.0, raw_value);
array_buffer.data = data_ptr as *const u8;
@ -211,6 +249,7 @@ impl Env {
Ok(array_buffer)
}
#[inline]
pub fn create_function(&self, name: &str, callback: Callback) -> Result<JsFunction> {
let mut raw_result = ptr::null_mut();
let status = unsafe {
@ -229,18 +268,21 @@ impl Env {
Ok(JsFunction::from_raw_unchecked(self.0, raw_result))
}
#[inline]
pub fn throw(&self, error: Error) -> Result<()> {
let err_value = self.create_error(error)?.0.value;
check_status(unsafe { sys::napi_throw(self.0, err_value) })?;
Ok(())
}
#[inline]
pub fn throw_error(&self, msg: &str) -> Result<()> {
let status = unsafe { sys::napi_throw_error(self.0, ptr::null(), msg.as_ptr() as *const _) };
check_status(status)?;
Ok(())
}
#[inline]
pub fn create_reference<T: NapiValue>(&self, value: T) -> Result<Ref<T>> {
let mut raw_ref = ptr::null_mut();
let initial_ref_count = 1;
@ -253,6 +295,7 @@ impl Env {
Ok(Ref::new(self.0, raw_ref))
}
#[inline]
pub fn get_reference_value<T: NapiValue>(&self, reference: &Ref<T>) -> Result<T> {
let mut raw_value = ptr::null_mut();
unsafe {
@ -263,6 +306,7 @@ impl Env {
Ok(T::from_raw_unchecked(self.0, raw_value))
}
#[inline]
pub fn define_class(
&self,
name: &str,
@ -293,6 +337,7 @@ impl Env {
Ok(JsFunction::from_raw_unchecked(self.0, raw_result))
}
#[inline]
pub fn wrap<T: 'static>(&self, js_object: &mut JsObject, native_object: T) -> Result<()> {
let status = unsafe {
sys::napi_wrap(
@ -308,6 +353,7 @@ impl Env {
check_status(status).or(Ok(()))
}
#[inline]
pub fn unwrap<T: 'static>(&self, js_object: &JsObject) -> Result<&mut T> {
unsafe {
let mut unknown_tagged_object: *mut c_void = ptr::null_mut();
@ -330,6 +376,7 @@ impl Env {
}
}
#[inline]
pub fn drop_wrapped<T: 'static>(&self, js_object: JsObject) -> Result<()> {
unsafe {
let mut unknown_tagged_object: *mut c_void = ptr::null_mut();
@ -351,6 +398,7 @@ impl Env {
}
}
#[inline]
pub fn create_external<T: 'static>(&self, native_object: T) -> Result<JsExternal> {
let mut object_value = ptr::null_mut();
let status = unsafe {
@ -367,6 +415,7 @@ impl Env {
Ok(JsExternal::from_raw_unchecked(self.0, object_value))
}
#[inline]
pub fn get_value_external<T: 'static>(&self, js_external: &JsExternal) -> Result<&mut T> {
unsafe {
let mut unknown_tagged_object = ptr::null_mut();
@ -391,6 +440,7 @@ impl Env {
}
}
#[inline]
pub fn create_error(&self, e: Error) -> Result<JsObject> {
let reason = e.reason;
let reason_string = self.create_string(reason.as_str())?;
@ -407,6 +457,7 @@ impl Env {
Ok(JsObject::from_raw_unchecked(self.0, result))
}
#[inline]
pub fn spawn<T: 'static + Task>(&self, task: T) -> Result<JsObject> {
let mut raw_promise = ptr::null_mut();
let mut raw_deferred = ptr::null_mut();
@ -416,6 +467,67 @@ impl Env {
Ok(JsObject::from_raw_unchecked(self.0, raw_promise))
}
#[inline]
pub fn get_global(&self) -> Result<JsObject> {
let mut raw_global = ptr::null_mut();
check_status(unsafe { sys::napi_get_global(self.0, &mut raw_global) })?;
Ok(JsObject::from_raw_unchecked(self.0, raw_global))
}
#[inline]
pub fn get_napi_version(&self) -> Result<u32> {
let global = self.get_global()?;
let process = global.get_named_property::<JsObject>("process")?;
let versions = process.get_named_property::<JsObject>("versions")?;
let napi_version = versions.get_named_property::<JsString>("napi")?;
napi_version
.as_str()?
.parse()
.map_err(|e| Error::new(Status::InvalidArg, format!("{}", e)))
}
#[cfg(all(feature = "libuv", napi4))]
#[inline]
pub fn execute<
T: 'static,
V: 'static + NapiValue,
F: 'static + Future<Output = Result<T>>,
R: 'static + Send + Sync + FnOnce(&mut Env, T) -> Result<V>,
>(
&self,
deferred: F,
resolver: R,
) -> Result<JsObject> {
use futures::prelude::*;
let mut raw_promise = ptr::null_mut();
let mut raw_deferred = ptr::null_mut();
unsafe {
let status = sys::napi_create_promise(self.0, &mut raw_deferred, &mut raw_promise);
check_status(status)?;
}
let event_loop = unsafe { sys::uv_default_loop() };
let raw_env = self.0;
let future_to_execute = promise::resolve_from_future(self.0, deferred, resolver, raw_deferred)
.map(move |v| match v {
Ok(value) => value,
Err(e) => {
let cloned_error = e.clone();
unsafe {
sys::napi_throw_error(raw_env, ptr::null(), e.reason.as_ptr() as *const _);
};
eprintln!("{:?}", &cloned_error);
panic!(cloned_error);
}
});
uv::execute(event_loop, Box::pin(future_to_execute))?;
Ok(JsObject::from_raw_unchecked(self.0, raw_promise))
}
#[inline]
pub fn get_node_version(&self) -> Result<NodeVersion> {
let mut result = ptr::null();
check_status(unsafe { sys::napi_get_node_version(self.0, &mut result) })?;
@ -426,13 +538,12 @@ impl Env {
unsafe extern "C" fn drop_buffer(env: sys::napi_env, finalize_data: *mut c_void, len: *mut c_void) {
let length = Box::from_raw(len as *mut u64);
let length = length.as_ref();
let length = *length as usize;
let _ = Vec::from_raw_parts(finalize_data as *mut u8, length, length);
let mut changed = 0;
let ajust_external_memory_status =
let adjust_external_memory_status =
sys::napi_adjust_external_memory(env, -(length as i64), &mut changed);
debug_assert!(Status::from(ajust_external_memory_status) == Status::Ok);
debug_assert!(Status::from(adjust_external_memory_status) == Status::Ok);
}
unsafe extern "C" fn raw_finalize<T>(

View file

@ -40,6 +40,7 @@ pub struct JsUndefined(pub(crate) Value);
#[derive(Clone, Copy, Debug)]
pub struct JsNull(pub(crate) Value);
#[cfg(napi6)]
#[derive(Clone, Copy, Debug)]
pub struct JsBigint(pub(crate) Value);
@ -165,6 +166,7 @@ impl_js_value_methods!(JsString);
impl_js_value_methods!(JsObject);
impl_js_value_methods!(JsFunction);
impl_js_value_methods!(JsExternal);
#[cfg(napi6)]
impl_js_value_methods!(JsBigint);
impl_js_value_methods!(JsSymbol);
@ -178,6 +180,7 @@ impl_napi_value_trait!(JsString, String);
impl_napi_value_trait!(JsObject, Object);
impl_napi_value_trait!(JsFunction, Function);
impl_napi_value_trait!(JsExternal, External);
#[cfg(napi6)]
impl_napi_value_trait!(JsBigint, Bigint);
impl_napi_value_trait!(JsSymbol, Symbol);

View file

@ -14,6 +14,7 @@ pub enum ValueType {
Object = 6,
Function = 7,
External = 8,
#[cfg(napi6)]
Bigint = 9,
}
@ -24,6 +25,7 @@ impl TryInto<sys::napi_valuetype> for ValueType {
use sys::napi_valuetype::*;
match self {
ValueType::Unknown => Err(Error::from_status(Status::Unknown)),
#[cfg(napi6)]
ValueType::Bigint => Ok(napi_bigint),
ValueType::Boolean => Ok(napi_boolean),
ValueType::External => Ok(napi_external),
@ -42,6 +44,7 @@ impl From<sys::napi_valuetype> for ValueType {
fn from(value: sys::napi_valuetype) -> Self {
use sys::napi_valuetype::*;
match value {
#[cfg(napi6)]
napi_bigint => ValueType::Bigint,
napi_boolean => ValueType::Boolean,
napi_external => ValueType::External,

View file

@ -4,10 +4,15 @@ mod env;
mod error;
mod js_values;
mod module;
#[cfg(all(feature = "libuv", napi4))]
mod promise;
mod status;
pub mod sys;
mod task;
#[cfg(napi4)]
pub mod threadsafe_function;
#[cfg(all(feature = "libuv", napi4))]
mod uv;
mod version;
pub use async_work::AsyncWork;

96
napi/src/promise.rs Normal file
View file

@ -0,0 +1,96 @@
use futures::prelude::*;
use std::os::raw::{c_char, c_void};
use std::ptr;
use crate::error::check_status;
use crate::{sys, Env, NapiValue, Result};
struct FuturePromise<T, V: NapiValue> {
deferred: sys::napi_deferred,
resolver: Box<dyn FnOnce(&mut Env, T) -> Result<V>>,
}
#[inline]
pub async fn resolve_from_future<
T,
V: NapiValue,
R: FnOnce(&mut Env, T) -> Result<V> + 'static,
F: Future<Output = Result<T>>,
>(
env: sys::napi_env,
fut: F,
resolver: R,
raw_deferred: sys::napi_deferred,
) -> Result<()> {
let mut async_resource_name = ptr::null_mut();
let s = "napi_resolve_promise_from_future";
let status = unsafe {
sys::napi_create_string_utf8(
env,
s.as_ptr() as *const c_char,
s.len() as u64,
&mut async_resource_name,
)
};
check_status(status)?;
let initial_thread_count: u64 = 1;
let mut tsfn_value = ptr::null_mut();
let future_promise = FuturePromise {
deferred: raw_deferred,
resolver: Box::from(resolver),
};
let status = unsafe {
sys::napi_create_threadsafe_function(
env,
ptr::null_mut(),
ptr::null_mut(),
async_resource_name,
0,
initial_thread_count,
ptr::null_mut(),
None,
Box::leak(Box::from(future_promise)) as *mut _ as *mut c_void,
Some(call_js_cb::<T, V>),
&mut tsfn_value,
)
};
check_status(status)?;
let val = fut.await?;
check_status(unsafe {
sys::napi_call_threadsafe_function(
tsfn_value,
Box::into_raw(Box::from(val)) as *mut _ as *mut c_void,
sys::napi_threadsafe_function_call_mode::napi_tsfn_nonblocking,
)
})?;
check_status(unsafe {
sys::napi_release_threadsafe_function(
tsfn_value,
sys::napi_threadsafe_function_release_mode::napi_tsfn_release,
)
})
}
unsafe extern "C" fn call_js_cb<T, V: NapiValue>(
raw_env: sys::napi_env,
_js_callback: sys::napi_value,
context: *mut c_void,
data: *mut c_void,
) {
let mut env = Env::from_raw(raw_env);
let future_promise = Box::from_raw(context as *mut FuturePromise<T, V>);
let value = ptr::read(data as *const _);
let js_value_to_resolve = (future_promise.resolver)(&mut env, value);
let deferred = future_promise.deferred;
match js_value_to_resolve {
Ok(v) => {
let status = sys::napi_resolve_deferred(raw_env, deferred, v.raw_value());
debug_assert!(status == sys::napi_status::napi_ok, "Resolve promise failed");
}
Err(e) => {
let status = sys::napi_reject_deferred(raw_env, deferred, e.into_raw(raw_env));
debug_assert!(status == sys::napi_status::napi_ok, "Reject promise failed");
}
};
}

View file

@ -17,9 +17,11 @@ pub enum Status {
EscapeCalledTwice,
HandleScopeMismatch,
CallbackScopeMismatch,
#[cfg(napi4)]
QueueFull,
#[cfg(napi4)]
Closing,
#[cfg(node6)]
#[cfg(napi6)]
BigintExpected,
Unknown,
}
@ -45,9 +47,11 @@ impl From<napi_status> for Status {
napi_escape_called_twice => EscapeCalledTwice,
napi_handle_scope_mismatch => HandleScopeMismatch,
napi_callback_scope_mismatch => CallbackScopeMismatch,
#[cfg(napi4)]
napi_queue_full => QueueFull,
#[cfg(napi4)]
napi_closing => Closing,
#[cfg(node6)]
#[cfg(napi6)]
napi_bigint_expected => BigintExpected,
_ => Unknown,
}
@ -72,9 +76,11 @@ impl Into<self::napi_status> for Status {
Self::EscapeCalledTwice => napi_status::napi_escape_called_twice,
Self::HandleScopeMismatch => napi_status::napi_handle_scope_mismatch,
Self::CallbackScopeMismatch => napi_status::napi_callback_scope_mismatch,
#[cfg(napi4)]
Self::QueueFull => napi_status::napi_queue_full,
#[cfg(napi4)]
Self::Closing => napi_status::napi_closing,
#[cfg(node6)]
#[cfg(napi6)]
Self::BigintExpected => napi_status::napi_bigint_expected,
Self::Unknown => napi_status::napi_generic_failure,
}

96
napi/src/uv.rs Normal file
View file

@ -0,0 +1,96 @@
extern crate alloc;
use alloc::alloc::{alloc, Layout};
use futures::future::LocalBoxFuture;
use futures::task::{waker, ArcWake, Context, Poll};
use std::future::Future;
use std::os::raw::c_void;
use std::pin::Pin;
use std::sync::Arc;
use crate::{sys, Error, Result, Status};
struct Task<'a> {
future: LocalBoxFuture<'a, ()>,
context: Context<'a>,
}
struct UvWaker(*mut sys::uv_async_t);
unsafe impl Send for UvWaker {}
unsafe impl Sync for UvWaker {}
impl UvWaker {
fn new(event_loop: *mut sys::uv_loop_s) -> Result<UvWaker> {
let uv_async_t = unsafe { alloc(Layout::new::<sys::uv_async_t>()) as *mut sys::uv_async_t };
unsafe {
let status = sys::uv_async_init(event_loop, uv_async_t, Some(poll_future));
if status != 0 {
return Err(Error::new(
Status::Unknown,
"Non-zero status returned from uv_async_init".to_owned(),
));
}
};
Ok(UvWaker(uv_async_t))
}
#[inline]
fn assign_task(&self, mut task: Task) {
if !task.poll_future() {
let arc_task = Arc::new(task);
unsafe {
sys::uv_handle_set_data(
self.0 as *mut sys::uv_handle_t,
Arc::into_raw(arc_task) as *mut c_void,
)
};
} else {
unsafe { sys::uv_close(self.0 as *mut sys::uv_handle_t, None) };
};
}
}
impl ArcWake for UvWaker {
fn wake_by_ref(arc_self: &Arc<Self>) {
let status = unsafe { sys::uv_async_send(arc_self.0) };
assert!(status == 0, "wake_uv_async_by_ref failed");
}
}
#[inline]
pub fn execute(event_loop: *mut sys::uv_loop_s, future: LocalBoxFuture<()>) -> Result<()> {
let uv_waker = UvWaker::new(event_loop)?;
let arc_waker = Arc::new(uv_waker);
let waker_to_poll = Arc::clone(&arc_waker);
let waker = waker(arc_waker);
let context = Context::from_waker(&waker);
let task = Task { future, context };
waker_to_poll.assign_task(task);
Ok(())
}
impl<'a> Task<'a> {
fn poll_future(&mut self) -> bool {
let mut pinned = Pin::new(&mut self.future);
let fut_mut = pinned.as_mut();
match fut_mut.poll(&mut self.context) {
Poll::Ready(_) => true,
Poll::Pending => false,
}
}
}
unsafe extern "C" fn poll_future(handle: *mut sys::uv_async_t) {
let data_ptr = sys::uv_handle_get_data(handle as *mut sys::uv_handle_t) as *mut Task;
let mut task = Arc::from_raw(data_ptr);
if let Some(mut_task) = Arc::get_mut(&mut task) {
if mut_task.poll_future() {
sys::uv_close(handle as *mut sys::uv_handle_t, None);
} else {
Arc::into_raw(task);
};
} else {
Arc::into_raw(task);
}
}

View file

@ -8,7 +8,8 @@ edition = "2018"
crate-type = ["cdylib"]
[dependencies]
napi-rs = { path = "../napi" }
futures = "0.3"
napi-rs = { path = "../napi", features = ["libuv"] }
napi-rs-derive = { path = "../napi-derive" }
tokio = { version = "0.2", features = ["default", "fs"]}

View file

@ -2,21 +2,15 @@ const test = require('ava')
const bindings = require('../index.node')
test('should call the function', async (t) => {
const ret = await new Promise((resolve) => {
test('should call the function', (t) => {
bindings.testCallFunction((arg1, arg2) => {
resolve(`${arg1} ${arg2}`)
t.is(`${arg1} ${arg2}`, 'hello world')
})
})
t.is(ret, 'hello world')
})
test('should set "this" properly', async (t) => {
test('should set "this" properly', (t) => {
const obj = {}
const ret = await new Promise((resolve) => {
bindings.testCallFunctionWithThis(obj, function () {
resolve(this)
bindings.testCallFunctionWithThis.call(obj, function () {
t.is(this, obj)
})
})
t.is(ret, obj)
})

View file

@ -0,0 +1,9 @@
const test = require('ava')
const bindings = require('../index.node')
test('should get napi version', (t) => {
const napiVersion = bindings.getNapiVersion()
t.true(typeof napiVersion === 'number')
t.is(`${napiVersion}`, process.versions.napi)
})

View file

@ -1,9 +1,16 @@
const test = require('ava')
const bindings = require('../index.node')
const bindings = require('../../index.node')
const napiVersion = require('../napi-version')
test('should get js function called from a thread', async (t) => {
let called = 0
if (napiVersion < 4) {
t.is(bindings.testThreadsafeFunction, undefined)
return
}
return new Promise((resolve, reject) => {
bindings.testThreadsafeFunction((...args) => {
called += 1

View file

@ -1,11 +1,17 @@
const test = require('ava')
const fs = require('fs')
const path = require('path')
const bindings = require('../index.node')
const bindings = require('../../index.node')
const napiVersion = require('../napi-version')
const filepath = path.resolve(__dirname, './example.txt')
test('should read a file and return its a buffer', async (t) => {
if (napiVersion < 4) {
t.is(bindings.testTokioReadfile, undefined)
return
}
return new Promise((resolve, reject) => {
bindings.testTokioReadfile(filepath, (err, value) => {
try {

View file

@ -1,7 +1,13 @@
const test = require('ava')
const bindings = require('../index.node')
const bindings = require('../../index.node')
const napiVersion = require('../napi-version')
test('should call callback with the first arguments as an Error', async (t) => {
if (napiVersion < 4) {
t.is(bindings.testTsfnError, undefined)
return
}
return new Promise((resolve, reject) => {
bindings.testTsfnError((err) => {
try {

View file

@ -0,0 +1,18 @@
const test = require('ava')
const { join } = require('path')
const { readFileSync } = require('fs')
const bindings = require('../../index.node')
const napiVersion = require('../napi-version')
const filepath = join(__dirname, './example.txt')
test('should call callback with the first arguments as an Error', async (t) => {
if (napiVersion < 4) {
t.is(bindings.uvReadFile, undefined)
return
}
const fileContent = await bindings.uvReadFile(filepath)
t.true(Buffer.isBuffer(fileContent))
t.deepEqual(readFileSync(filepath), fileContent)
})

View file

@ -8,15 +8,15 @@ pub fn call_function(ctx: CallContext) -> Result<JsNull> {
js_func.call(None, &[js_string_hello, js_string_world])?;
Ok(ctx.env.get_null()?)
ctx.env.get_null()
}
#[js_function(2)]
pub fn call_function_with_this(ctx: CallContext) -> Result<JsNull> {
let js_this = ctx.get::<JsObject>(0)?;
let js_func = ctx.get::<JsFunction>(1)?;
#[js_function(1)]
pub fn call_function_with_this(ctx: CallContext<JsObject>) -> Result<JsNull> {
let js_this = ctx.this;
let js_func = ctx.get::<JsFunction>(0)?;
js_func.call(Some(&js_this), &[])?;
Ok(ctx.env.get_null()?)
ctx.env.get_null()
}

View file

@ -5,6 +5,10 @@ extern crate napi_rs_derive;
use napi::{CallContext, Error, JsString, JsUnknown, Module, Result, Status};
#[cfg(napi4)]
mod napi4;
#[cfg(napi4)]
mod libuv;
#[cfg(napi5)]
mod napi5;
@ -13,16 +17,20 @@ mod function;
mod external;
mod symbol;
mod task;
mod tsfn;
mod napi_version;
use buffer::{buffer_to_string, get_buffer_length};
use function::{call_function, call_function_with_this};
use external::{create_external, get_external_count};
#[cfg(napi4)]
use napi4::{test_threadsafe_function, test_tokio_readfile, test_tsfn_error};
#[cfg(napi5)]
use napi5::is_date::test_object_is_date;
use symbol::{create_named_symbol, create_symbol_from_js_string, create_unnamed_symbol};
use task::test_spawn_thread;
use tsfn::{test_threadsafe_function, test_tokio_readfile, test_tsfn_error};
#[cfg(napi4)]
use libuv::read_file::uv_read_file;
use napi_version::get_napi_version;
register_module!(test_module, init);
@ -37,11 +45,17 @@ fn init(module: &mut Module) -> Result<()> {
module.create_named_method("createNamedSymbol", create_named_symbol)?;
module.create_named_method("createUnnamedSymbol", create_unnamed_symbol)?;
module.create_named_method("createSymbolFromJsString", create_symbol_from_js_string)?;
module.create_named_method("testTsfnError", test_tsfn_error)?;
module.create_named_method("testThreadsafeFunction", test_threadsafe_function)?;
module.create_named_method("testTokioReadfile", test_tokio_readfile)?;
module.create_named_method("getNapiVersion", get_napi_version)?;
module.create_named_method("testCallFunction", call_function)?;
module.create_named_method("testCallFunctionWithThis", call_function_with_this)?;
#[cfg(napi4)]
module.create_named_method("testTsfnError", test_tsfn_error)?;
#[cfg(napi4)]
module.create_named_method("testThreadsafeFunction", test_threadsafe_function)?;
#[cfg(napi4)]
module.create_named_method("testTokioReadfile", test_tokio_readfile)?;
#[cfg(napi4)]
module.create_named_method("uvReadFile", uv_read_file)?;
#[cfg(napi5)]
module.create_named_method("testObjectIsDate", test_object_is_date)?;
Ok(())

View file

@ -0,0 +1 @@
pub mod read_file;

View file

@ -0,0 +1,20 @@
use std::thread;
use std::fs;
use futures::prelude::*;
use futures::channel::oneshot;
use napi::{CallContext, Result, JsString, JsObject, Status, Error};
#[js_function(1)]
pub fn uv_read_file(ctx: CallContext) -> Result<JsObject> {
let path = ctx.get::<JsString>(0)?;
let (sender, receiver) = oneshot::channel();
let p = path.as_str()?.to_owned();
thread::spawn(|| {
let res = fs::read(p).map_err(|e| Error::new(Status::Unknown, format!("{}", e)));
sender.send(res).expect("Send data failed");
});
ctx.env.execute(receiver.map_err(|e| Error::new(Status::Unknown, format!("{}", e))).map(|x| x.and_then(|x| x)), |&mut env, data| {
env.create_buffer_with_data(data)
})
}

View file

@ -0,0 +1,3 @@
mod tsfn;
pub use tsfn::*;

View file

@ -0,0 +1,6 @@
use napi::{CallContext, JsNumber, Result};
#[js_function]
pub fn get_napi_version(ctx: CallContext) -> Result<JsNumber> {
ctx.env.create_uint32(ctx.env.get_napi_version()?)
}

View file

@ -1,4 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1