Skip to content

Commit

Permalink
fix(napi): support for env cleanup hooks (denoland#17324)
Browse files Browse the repository at this point in the history
This commit adds support for "napi_add_env_cleanup_hook" and
"napi_remove_env_cleanup_hook" function for Node-API.
  • Loading branch information
bartlomieju committed Jan 10, 2023
1 parent 71ea4ef commit 14ada3d
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 18 deletions.
43 changes: 34 additions & 9 deletions cli/napi/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,24 +52,49 @@ fn napi_fatal_exception(env: *mut Env, value: napi_value) -> Result {
);
}

// TODO: properly implement
#[napi_sym::napi_sym]
fn napi_add_env_cleanup_hook(
_env: *mut Env,
_hook: extern "C" fn(*const c_void),
_data: *const c_void,
env: *mut Env,
hook: extern "C" fn(*const c_void),
data: *const c_void,
) -> Result {
log::info!("napi_add_env_cleanup_hook is currently not supported");
let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?;

{
let mut env_cleanup_hooks = env.cleanup_hooks.borrow_mut();
if env_cleanup_hooks
.iter()
.any(|pair| pair.0 == hook && pair.1 == data)
{
panic!("Cleanup hook with this data already registered");
}
env_cleanup_hooks.push((hook, data));
}
Ok(())
}

#[napi_sym::napi_sym]
fn napi_remove_env_cleanup_hook(
_env: *mut Env,
_hook: extern "C" fn(*const c_void),
_data: *const c_void,
env: *mut Env,
hook: extern "C" fn(*const c_void),
data: *const c_void,
) -> Result {
log::info!("napi_remove_env_cleanup_hook is currently not supported");
let env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?;

{
let mut env_cleanup_hooks = env.cleanup_hooks.borrow_mut();
// Hooks are supposed to be removed in LIFO order
let maybe_index = env_cleanup_hooks
.iter()
.rposition(|&pair| pair.0 == hook && pair.1 == data);

if let Some(index) = maybe_index {
env_cleanup_hooks.remove(index);
} else {
panic!("Cleanup hook with this data not found");
}
}

Ok(())
}

Expand Down
24 changes: 23 additions & 1 deletion ext/napi/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use std::cell::RefCell;
use std::ffi::CString;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
use std::task::Poll;
use std::thread_local;

Expand Down Expand Up @@ -333,8 +334,20 @@ pub struct NapiState {
mpsc::UnboundedReceiver<ThreadSafeFunctionStatus>,
pub threadsafe_function_sender:
mpsc::UnboundedSender<ThreadSafeFunctionStatus>,
pub env_cleanup_hooks:
Rc<RefCell<Vec<(extern "C" fn(*const c_void), *const c_void)>>>,
}

impl Drop for NapiState {
fn drop(&mut self) {
let mut hooks = self.env_cleanup_hooks.borrow_mut();
// Hooks are supposed to be run in LIFO order
let hooks = hooks.drain(..).rev();
for (fn_ptr, data) in hooks {
(fn_ptr)(data);
}
}
}
#[repr(C)]
#[derive(Debug)]
/// Env that is shared between all contexts in same native module.
Expand Down Expand Up @@ -376,6 +389,8 @@ pub struct Env {
pub async_work_sender: mpsc::UnboundedSender<PendingNapiAsyncWork>,
pub threadsafe_function_sender:
mpsc::UnboundedSender<ThreadSafeFunctionStatus>,
pub cleanup_hooks:
Rc<RefCell<Vec<(extern "C" fn(*const c_void), *const c_void)>>>,
}

unsafe impl Send for Env {}
Expand All @@ -387,6 +402,9 @@ impl Env {
context: v8::Global<v8::Context>,
sender: mpsc::UnboundedSender<PendingNapiAsyncWork>,
threadsafe_function_sender: mpsc::UnboundedSender<ThreadSafeFunctionStatus>,
cleanup_hooks: Rc<
RefCell<Vec<(extern "C" fn(*const c_void), *const c_void)>>,
>,
) -> Self {
let sc = sender.clone();
ASYNC_WORK_SENDER.with(|s| {
Expand All @@ -404,6 +422,7 @@ impl Env {
open_handle_scopes: 0,
async_work_sender: sender,
threadsafe_function_sender,
cleanup_hooks,
}
}

Expand Down Expand Up @@ -507,6 +526,7 @@ pub fn init<P: NapiPermissions + 'static>(unstable: bool) -> Extension {
threadsafe_function_sender,
threadsafe_function_receiver,
active_threadsafe_functions: 0,
env_cleanup_hooks: Rc::new(RefCell::new(vec![])),
});
state.put(Unstable(unstable));
Ok(())
Expand Down Expand Up @@ -543,13 +563,14 @@ where
let permissions = op_state.borrow_mut::<NP>();
permissions.check(Some(&PathBuf::from(&path)))?;

let (async_work_sender, tsfn_sender, isolate_ptr) = {
let (async_work_sender, tsfn_sender, isolate_ptr, cleanup_hooks) = {
let napi_state = op_state.borrow::<NapiState>();
let isolate_ptr = op_state.borrow::<*mut v8::OwnedIsolate>();
(
napi_state.async_work_sender.clone(),
napi_state.threadsafe_function_sender.clone(),
*isolate_ptr,
napi_state.env_cleanup_hooks.clone(),
)
};

Expand All @@ -571,6 +592,7 @@ where
v8::Global::new(scope, ctx),
async_work_sender,
tsfn_sender,
cleanup_hooks,
);
env.shared = Box::into_raw(Box::new(env_shared));
let env_ptr = Box::into_raw(Box::new(env)) as _;
Expand Down
33 changes: 33 additions & 0 deletions test_napi/cleanup_hook_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.

