diff --git a/napi/src/env.rs b/napi/src/env.rs index 2e46f0dc..2b4d2d98 100644 --- a/napi/src/env.rs +++ b/napi/src/env.rs @@ -5,10 +5,11 @@ use std::mem; use std::os::raw::{c_char, c_void}; use std::ptr; +use crate::async_work::AsyncWork; use crate::error::check_status; use crate::js_values::*; use crate::task::Task; -use crate::{sys, AsyncWork, Error, NodeVersion, Result, Status}; +use crate::{sys, Error, NodeVersion, Result, Status}; #[cfg(all(any(feature = "libuv", feature = "tokio_rt"), napi4))] use crate::promise; diff --git a/napi/src/error.rs b/napi/src/error.rs index 2dcabbf3..6c44603d 100644 --- a/napi/src/error.rs +++ b/napi/src/error.rs @@ -37,7 +37,7 @@ impl Error { } } - pub fn into_raw(self, env: sys::napi_env) -> sys::napi_value { + pub(crate) fn into_raw(self, env: sys::napi_env) -> sys::napi_value { let mut err = ptr::null_mut(); let s = self.reason; unsafe { diff --git a/napi/src/js_values/mod.rs b/napi/src/js_values/mod.rs index 3d3472d0..37eb34ae 100644 --- a/napi/src/js_values/mod.rs +++ b/napi/src/js_values/mod.rs @@ -25,9 +25,9 @@ pub use function::JsFunction; pub use number::JsNumber; pub use object::JsObject; pub use string::JsString; -pub use tagged_object::TaggedObject; -pub use value::Value; -pub use value_ref::Ref; +pub(crate) use tagged_object::TaggedObject; +pub(crate) use value::Value; +pub(crate) use value_ref::Ref; pub use value_type::ValueType; // Value types @@ -51,7 +51,7 @@ pub struct JsSymbol(pub(crate) Value); pub struct JsExternal(pub(crate) Value); #[inline] -pub fn type_of(env: sys::napi_env, raw_value: sys::napi_value) -> Result { +pub(crate) fn type_of(env: sys::napi_env, raw_value: sys::napi_value) -> Result { unsafe { let mut value_type = sys::napi_valuetype::napi_undefined; check_status(sys::napi_typeof(env, raw_value, &mut value_type))?; diff --git a/napi/src/lib.rs b/napi/src/lib.rs index 6abd9085..3a274512 100644 --- a/napi/src/lib.rs +++ b/napi/src/lib.rs @@ -1,3 +1,61 @@ +//! High level NodeJS [N-API](https://nodejs.org/api/n-api.html) binding +//! +//! **napi-rs** provides minimal overhead to write N-API modules in `Rust`. +//! ## Feature flags +//! ### libuv +//! With `libuv` feature, you can execute a rust future in `libuv` in NodeJS, and return a `promise` object. +//! ``` +//! 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) +//! }) +//! } +//! ``` +//! ### tokio_rt +//! With `tokio_rt` feature, `napi-rs` provides a ***tokio runtime*** in an additional thread. +//! And you can easily run tokio `future` in it and return `promise`. +//! +//! ``` +//! use futures::prelude::*; +//! use napi::{CallContext, Error, JsObject, JsString, Result, Status}; +//! use tokio; +//! +//! #[js_function(1)] +//! pub fn tokio_readfile(ctx: CallContext) -> Result { +//! let js_filepath = ctx.get::(0)?; +//! let path_str = js_filepath.as_str()?; +//! ctx.env.execute_tokio_future( +//! tokio::fs::read(path_str.to_owned()) +//! .map(|v| v.map_err(|e| Error::new(Status::Unknown, format!("failed to read file, {}", e)))), +//! |&mut env, data| env.create_buffer_with_data(data), +//! ) +//! } +//! ``` +//! +//! ***Tokio channel in `napi-rs` buffer size is default `100`.*** +//! +//! ***You can adjust it via `NAPI_RS_TOKIO_CHANNEL_BUFFER_SIZE` environment variable*** +//! +//! ``` +//! NAPI_RS_TOKIO_CHANNEL_BUFFER_SIZE=1000 node ./app.js +//! ``` +//! + mod async_work; mod call_context; mod env; @@ -17,20 +75,28 @@ mod tokio_rt; mod uv; mod version; -pub use async_work::AsyncWork; pub use call_context::CallContext; pub use env::*; pub use error::{Error, Result}; pub use js_values::*; pub use module::Module; pub use status::Status; -pub use sys::napi_valuetype; pub use task::Task; pub use version::NodeVersion; #[cfg(all(feature = "tokio_rt", napi4))] pub use tokio_rt::shutdown as shutdown_tokio_rt; +/// register nodejs module +/// +/// ## Example +/// ``` +/// register_module!(test_module, init); +/// +/// fn init(module: &mut Module) -> Result<()> { +/// module.create_named_method("nativeFunction", native_function)?; +/// } +/// ``` #[macro_export] macro_rules! register_module { ($module_name:ident, $init:ident) => { diff --git a/napi/src/tokio_rt.rs b/napi/src/tokio_rt.rs index e7432211..387cee6e 100644 --- a/napi/src/tokio_rt.rs +++ b/napi/src/tokio_rt.rs @@ -1,5 +1,5 @@ +use std::env::var; use std::ffi::c_void; -use std::mem; use std::pin::Pin; use std::thread::spawn; use std::time::Duration; @@ -20,7 +20,11 @@ pub(crate) enum Message { pub(crate) fn get_tokio_sender() -> &'static mpsc::Sender { static SENDER: OnceCell> = OnceCell::new(); SENDER.get_or_init(|| { - let (sender, mut receiver) = mpsc::channel(100); + let buffer_size = var("NAPI_RS_TOKIO_CHANNEL_BUFFER_SIZE") + .map_err(|_| ()) + .and_then(|s| s.parse().map_err(|_| ())) + .unwrap_or(100); + let (sender, mut receiver) = mpsc::channel(buffer_size); spawn(move || { let mut rt = Runtime::new().expect("Failed to create tokio runtime"); rt.block_on(async { @@ -33,7 +37,6 @@ pub(crate) fn get_tokio_sender() -> &'static mpsc::Sender { } }); rt.shutdown_timeout(Duration::from_secs(5)); - mem::drop(receiver); }); sender diff --git a/test_module/__test__/napi4/tokio_rt-isolate.spec.js b/test_module/__test__/napi4/tokio_rt-isolate.spec.js new file mode 100644 index 00000000..ea01323c --- /dev/null +++ b/test_module/__test__/napi4/tokio_rt-isolate.spec.js @@ -0,0 +1,27 @@ +const test = require('ava') +const { join } = require('path') + +const napiVersion = require('../napi-version') + +const filepath = join(__dirname, './example.txt') + +process.env.NAPI_RS_TOKIO_CHANNEL_BUFFER_SIZE = '1' + +const bindings = require('../../index.node') + +test('should be able adjust queue size via process.env', async (t) => { + if (napiVersion < 4) { + t.is(bindings.testExecuteTokioReadfile, undefined) + return + } + try { + await Promise.all( + Array.from({ length: 50 }).map((_) => + bindings.testExecuteTokioReadfile(filepath), + ), + ) + throw new TypeError('Unreachable') + } catch (e) { + t.is(e.message, 'QueueFull: Failed to run future: no available capacity') + } +})