Skip to content

Commit

Permalink
fix(cli/napi): handle finalizers (denoland#19168)
Browse files Browse the repository at this point in the history
  • Loading branch information
littledivy committed May 18, 2023
1 parent 695b5de commit c3f7e6e
Show file tree
Hide file tree
Showing 6 changed files with 315 additions and 48 deletions.
135 changes: 91 additions & 44 deletions cli/napi/js_native_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,18 +497,21 @@ fn napi_create_range_error(

#[napi_sym::napi_sym]
fn napi_create_external(
env: *mut Env,
env_ptr: *mut Env,
value: *mut c_void,
_finalize_cb: napi_finalize,
_finalize_hint: *mut c_void,
finalize_cb: napi_finalize,
finalize_hint: *mut c_void,
result: *mut napi_value,
) -> Result {
check_env!(env);
let env = unsafe { &mut *env };
let value: v8::Local<v8::Value> =
check_env!(env_ptr);
let env = unsafe { &mut *env_ptr };

let external: v8::Local<v8::Value> =
v8::External::new(&mut env.scope(), value).into();
// TODO: finalization
*result = value.into();

let value = weak_local(env_ptr, external, value, finalize_cb, finalize_hint);

*result = transmute(value);
Ok(())
}

Expand All @@ -517,6 +520,7 @@ pub type BackingStoreDeleterCallback = unsafe extern "C" fn(
byte_length: usize,
deleter_data: *mut c_void,
);

extern "C" {
fn v8__ArrayBuffer__NewBackingStore__with_data(
data: *mut c_void,
Expand All @@ -526,69 +530,104 @@ extern "C" {
) -> *mut BackingStore;
}

struct BufferFinalizer {
env: *mut Env,
finalize_cb: napi_finalize,
finalize_data: *mut c_void,
finalize_hint: *mut c_void,
}

impl BufferFinalizer {
fn into_raw(self) -> *mut BufferFinalizer {
Box::into_raw(Box::new(self))
}
}

impl Drop for BufferFinalizer {
fn drop(&mut self) {
unsafe {
(self.finalize_cb)(self.env as _, self.finalize_data, self.finalize_hint);
}
}
}

pub extern "C" fn backing_store_deleter_callback(
data: *mut c_void,
byte_length: usize,
_deleter_data: *mut c_void,
_byte_length: usize,
deleter_data: *mut c_void,
) {
let slice_ptr = ptr::slice_from_raw_parts_mut(data as *mut u8, byte_length);
let b = unsafe { Box::from_raw(slice_ptr) };
drop(b);
let mut finalizer =
unsafe { Box::from_raw(deleter_data as *mut BufferFinalizer) };

finalizer.finalize_data = data;
}

#[napi_sym::napi_sym]
fn napi_create_external_arraybuffer(
env: *mut Env,
env_ptr: *mut Env,
data: *mut c_void,
byte_length: usize,
_finalize_cb: napi_finalize,
finalize_cb: napi_finalize,
finalize_hint: *mut c_void,
result: *mut napi_value,
) -> Result {
check_env!(env);
let env = unsafe { &mut *env };
let _slice = std::slice::from_raw_parts(data as *mut u8, byte_length);
// TODO: finalization
check_env!(env_ptr);
let env = unsafe { &mut *env_ptr };

let finalizer = BufferFinalizer {
env: env_ptr,
finalize_data: ptr::null_mut(),
finalize_cb,
finalize_hint,
};

let store: UniqueRef<BackingStore> =
transmute(v8__ArrayBuffer__NewBackingStore__with_data(
data,
byte_length,
backing_store_deleter_callback,
finalize_hint,
finalizer.into_raw() as _,
));

let ab =
v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared());
let value: v8::Local<v8::Value> = ab.into();

*result = value.into();
Ok(())
}

#[napi_sym::napi_sym]
fn napi_create_external_buffer(
env: *mut Env,
byte_length: isize,
env_ptr: *mut Env,
byte_length: usize,
data: *mut c_void,
_finalize_cb: napi_finalize,
_finalize_hint: *mut c_void,
finalize_cb: napi_finalize,
finalize_hint: *mut c_void,
result: *mut napi_value,
) -> Result {
check_env!(env);
let env = unsafe { &mut *env };
let slice = if byte_length == -1 {
std::ffi::CStr::from_ptr(data as *const _).to_bytes()
} else {
std::slice::from_raw_parts(data as *mut u8, byte_length as usize)
check_env!(env_ptr);
let env = unsafe { &mut *env_ptr };

let finalizer = BufferFinalizer {
env: env_ptr,
finalize_data: ptr::null_mut(),
finalize_cb,
finalize_hint,
};
// TODO: make this not copy the slice
// TODO: finalization
let store = v8::ArrayBuffer::new_backing_store_from_boxed_slice(
slice.to_vec().into_boxed_slice(),
);

let store: UniqueRef<BackingStore> =
transmute(v8__ArrayBuffer__NewBackingStore__with_data(
data,
byte_length,
backing_store_deleter_callback,
finalizer.into_raw() as _,
));

let ab =
v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared());
let value =
v8::Uint8Array::new(&mut env.scope(), ab, 0, slice.len()).unwrap();
v8::Uint8Array::new(&mut env.scope(), ab, 0, byte_length).unwrap();
let value: v8::Local<v8::Value> = value.into();
*result = value.into();
Ok(())
Expand Down Expand Up @@ -1223,17 +1262,25 @@ fn napi_get_value_uint32(
Ok(())
}

// TODO
#[napi_sym::napi_sym]
fn napi_add_finalizer(
_env: *mut Env,
_js_object: napi_value,
_native_object: *const c_void,
_finalize_cb: napi_finalize,
_finalize_hint: *const c_void,
_result: *mut napi_ref,
env_ptr: *mut Env,
js_object: napi_value,
native_object: *mut c_void,
finalize_cb: napi_finalize,
finalize_hint: *mut c_void,
result: *mut napi_ref,
) -> Result {
log::info!("napi_add_finalizer is not yet supported.");
check_env!(env_ptr);

let value = napi_value_unchecked(js_object);
let value =
weak_local(env_ptr, value, native_object, finalize_cb, finalize_hint);

if !result.is_null() {
*result = transmute(value);
}

Ok(())
}

Expand Down
16 changes: 13 additions & 3 deletions cli/napi/threadsafe_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,21 @@ pub struct TsFn {
pub context: *mut c_void,
pub thread_counter: usize,
pub ref_counter: Arc<AtomicUsize>,
finalizer: Option<napi_finalize>,
finalizer_data: *mut c_void,
sender: mpsc::UnboundedSender<PendingNapiAsyncWork>,
tsfn_sender: mpsc::UnboundedSender<ThreadSafeFunctionStatus>,
}

impl Drop for TsFn {
fn drop(&mut self) {
let env = unsafe { self.env.as_mut().unwrap() };
env.remove_threadsafe_function_ref_counter(self.id)
env.remove_threadsafe_function_ref_counter(self.id);
if let Some(finalizer) = self.finalizer {
unsafe {
(finalizer)(self.env as _, self.finalizer_data, ptr::null_mut());
}
}
}
}

Expand Down Expand Up @@ -126,8 +133,8 @@ fn napi_create_threadsafe_function(
_async_resource_name: napi_value,
_max_queue_size: usize,
initial_thread_count: usize,
_thread_finialize_data: *mut c_void,
_thread_finalize_cb: napi_finalize,
thread_finialize_data: *mut c_void,
thread_finalize_cb: Option<napi_finalize>,
context: *mut c_void,
maybe_call_js_cb: Option<napi_threadsafe_function_call_js>,
result: *mut napi_threadsafe_function,
Expand All @@ -153,10 +160,13 @@ fn napi_create_threadsafe_function(
context,
thread_counter: initial_thread_count,
sender: env_ref.async_work_sender.clone(),
finalizer: thread_finalize_cb,
finalizer_data: thread_finialize_data,
tsfn_sender: env_ref.threadsafe_function_sender.clone(),
ref_counter: Arc::new(AtomicUsize::new(1)),
env,
};

env_ref
.add_threadsafe_function_ref_counter(tsfn.id, tsfn.ref_counter.clone());

Expand Down
44 changes: 44 additions & 0 deletions ext/napi/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,50 @@ pub trait NapiPermissions {
-> std::result::Result<(), AnyError>;
}

/// # Safety
///
/// This function is unsafe because it dereferences raw pointer Env.
/// - The caller must ensure that the pointer is valid.
/// - The caller must ensure that the pointer is not freed.
pub unsafe fn weak_local(
env_ptr: *mut Env,
value: v8::Local<v8::Value>,
data: *mut c_void,
finalize_cb: napi_finalize,
finalize_hint: *mut c_void,
) -> Option<v8::Local<v8::Value>> {
use std::cell::Cell;

let env = &mut *env_ptr;

let weak_ptr = Rc::new(Cell::new(None));
let scope = &mut env.scope();

let weak = v8::Weak::with_finalizer(
scope,
value,
Box::new({
let weak_ptr = weak_ptr.clone();
move |isolate| {
finalize_cb(env_ptr as _, data as _, finalize_hint as _);

// Self-deleting weak.
if let Some(weak_ptr) = weak_ptr.get() {
let weak: v8::Weak<v8::Value> =
unsafe { v8::Weak::from_raw(isolate, Some(weak_ptr)) };
drop(weak);
}
}
}),
);

let value = weak.to_local(scope);
let raw = weak.into_raw();
weak_ptr.set(raw);

value
}

#[op(v8)]
fn op_napi_open<NP, 'scope>(
scope: &mut v8::HandleScope<'scope>,
Expand Down
25 changes: 24 additions & 1 deletion test_napi/object_wrap_test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.

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

const objectWrap = loadTestLibrary();

Expand All @@ -16,3 +16,26 @@ Deno.test("napi object wrap new", function () {
assertEquals(obj.get_value(), 10);
assertEquals(objectWrap.NapiObject.factory(), 64);
});

Deno.test("napi bind finalizer", function () {
const obj = {};
objectWrap.test_bind_finalizer(obj);
});

Deno.test("napi external finalizer", function () {
let obj = objectWrap.test_external_finalizer();
assert(obj);
obj = null;
});

Deno.test("napi external buffer", function () {
let buf = objectWrap.test_external_buffer();
assertEquals(buf, new Uint8Array([1, 2, 3]));
buf = null;
});

Deno.test("napi external arraybuffer", function () {
let buf = objectWrap.test_external_arraybuffer();
assertEquals(new Uint8Array(buf), new Uint8Array([1, 2, 3]));
buf = null;
});
Loading

0 comments on commit c3f7e6e

Please sign in to comment.