feat(napi): provide execute function to run Future on libuv
This commit is contained in:
parent
646f47ff66
commit
f4a331cfe2
26 changed files with 462 additions and 50 deletions
|
@ -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:
|
2
.github/workflows/windows.yaml
vendored
2
.github/workflows/windows.yaml
vendored
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"] }
|
||||
|
|
131
napi/src/env.rs
131
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<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,7 +191,7 @@ 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,
|
||||
)
|
||||
};
|
||||
|
@ -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();
|
||||
|
@ -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,7 +538,6 @@ 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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
96
napi/src/promise.rs
Normal 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");
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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
96
napi/src/uv.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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"]}
|
||||
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
9
test_module/__test__/get-napi-version.spec.js
Normal file
9
test_module/__test__/get-napi-version.spec.js
Normal 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)
|
||||
})
|
|
@ -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
|
|
@ -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 {
|
|
@ -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 {
|
18
test_module/__test__/napi4/uv.spec.js
Normal file
18
test_module/__test__/napi4/uv.spec.js
Normal 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)
|
||||
})
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
|
|
1
test_module/src/libuv/mod.rs
Normal file
1
test_module/src/libuv/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod read_file;
|
20
test_module/src/libuv/read_file.rs
Normal file
20
test_module/src/libuv/read_file.rs
Normal 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)
|
||||
})
|
||||
}
|
3
test_module/src/napi4/mod.rs
Normal file
3
test_module/src/napi4/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod tsfn;
|
||||
|
||||
pub use tsfn::*;
|
6
test_module/src/napi_version.rs
Normal file
6
test_module/src/napi_version.rs
Normal 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()?)
|
||||
}
|
Loading…
Reference in a new issue