import { assertEquals, loadTestLibrary } from "./common.js";

const properties = loadTestLibrary();

if (import.meta.main) {
properties.installCleanupHook();
console.log("installed cleanup hook");
} else {
Deno.test("napi cleanup hook", async () => {
const { stdout, stderr, code } = await new Deno.Command(Deno.execPath(), {
args: [
"run",
"--allow-read",
"--allow-run",
"--allow-ffi",
"--unstable",
import.meta.url,
],
}).output();

assertEquals(code, 0);
assertEquals(new TextDecoder().decode(stderr), "");

const stdoutText = new TextDecoder().decode(stdout);
const stdoutLines = stdoutText.split("\n");
assertEquals(stdoutLines.length, 4);
assertEquals(stdoutLines[0], "installed cleanup hook");
assertEquals(stdoutLines[1], "cleanup(18)");
assertEquals(stdoutLines[2], "cleanup(42)");
});
}
1 change: 0 additions & 1 deletion test_napi/src/arraybuffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

use napi_sys::Status::napi_ok;
use napi_sys::*;
use std::ptr;

extern "C" fn test_detached(
env: napi_env,
Expand Down
57 changes: 51 additions & 6 deletions test_napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
#![allow(clippy::all)]
#![allow(clippy::undocumented_unsafe_blocks)]

use napi_sys::Status::napi_ok;
use std::ffi::c_void;

use napi_sys::*;

pub mod array;
Expand All @@ -20,9 +23,9 @@ pub mod typedarray;
#[macro_export]
macro_rules! get_callback_info {
($env: expr, $callback_info: expr, $size: literal) => {{
let mut args = [ptr::null_mut(); $size];
let mut args = [std::ptr::null_mut(); $size];
let mut argc = $size;
let mut this = ptr::null_mut();
let mut this = std::ptr::null_mut();
unsafe {
assert!(
napi_get_cb_info(
Expand All @@ -31,7 +34,7 @@ macro_rules! get_callback_info {
&mut argc,
args.as_mut_ptr(),
&mut this,
ptr::null_mut(),
std::ptr::null_mut(),
) == napi_ok,
)
};
Expand All @@ -44,17 +47,58 @@ macro_rules! new_property {
($env: expr, $name: expr, $value: expr) => {
napi_property_descriptor {
utf8name: $name.as_ptr() as *const std::os::raw::c_char,
name: ptr::null_mut(),
name: std::ptr::null_mut(),
method: Some($value),
getter: None,
setter: None,
data: ptr::null_mut(),
data: std::ptr::null_mut(),
attributes: 0,
value: ptr::null_mut(),
value: std::ptr::null_mut(),
}
};
}

extern "C" fn cleanup(arg: *mut c_void) {
println!("cleanup({})", arg as i64);
}

static SECRET: i64 = 42;
static WRONG_SECRET: i64 = 17;
static THIRD_SECRET: i64 = 18;

extern "C" fn install_cleanup_hook(
env: napi_env,
info: napi_callback_info,
) -> napi_value {
let (_args, argc, _) = get_callback_info!(env, info, 1);
assert_eq!(argc, 0);

unsafe {
napi_add_env_cleanup_hook(env, Some(cleanup), WRONG_SECRET as *mut c_void);
napi_add_env_cleanup_hook(env, Some(cleanup), SECRET as *mut c_void);
napi_add_env_cleanup_hook(env, Some(cleanup), THIRD_SECRET as *mut c_void);
napi_remove_env_cleanup_hook(
env,
Some(cleanup),
WRONG_SECRET as *mut c_void,
);
}

std::ptr::null_mut()
}

pub fn init_cleanup_hook(env: napi_env, exports: napi_value) {
let properties = &[new_property!(
env,
"installCleanupHook\0",
install_cleanup_hook
)];

unsafe {
napi_define_properties(env, exports, properties.len(), properties.as_ptr())
};
}

#[no_mangle]
unsafe extern "C" fn napi_register_module_v1(
env: napi_env,
Expand All @@ -77,6 +121,7 @@ unsafe extern "C" fn napi_register_module_v1(
object_wrap::init(env, exports);
callback::init(env, exports);
r#async::init(env, exports);
init_cleanup_hook(env, exports);

exports
}
1 change: 0 additions & 1 deletion test_napi/src/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
use napi_sys::Status::napi_ok;
use napi_sys::ValueType::napi_string;
use napi_sys::*;
use std::ptr;

extern "C" fn test_utf8(env: napi_env, info: napi_callback_info) -> napi_value {
let (args, argc, _) = crate::get_callback_info!(env, info, 1);
Expand Down
2 changes: 2 additions & 0 deletions test_napi/tests/napi_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ fn napi_tests() {
.arg("--allow-read")
.arg("--allow-env")
.arg("--allow-ffi")
.arg("--allow-run")
.arg("--unstable")
.spawn()
.unwrap()
.wait_with_output()
.unwrap();
let stdout = std::str::from_utf8(&output.stdout).unwrap();
let stderr = std::str::from_utf8(&output.stderr).unwrap();

if !output.status.success() {
println!("stdout {}", stdout);
println!("stderr {}", stderr);
Expand Down

0 comments on commit 14ada3d

Please sign in to comment.