feat(napi): implement env cleanup hook

This commit is contained in:
LongYinan 2020-10-04 16:02:04 +08:00
parent 973d3ee008
commit 45411a59ea
No known key found for this signature in database
GPG key ID: C3666B7FC82ADAD7
8 changed files with 106 additions and 8 deletions

View file

@ -4,7 +4,7 @@ use crate::error::check_status;
use crate::{sys, Either, Env, Error, JsUndefined, NapiValue, Result, Status}; use crate::{sys, Either, Env, Error, JsUndefined, NapiValue, Result, Status};
pub struct CallContext<'env> { pub struct CallContext<'env> {
pub env: &'env Env, pub env: &'env mut Env,
raw_this: sys::napi_value, raw_this: sys::napi_value,
callback_info: sys::napi_callback_info, callback_info: sys::napi_callback_info,
args: &'env [sys::napi_value], args: &'env [sys::napi_value],
@ -14,7 +14,7 @@ pub struct CallContext<'env> {
impl<'env> CallContext<'env> { impl<'env> CallContext<'env> {
pub fn new( pub fn new(
env: &'env Env, env: &'env mut Env,
callback_info: sys::napi_callback_info, callback_info: sys::napi_callback_info,
raw_this: sys::napi_value, raw_this: sys::napi_value,
args: &'env [sys::napi_value], args: &'env [sys::napi_value],

8
napi/src/cleanup_env.rs Normal file
View file

@ -0,0 +1,8 @@
pub(crate) struct CleanupEnvHookData<T: 'static> {
pub(crate) data: T,
pub(crate) hook: Box<dyn FnOnce(T) -> ()>,
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy)]
pub struct CleanupEnvHook<T: 'static>(pub(crate) *mut CleanupEnvHookData<T>);

View file

@ -5,6 +5,7 @@ use std::mem;
use std::os::raw::{c_char, c_void}; use std::os::raw::{c_char, c_void};
use std::ptr; use std::ptr;
use super::cleanup_env::{CleanupEnvHook, CleanupEnvHookData};
use crate::async_work::{self, AsyncWorkPromise}; use crate::async_work::{self, AsyncWorkPromise};
use crate::error::check_status; use crate::error::check_status;
use crate::js_values::*; use crate::js_values::*;
@ -455,13 +456,12 @@ impl Env {
let tagged_object: *mut TaggedObject<T> = mem::transmute(unknown_tagged_object); let tagged_object: *mut TaggedObject<T> = mem::transmute(unknown_tagged_object);
(*tagged_object).object.as_mut().ok_or(Error { (*tagged_object).object.as_mut().ok_or(Error {
status: Status::InvalidArg, status: Status::InvalidArg,
reason: "Invalid argument, nothing attach to js_external".to_owned(), reason: "nothing attach to js_external".to_owned(),
}) })
} else { } else {
Err(Error { Err(Error {
status: Status::InvalidArg, status: Status::InvalidArg,
reason: "Invalid argument, T on get_value_external is not the type of wrapped object" reason: "T on get_value_external is not the type of wrapped object".to_owned(),
.to_owned(),
}) })
} }
} }
@ -519,6 +519,41 @@ impl Env {
Ok(uv_loop) Ok(uv_loop)
} }
#[cfg(napi3)]
pub fn add_env_cleanup_hook<T, F>(
&mut self,
cleanup_data: T,
cleanup_fn: F,
) -> Result<CleanupEnvHook<T>>
where
T: 'static,
F: 'static + FnOnce(T) -> (),
{
let hook = CleanupEnvHookData {
data: cleanup_data,
hook: Box::new(cleanup_fn),
};
let hook_ref = Box::leak(Box::new(hook));
check_status(unsafe {
sys::napi_add_env_cleanup_hook(
self.0,
Some(cleanup_env::<T>),
hook_ref as *mut CleanupEnvHookData<T> as *mut _,
)
})?;
Ok(CleanupEnvHook(hook_ref))
}
#[cfg(napi3)]
pub fn remove_env_cleanup_hook<T>(&mut self, hook: CleanupEnvHook<T>) -> Result<()>
where
T: 'static,
{
check_status(unsafe {
sys::napi_remove_env_cleanup_hook(self.0, Some(cleanup_env::<T>), hook.0 as *mut _)
})
}
#[cfg(all(feature = "libuv", napi4))] #[cfg(all(feature = "libuv", napi4))]
pub fn execute< pub fn execute<
T: 'static + Send, T: 'static + Send,
@ -665,3 +700,8 @@ unsafe extern "C" fn raw_finalize<T>(
let tagged_object: *mut TaggedObject<T> = mem::transmute(finalize_data); let tagged_object: *mut TaggedObject<T> = mem::transmute(finalize_data);
Box::from_raw(tagged_object); Box::from_raw(tagged_object);
} }
unsafe extern "C" fn cleanup_env<T: 'static>(hook_data: *mut c_void) {
let cleanup_env_hook = Box::from_raw(hook_data as *mut CleanupEnvHookData<T>);
(cleanup_env_hook.hook)(cleanup_env_hook.data);
}

