diff --git a/.github/workflows/napi4.yaml b/.github/workflows/napi3.yaml similarity index 83% rename from .github/workflows/napi4.yaml rename to .github/workflows/napi3.yaml index c06475a1..1cc3afc4 100644 --- a/.github/workflows/napi4.yaml +++ b/.github/workflows/napi3.yaml @@ -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: diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index b6fd81dd..1b2edc67 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -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 diff --git a/build/src/lib.rs b/build/src/lib.rs index de75193d..5f18b121 100644 --- a/build/src/lib.rs +++ b/build/src/lib.rs @@ -83,14 +83,14 @@ fn setup_napi_feature() { let napi_version_number = napi_version.trim().parse::().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); } } diff --git a/napi/Cargo.toml b/napi/Cargo.toml index a9c967d4..365064a6 100644 --- a/napi/Cargo.toml +++ b/napi/Cargo.toml @@ -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"] } diff --git a/napi/src/env.rs b/napi/src/env.rs index 4c5b1be1..14047573 100644 --- a/napi/src/env.rs +++ b/napi/src/env.rs @@ -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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { + 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 { + 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) -> Result { + 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) -> Result { + 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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) -> Result { 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 { 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) -> Result { 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 { 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(&self, value: T) -> Result> { 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(&self, reference: &Ref) -> Result { 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(&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(&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(&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(&self, native_object: T) -> Result { 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(&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 { 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(&self, task: T) -> Result { 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 { + 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 { + let global = self.get_global()?; + let process = global.get_named_property::("process")?; + let versions = process.get_named_property::("versions")?; + let napi_version = versions.get_named_property::("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>, + R: 'static + Send + Sync + FnOnce(&mut Env, T) -> Result, + >( + &self, + deferred: F, + resolver: R, + ) -> Result { + 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 { 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( diff --git a/napi/src/js_values/mod.rs b/napi/src/js_values/mod.rs index 84e4abad..3d3472d0 100644 --- a/napi/src/js_values/mod.rs +++ b/napi/src/js_values/mod.rs @@ -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); diff --git a/napi/src/js_values/value_type.rs b/napi/src/js_values/value_type.rs index 9163cd0e..2908e902 100644 --- a/napi/src/js_values/value_type.rs +++ b/napi/src/js_values/value_type.rs @@ -14,6 +14,7 @@ pub enum ValueType { Object = 6, Function = 7, External = 8, + #[cfg(napi6)] Bigint = 9, } @@ -24,6 +25,7 @@ impl TryInto 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 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, diff --git a/napi/src/lib.rs b/napi/src/lib.rs index 879fffeb..33c296b0 100644 --- a/napi/src/lib.rs +++ b/napi/src/lib.rs @@ -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; diff --git a/napi/src/promise.rs b/napi/src/promise.rs new file mode 100644 index 00000000..0b13c319 --- /dev/null +++ b/napi/src/promise.rs @@ -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 { + deferred: sys::napi_deferred, + resolver: Box Result>, +} + +#[inline] +pub async fn resolve_from_future< + T, + V: NapiValue, + R: FnOnce(&mut Env, T) -> Result + 'static, + F: Future>, +>( + 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::), + &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( + 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); + 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"); + } + }; +} diff --git a/napi/src/status.rs b/napi/src/status.rs index cb38de28..de981ed1 100644 --- a/napi/src/status.rs +++ b/napi/src/status.rs @@ -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 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 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, } diff --git a/napi/src/uv.rs b/napi/src/uv.rs new file mode 100644 index 00000000..bf91fc19 --- /dev/null +++ b/napi/src/uv.rs @@ -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 { + let uv_async_t = unsafe { alloc(Layout::new::()) 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) { + 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); + } +} diff --git a/test_module/Cargo.toml b/test_module/Cargo.toml index d0dfd8c1..4ed26aeb 100644 --- a/test_module/Cargo.toml +++ b/test_module/Cargo.toml @@ -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"]} diff --git a/test_module/__test__/function.spec.js b/test_module/__test__/function.spec.js index 103b8b7b..d23585cc 100644 --- a/test_module/__test__/function.spec.js +++ b/test_module/__test__/function.spec.js @@ -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) => { - bindings.testCallFunction((arg1, arg2) => { - resolve(`${arg1} ${arg2}`) - }) +test('should call the function', (t) => { + bindings.testCallFunction((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) }) diff --git a/test_module/__test__/get-napi-version.spec.js b/test_module/__test__/get-napi-version.spec.js new file mode 100644 index 00000000..42a8eccc --- /dev/null +++ b/test_module/__test__/get-napi-version.spec.js @@ -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) +}) diff --git a/test_module/__test__/example.txt b/test_module/__test__/napi4/example.txt similarity index 100% rename from test_module/__test__/example.txt rename to test_module/__test__/napi4/example.txt diff --git a/test_module/__test__/threadsafe_function.spec.js b/test_module/__test__/napi4/threadsafe_function.spec.js similarity index 68% rename from test_module/__test__/threadsafe_function.spec.js rename to test_module/__test__/napi4/threadsafe_function.spec.js index eac5de27..418db223 100644 --- a/test_module/__test__/threadsafe_function.spec.js +++ b/test_module/__test__/napi4/threadsafe_function.spec.js @@ -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 diff --git a/test_module/__test__/tokio_readfile.spec.js b/test_module/__test__/napi4/tokio_readfile.spec.js similarity index 74% rename from test_module/__test__/tokio_readfile.spec.js rename to test_module/__test__/napi4/tokio_readfile.spec.js index 695caa03..6c39c027 100644 --- a/test_module/__test__/tokio_readfile.spec.js +++ b/test_module/__test__/napi4/tokio_readfile.spec.js @@ -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 { diff --git a/test_module/__test__/tsfn_error.spec.js b/test_module/__test__/napi4/tsfn_error.spec.js similarity index 67% rename from test_module/__test__/tsfn_error.spec.js rename to test_module/__test__/napi4/tsfn_error.spec.js index d692a920..ad5a5468 100644 --- a/test_module/__test__/tsfn_error.spec.js +++ b/test_module/__test__/napi4/tsfn_error.spec.js @@ -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 { diff --git a/test_module/__test__/napi4/uv.spec.js b/test_module/__test__/napi4/uv.spec.js new file mode 100644 index 00000000..d31eeaae --- /dev/null +++ b/test_module/__test__/napi4/uv.spec.js @@ -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) +}) diff --git a/test_module/src/function.rs b/test_module/src/function.rs index 6e7a13ec..733f7f8e 100644 --- a/test_module/src/function.rs +++ b/test_module/src/function.rs @@ -8,15 +8,15 @@ pub fn call_function(ctx: CallContext) -> Result { 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 { - let js_this = ctx.get::(0)?; - let js_func = ctx.get::(1)?; +#[js_function(1)] +pub fn call_function_with_this(ctx: CallContext) -> Result { + let js_this = ctx.this; + let js_func = ctx.get::(0)?; js_func.call(Some(&js_this), &[])?; - Ok(ctx.env.get_null()?) + ctx.env.get_null() } diff --git a/test_module/src/lib.rs b/test_module/src/lib.rs index bcf0847c..b01a595a 100644 --- a/test_module/src/lib.rs +++ b/test_module/src/lib.rs @@ -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(()) diff --git a/test_module/src/libuv/mod.rs b/test_module/src/libuv/mod.rs new file mode 100644 index 00000000..1cbe4ba6 --- /dev/null +++ b/test_module/src/libuv/mod.rs @@ -0,0 +1 @@ +pub mod read_file; diff --git a/test_module/src/libuv/read_file.rs b/test_module/src/libuv/read_file.rs new file mode 100644 index 00000000..04258f56 --- /dev/null +++ b/test_module/src/libuv/read_file.rs @@ -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 { + let path = ctx.get::(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) + }) +} diff --git a/test_module/src/napi4/mod.rs b/test_module/src/napi4/mod.rs new file mode 100644 index 00000000..832dd8ee --- /dev/null +++ b/test_module/src/napi4/mod.rs @@ -0,0 +1,3 @@ +mod tsfn; + +pub use tsfn::*; diff --git a/test_module/src/tsfn.rs b/test_module/src/napi4/tsfn.rs similarity index 100% rename from test_module/src/tsfn.rs rename to test_module/src/napi4/tsfn.rs diff --git a/test_module/src/napi_version.rs b/test_module/src/napi_version.rs new file mode 100644 index 00000000..158385b0 --- /dev/null +++ b/test_module/src/napi_version.rs @@ -0,0 +1,6 @@ +use napi::{CallContext, JsNumber, Result}; + +#[js_function] +pub fn get_napi_version(ctx: CallContext) -> Result { + ctx.env.create_uint32(ctx.env.get_napi_version()?) +} diff --git a/test_module/yarn.lock b/test_module/yarn.lock deleted file mode 100644 index fb57ccd1..00000000 --- a/test_module/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - -