feat(napi): provide execute function to run Future on libuv

This commit is contained in:
LongYinan 2020-07-03 00:31:50 +08:00 committed by LongYinan
parent 646f47ff66
commit f4a331cfe2
No known key found for this signature in database
GPG key ID: A3FFE134A3E20881
26 changed files with 462 additions and 50 deletions

View file

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

View file

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

View file

@ -83,14 +83,14 @@ fn setup_napi_feature() {
let napi_version_number = napi_version.trim().parse::<u32>().unwrap(); 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"); 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); println!("cargo:rustc-cfg=napi{}", napi_version_number);
} else { } else {
for version in 4..napi_version_number { for version in 2..(napi_version_number + 1) {
println!("cargo:rustc-cfg=napi{}", version); 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"] keywords = ["NodeJS", "FFI", "NAPI", "n-api"]
edition = "2018" edition = "2018"
[features]
libuv = ["futures"]
[dependencies]
futures = { version = "0.3", optional = true }
[target.'cfg(windows)'.build-dependencies] [target.'cfg(windows)'.build-dependencies]
flate2 = "1.0" flate2 = "1.0"
reqwest = { version = "0.10", features = ["native-tls", "blocking"] } 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::js_values::*;
use crate::{sys, AsyncWork, Error, NodeVersion, Result, Status}; 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; pub type Callback = extern "C" fn(sys::napi_env, sys::napi_callback_info) -> sys::napi_value;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Env(pub(crate) sys::napi_env); pub struct Env(pub(crate) sys::napi_env);
impl Env { impl Env {
#[inline]
pub fn from_raw(env: sys::napi_env) -> Self { pub fn from_raw(env: sys::napi_env) -> Self {
Env(env) Env(env)
} }
#[inline]
pub fn get_undefined(&self) -> Result<JsUndefined> { pub fn get_undefined(&self) -> Result<JsUndefined> {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_get_undefined(self.0, &mut raw_value) }; 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)) Ok(JsUndefined::from_raw_unchecked(self.0, raw_value))
} }
#[inline]
pub fn get_null(&self) -> Result<JsNull> { pub fn get_null(&self) -> Result<JsNull> {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_get_null(self.0, &mut raw_value) }; 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)) Ok(JsNull::from_raw_unchecked(self.0, raw_value))
} }
#[inline]
pub fn get_boolean(&self, value: bool) -> Result<JsBoolean> { pub fn get_boolean(&self, value: bool) -> Result<JsBoolean> {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_get_boolean(self.0, value, &mut raw_value) }; 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)) Ok(JsBoolean::from_raw_unchecked(self.0, raw_value))
} }
#[inline]
pub fn create_int32(&self, int: i32) -> Result<JsNumber> { pub fn create_int32(&self, int: i32) -> Result<JsNumber> {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
let status = let status =
@ -49,6 +61,7 @@ impl Env {
Ok(JsNumber::from_raw_unchecked(self.0, raw_value)) Ok(JsNumber::from_raw_unchecked(self.0, raw_value))
} }
#[inline]
pub fn create_int64(&self, int: i64) -> Result<JsNumber> { pub fn create_int64(&self, int: i64) -> Result<JsNumber> {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
let status = let status =
@ -57,6 +70,7 @@ impl Env {
Ok(JsNumber::from_raw_unchecked(self.0, raw_value)) Ok(JsNumber::from_raw_unchecked(self.0, raw_value))
} }
#[inline]
pub fn create_uint32(&self, number: u32) -> Result<JsNumber> { pub fn create_uint32(&self, number: u32) -> Result<JsNumber> {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_create_uint32(self.0, number, &mut raw_value) }; 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)) Ok(JsNumber::from_raw_unchecked(self.0, raw_value))
} }
#[inline]
pub fn create_double(&self, double: f64) -> Result<JsNumber> { pub fn create_double(&self, double: f64) -> Result<JsNumber> {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
let status = let status =
@ -72,20 +87,35 @@ impl Env {
Ok(JsNumber::from_raw_unchecked(self.0, raw_value)) Ok(JsNumber::from_raw_unchecked(self.0, raw_value))
} }
#[inline]
pub fn create_string(&self, s: &str) -> Result<JsString> { 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 mut raw_value = ptr::null_mut();
let status = unsafe { let status = unsafe { sys::napi_create_string_utf8(self.0, data_ptr, len, &mut raw_value) };
sys::napi_create_string_utf8(
self.0,
s.as_ptr() as *const c_char,
s.len() as u64,
&mut raw_value,
)
};
check_status(status)?; check_status(status)?;
Ok(JsString::from_raw_unchecked(self.0, raw_value)) Ok(JsString::from_raw_unchecked(self.0, raw_value))
} }
#[inline]
pub fn create_string_utf16(&self, chars: &[u16]) -> Result<JsString> { pub fn create_string_utf16(&self, chars: &[u16]) -> Result<JsString> {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
let status = unsafe { let status = unsafe {
@ -95,12 +125,14 @@ impl Env {
Ok(JsString::from_raw_unchecked(self.0, raw_value)) Ok(JsString::from_raw_unchecked(self.0, raw_value))
} }
#[inline]
pub fn create_symbol_from_js_string(&self, description: JsString) -> Result<JsSymbol> { pub fn create_symbol_from_js_string(&self, description: JsString) -> Result<JsSymbol> {
let mut result = ptr::null_mut(); let mut result = ptr::null_mut();
check_status(unsafe { sys::napi_create_symbol(self.0, description.0.value, &mut result) })?; check_status(unsafe { sys::napi_create_symbol(self.0, description.0.value, &mut result) })?;
Ok(JsSymbol::from_raw_unchecked(self.0, result)) Ok(JsSymbol::from_raw_unchecked(self.0, result))
} }
#[inline]
pub fn create_symbol(&self, description: Option<&str>) -> Result<JsSymbol> { pub fn create_symbol(&self, description: Option<&str>) -> Result<JsSymbol> {
let mut result = ptr::null_mut(); let mut result = ptr::null_mut();
check_status(unsafe { check_status(unsafe {
@ -116,6 +148,7 @@ impl Env {
Ok(JsSymbol::from_raw_unchecked(self.0, result)) Ok(JsSymbol::from_raw_unchecked(self.0, result))
} }
#[inline]
pub fn create_object(&self) -> Result<JsObject> { pub fn create_object(&self) -> Result<JsObject> {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_create_object(self.0, &mut raw_value) }; 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)) Ok(JsObject::from_raw_unchecked(self.0, raw_value))
} }
#[inline]
pub fn create_array_with_length(&self, length: usize) -> Result<JsObject> { pub fn create_array_with_length(&self, length: usize) -> Result<JsObject> {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
let status = let status =
@ -131,6 +165,7 @@ impl Env {
Ok(JsObject::from_raw_unchecked(self.0, raw_value)) Ok(JsObject::from_raw_unchecked(self.0, raw_value))
} }
#[inline]
pub fn create_buffer(&self, length: u64) -> Result<JsBuffer> { pub fn create_buffer(&self, length: u64) -> Result<JsBuffer> {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
let mut data = Vec::with_capacity(length as usize); let mut data = Vec::with_capacity(length as usize);
@ -145,6 +180,7 @@ impl Env {
Ok(buffer) Ok(buffer)
} }
#[inline]
pub fn create_buffer_with_data(&self, data: Vec<u8>) -> Result<JsBuffer> { pub fn create_buffer_with_data(&self, data: Vec<u8>) -> Result<JsBuffer> {
let length = data.len() as u64; let length = data.len() as u64;
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
@ -155,7 +191,7 @@ impl Env {
length, length,
data_ptr as *mut c_void, data_ptr as *mut c_void,
Some(drop_buffer), Some(drop_buffer),
&length as *const _ as *mut c_void, Box::leak(Box::new(length)) as *mut u64 as *mut _,
&mut raw_value, &mut raw_value,
) )
}; };
@ -171,6 +207,7 @@ impl Env {
Ok(buffer) Ok(buffer)
} }
#[inline]
pub fn create_arraybuffer(&self, length: u64) -> Result<JsArrayBuffer> { pub fn create_arraybuffer(&self, length: u64) -> Result<JsArrayBuffer> {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
let mut data = Vec::with_capacity(length as usize); let mut data = Vec::with_capacity(length as usize);
@ -185,6 +222,7 @@ impl Env {
Ok(array_buffer) Ok(array_buffer)
} }
#[inline]
pub fn create_arraybuffer_with_data(&self, data: Vec<u8>) -> Result<JsArrayBuffer> { pub fn create_arraybuffer_with_data(&self, data: Vec<u8>) -> Result<JsArrayBuffer> {
let length = data.len() as u64; let length = data.len() as u64;
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
@ -211,6 +249,7 @@ impl Env {
Ok(array_buffer) Ok(array_buffer)
} }
#[inline]
pub fn create_function(&self, name: &str, callback: Callback) -> Result<JsFunction> { pub fn create_function(&self, name: &str, callback: Callback) -> Result<JsFunction> {
let mut raw_result = ptr::null_mut(); let mut raw_result = ptr::null_mut();
let status = unsafe { let status = unsafe {
@ -229,18 +268,21 @@ impl Env {
Ok(JsFunction::from_raw_unchecked(self.0, raw_result)) Ok(JsFunction::from_raw_unchecked(self.0, raw_result))
} }
#[inline]
pub fn throw(&self, error: Error) -> Result<()> { pub fn throw(&self, error: Error) -> Result<()> {
let err_value = self.create_error(error)?.0.value; let err_value = self.create_error(error)?.0.value;
check_status(unsafe { sys::napi_throw(self.0, err_value) })?; check_status(unsafe { sys::napi_throw(self.0, err_value) })?;
Ok(()) Ok(())
} }
#[inline]
pub fn throw_error(&self, msg: &str) -> Result<()> { pub fn throw_error(&self, msg: &str) -> Result<()> {
let status = unsafe { sys::napi_throw_error(self.0, ptr::null(), msg.as_ptr() as *const _) }; let status = unsafe { sys::napi_throw_error(self.0, ptr::null(), msg.as_ptr() as *const _) };
check_status(status)?; check_status(status)?;
Ok(()) Ok(())
} }
#[inline]
pub fn create_reference<T: NapiValue>(&self, value: T) -> Result<Ref<T>> { pub fn create_reference<T: NapiValue>(&self, value: T) -> Result<Ref<T>> {
let mut raw_ref = ptr::null_mut(); let mut raw_ref = ptr::null_mut();
let initial_ref_count = 1; let initial_ref_count = 1;
@ -253,6 +295,7 @@ impl Env {
Ok(Ref::new(self.0, raw_ref)) Ok(Ref::new(self.0, raw_ref))
} }
#[inline]
pub fn get_reference_value<T: NapiValue>(&self, reference: &Ref<T>) -> Result<T> { pub fn get_reference_value<T: NapiValue>(&self, reference: &Ref<T>) -> Result<T> {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
unsafe { unsafe {
@ -263,6 +306,7 @@ impl Env {
Ok(T::from_raw_unchecked(self.0, raw_value)) Ok(T::from_raw_unchecked(self.0, raw_value))
} }
#[inline]
pub fn define_class( pub fn define_class(
&self, &self,
name: &str, name: &str,
@ -293,6 +337,7 @@ impl Env {
Ok(JsFunction::from_raw_unchecked(self.0, raw_result)) Ok(JsFunction::from_raw_unchecked(self.0, raw_result))
} }
#[inline]
pub fn wrap<T: 'static>(&self, js_object: &mut JsObject, native_object: T) -> Result<()> { pub fn wrap<T: 'static>(&self, js_object: &mut JsObject, native_object: T) -> Result<()> {
let status = unsafe { let status = unsafe {
sys::napi_wrap( sys::napi_wrap(
@ -308,6 +353,7 @@ impl Env {
check_status(status).or(Ok(())) check_status(status).or(Ok(()))
} }
#[inline]
pub fn unwrap<T: 'static>(&self, js_object: &JsObject) -> Result<&mut T> { pub fn unwrap<T: 'static>(&self, js_object: &JsObject) -> Result<&mut T> {
unsafe { unsafe {
let mut unknown_tagged_object: *mut c_void = ptr::null_mut(); 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<()> { pub fn drop_wrapped<T: 'static>(&self, js_object: JsObject) -> Result<()> {
unsafe { unsafe {
let mut unknown_tagged_object: *mut c_void = ptr::null_mut(); 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> { pub fn create_external<T: 'static>(&self, native_object: T) -> Result<JsExternal> {
let mut object_value = ptr::null_mut(); let mut object_value = ptr::null_mut();
let status = unsafe { let status = unsafe {
@ -367,6 +415,7 @@ impl Env {
Ok(JsExternal::from_raw_unchecked(self.0, object_value)) Ok(JsExternal::from_raw_unchecked(self.0, object_value))
} }
#[inline]
pub fn get_value_external<T: 'static>(&self, js_external: &JsExternal) -> Result<&mut T> { pub fn get_value_external<T: 'static>(&self, js_external: &JsExternal) -> Result<&mut T> {
unsafe { unsafe {
let mut unknown_tagged_object = ptr::null_mut(); let mut unknown_tagged_object = ptr::null_mut();
@ -391,6 +440,7 @@ impl Env {
} }
} }
#[inline]
pub fn create_error(&self, e: Error) -> Result<JsObject> { pub fn create_error(&self, e: Error) -> Result<JsObject> {
let reason = e.reason; let reason = e.reason;
let reason_string = self.create_string(reason.as_str())?; let reason_string = self.create_string(reason.as_str())?;
@ -407,6 +457,7 @@ impl Env {
Ok(JsObject::from_raw_unchecked(self.0, result)) Ok(JsObject::from_raw_unchecked(self.0, result))
} }
#[inline]
pub fn spawn<T: 'static + Task>(&self, task: T) -> Result<JsObject> { pub fn spawn<T: 'static + Task>(&self, task: T) -> Result<JsObject> {
let mut raw_promise = ptr::null_mut(); let mut raw_promise = ptr::null_mut();
let mut raw_deferred = 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)) 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> { pub fn get_node_version(&self) -> Result<NodeVersion> {
let mut result = ptr::null(); let mut result = ptr::null();
check_status(unsafe { sys::napi_get_node_version(self.0, &mut result) })?; check_status(unsafe { sys::napi_get_node_version(self.0, &mut result) })?;
@ -426,7 +538,6 @@ impl Env {
unsafe extern "C" fn drop_buffer(env: sys::napi_env, finalize_data: *mut c_void, len: *mut c_void) { 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 = Box::from_raw(len as *mut u64);
let length = length.as_ref();
let length = *length as usize; let length = *length as usize;
let _ = Vec::from_raw_parts(finalize_data as *mut u8, length, length); let _ = Vec::from_raw_parts(finalize_data as *mut u8, length, length);
let mut changed = 0; let mut changed = 0;

View file

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

View file

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

View file

@ -4,10 +4,15 @@ mod env;
mod error; mod error;
mod js_values; mod js_values;
mod module; mod module;
#[cfg(all(feature = "libuv", napi4))]
mod promise;
mod status; mod status;
pub mod sys; pub mod sys;
mod task; mod task;
#[cfg(napi4)]
pub mod threadsafe_function; pub mod threadsafe_function;
#[cfg(all(feature = "libuv", napi4))]
mod uv;
mod version; mod version;
pub use async_work::AsyncWork; 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, EscapeCalledTwice,
HandleScopeMismatch, HandleScopeMismatch,
CallbackScopeMismatch, CallbackScopeMismatch,
#[cfg(napi4)]
QueueFull, QueueFull,
#[cfg(napi4)]
Closing, Closing,
#[cfg(node6)] #[cfg(napi6)]
BigintExpected, BigintExpected,
Unknown, Unknown,
} }
@ -45,9 +47,11 @@ impl From<napi_status> for Status {
napi_escape_called_twice => EscapeCalledTwice, napi_escape_called_twice => EscapeCalledTwice,
napi_handle_scope_mismatch => HandleScopeMismatch, napi_handle_scope_mismatch => HandleScopeMismatch,
napi_callback_scope_mismatch => CallbackScopeMismatch, napi_callback_scope_mismatch => CallbackScopeMismatch,
#[cfg(napi4)]
napi_queue_full => QueueFull, napi_queue_full => QueueFull,
#[cfg(napi4)]
napi_closing => Closing, napi_closing => Closing,
#[cfg(node6)] #[cfg(napi6)]
napi_bigint_expected => BigintExpected, napi_bigint_expected => BigintExpected,
_ => Unknown, _ => Unknown,
} }
@ -72,9 +76,11 @@ impl Into<self::napi_status> for Status {
Self::EscapeCalledTwice => napi_status::napi_escape_called_twice, Self::EscapeCalledTwice => napi_status::napi_escape_called_twice,
Self::HandleScopeMismatch => napi_status::napi_handle_scope_mismatch, Self::HandleScopeMismatch => napi_status::napi_handle_scope_mismatch,
Self::CallbackScopeMismatch => napi_status::napi_callback_scope_mismatch, Self::CallbackScopeMismatch => napi_status::napi_callback_scope_mismatch,
#[cfg(napi4)]
Self::QueueFull => napi_status::napi_queue_full, Self::QueueFull => napi_status::napi_queue_full,
#[cfg(napi4)]
Self::Closing => napi_status::napi_closing, Self::Closing => napi_status::napi_closing,
#[cfg(node6)] #[cfg(napi6)]
Self::BigintExpected => napi_status::napi_bigint_expected, Self::BigintExpected => napi_status::napi_bigint_expected,
Self::Unknown => napi_status::napi_generic_failure, 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"] crate-type = ["cdylib"]
[dependencies] [dependencies]
napi-rs = { path = "../napi" } futures = "0.3"
napi-rs = { path = "../napi", features = ["libuv"] }
napi-rs-derive = { path = "../napi-derive" } napi-rs-derive = { path = "../napi-derive" }
tokio = { version = "0.2", features = ["default", "fs"]} tokio = { version = "0.2", features = ["default", "fs"]}

View file

@ -2,21 +2,15 @@ const test = require('ava')
const bindings = require('../index.node') const bindings = require('../index.node')
test('should call the function', async (t) => { test('should call the function', (t) => {
const ret = await new Promise((resolve) => { bindings.testCallFunction((arg1, arg2) => {
bindings.testCallFunction((arg1, arg2) => { t.is(`${arg1} ${arg2}`, 'hello world')
resolve(`${arg1} ${arg2}`)
})
}) })
t.is(ret, 'hello world')
}) })
test('should set "this" properly', async (t) => { test('should set "this" properly', (t) => {
const obj = {} const obj = {}
const ret = await new Promise((resolve) => { bindings.testCallFunctionWithThis.call(obj, function () {
bindings.testCallFunctionWithThis(obj, function () { t.is(this, obj)
resolve(this)
})
}) })
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 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) => { test('should get js function called from a thread', async (t) => {
let called = 0 let called = 0
if (napiVersion < 4) {
t.is(bindings.testThreadsafeFunction, undefined)
return
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
bindings.testThreadsafeFunction((...args) => { bindings.testThreadsafeFunction((...args) => {
called += 1 called += 1

View file

@ -1,11 +1,17 @@
const test = require('ava') const test = require('ava')
const fs = require('fs') const fs = require('fs')
const path = require('path') 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') const filepath = path.resolve(__dirname, './example.txt')
test('should read a file and return its a buffer', async (t) => { 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) => { return new Promise((resolve, reject) => {
bindings.testTokioReadfile(filepath, (err, value) => { bindings.testTokioReadfile(filepath, (err, value) => {
try { try {

View file

@ -1,7 +1,13 @@
const test = require('ava') 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) => { 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) => { return new Promise((resolve, reject) => {
bindings.testTsfnError((err) => { bindings.testTsfnError((err) => {
try { 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])?; js_func.call(None, &[js_string_hello, js_string_world])?;
Ok(ctx.env.get_null()?) ctx.env.get_null()
} }
#[js_function(2)] #[js_function(1)]
pub fn call_function_with_this(ctx: CallContext) -> Result<JsNull> { pub fn call_function_with_this(ctx: CallContext<JsObject>) -> Result<JsNull> {
let js_this = ctx.get::<JsObject>(0)?; let js_this = ctx.this;
let js_func = ctx.get::<JsFunction>(1)?; let js_func = ctx.get::<JsFunction>(0)?;
js_func.call(Some(&js_this), &[])?; 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}; use napi::{CallContext, Error, JsString, JsUnknown, Module, Result, Status};
#[cfg(napi4)]
mod napi4;
#[cfg(napi4)]
mod libuv;
#[cfg(napi5)] #[cfg(napi5)]
mod napi5; mod napi5;
@ -13,16 +17,20 @@ mod function;
mod external; mod external;
mod symbol; mod symbol;
mod task; mod task;
mod tsfn; mod napi_version;
use buffer::{buffer_to_string, get_buffer_length}; use buffer::{buffer_to_string, get_buffer_length};
use function::{call_function, call_function_with_this}; use function::{call_function, call_function_with_this};
use external::{create_external, get_external_count}; use external::{create_external, get_external_count};
#[cfg(napi4)]
use napi4::{test_threadsafe_function, test_tokio_readfile, test_tsfn_error};
#[cfg(napi5)] #[cfg(napi5)]
use napi5::is_date::test_object_is_date; use napi5::is_date::test_object_is_date;
use symbol::{create_named_symbol, create_symbol_from_js_string, create_unnamed_symbol}; use symbol::{create_named_symbol, create_symbol_from_js_string, create_unnamed_symbol};
use task::test_spawn_thread; 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); 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("createNamedSymbol", create_named_symbol)?;
module.create_named_method("createUnnamedSymbol", create_unnamed_symbol)?; module.create_named_method("createUnnamedSymbol", create_unnamed_symbol)?;
module.create_named_method("createSymbolFromJsString", create_symbol_from_js_string)?; module.create_named_method("createSymbolFromJsString", create_symbol_from_js_string)?;
module.create_named_method("testTsfnError", test_tsfn_error)?; module.create_named_method("getNapiVersion", get_napi_version)?;
module.create_named_method("testThreadsafeFunction", test_threadsafe_function)?;
module.create_named_method("testTokioReadfile", test_tokio_readfile)?;
module.create_named_method("testCallFunction", call_function)?; module.create_named_method("testCallFunction", call_function)?;
module.create_named_method("testCallFunctionWithThis", call_function_with_this)?; 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)] #[cfg(napi5)]
module.create_named_method("testObjectIsDate", test_object_is_date)?; module.create_named_method("testObjectIsDate", test_object_is_date)?;
Ok(()) 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()?)
}