View file

@ -92,6 +92,7 @@
mod async_work; mod async_work;
mod call_context; mod call_context;
mod cleanup_env;
mod env; mod env;
mod error; mod error;
mod js_values; mod js_values;
@ -100,6 +101,8 @@ mod module;
mod promise; mod promise;
mod status; mod status;
mod task; mod task;
#[cfg(napi3)]
pub use cleanup_env::CleanupEnvHook;
#[cfg(napi4)] #[cfg(napi4)]
pub mod threadsafe_function; pub mod threadsafe_function;
#[cfg(all(feature = "tokio_rt", napi4))] #[cfg(all(feature = "tokio_rt", napi4))]

View file

@ -124,15 +124,14 @@ impl<T: ToJs> ThreadsafeFunction<T> {
pub fn create(env: &Env, func: JsFunction, to_js: T, max_queue_size: u64) -> Result<Self> { pub fn create(env: &Env, func: JsFunction, to_js: T, max_queue_size: u64) -> Result<Self> {
let mut async_resource_name = ptr::null_mut(); let mut async_resource_name = ptr::null_mut();
let s = "napi_rs_threadsafe_function"; let s = "napi_rs_threadsafe_function";
let status = unsafe { check_status(unsafe {
sys::napi_create_string_utf8( sys::napi_create_string_utf8(
env.0, env.0,
s.as_ptr() as *const c_char, s.as_ptr() as *const c_char,
s.len() as u64, s.len() as u64,
&mut async_resource_name, &mut async_resource_name,
) )
}; })?;
check_status(status)?;
let initial_thread_count: u64 = 1; let initial_thread_count: u64 = 1;
let mut result = ptr::null_mut(); let mut result = ptr::null_mut();

View file

@ -0,0 +1,17 @@
import test from 'ava'
const bindings = require('../index.node')
test('should be able to add cleanup hook', (t) => {
t.notThrows(() => {
const ret = bindings.addCleanupHook()
t.is(typeof ret, 'object')
})
})
test('should be able to remove cleanup hook', (t) => {
t.notThrows(() => {
const ret = bindings.addCleanupHook()
bindings.removeCleanupHook(ret)
})
})

View file

@ -0,0 +1,27 @@
use napi::{
CallContext, CleanupEnvHook, ContextlessResult, Env, JsExternal, JsUndefined, Module, Result,
};
#[contextless_function]
fn add_cleanup_hook(mut env: Env) -> ContextlessResult<JsExternal> {
let hook = env.add_env_cleanup_hook((), |_| {
println!("cleanup hook executed");
})?;
env.create_external(hook).map(Some)
}
#[js_function(1)]
fn remove_cleanup_hook(ctx: CallContext) -> Result<JsUndefined> {
let hook_external = ctx.get::<JsExternal>(0)?;
let hook = *ctx
.env
.get_value_external::<CleanupEnvHook<()>>(&hook_external)?;
ctx.env.remove_env_cleanup_hook(hook)?;
ctx.env.get_undefined()
}
pub fn register_js(module: &mut Module) -> Result<()> {
module.create_named_method("addCleanupHook", add_cleanup_hook)?;
module.create_named_method("removeCleanupHook", remove_cleanup_hook)?;
Ok(())
}

View file

@ -7,6 +7,8 @@ extern crate serde_derive;
use napi::{Module, Result}; use napi::{Module, Result};
#[cfg(napi3)]
mod cleanup_env;
#[cfg(napi4)] #[cfg(napi4)]
mod libuv; mod libuv;
#[cfg(napi4)] #[cfg(napi4)]
@ -54,6 +56,8 @@ fn init(module: &mut Module) -> Result<()> {
env::register_js(module)?; env::register_js(module)?;
object::register_js(module)?; object::register_js(module)?;
global::register_js(module)?; global::register_js(module)?;
#[cfg(napi3)]
cleanup_env::register_js(module)?;
#[cfg(napi4)] #[cfg(napi4)]
napi4::register_js(module)?; napi4::register_js(module)?;
#[cfg(napi4)] #[cfg(napi4)]