diff --git a/crates/napi/src/bindgen_runtime/mod.rs b/crates/napi/src/bindgen_runtime/mod.rs index 63941f09..7b116eb6 100644 --- a/crates/napi/src/bindgen_runtime/mod.rs +++ b/crates/napi/src/bindgen_runtime/mod.rs @@ -30,6 +30,8 @@ pub unsafe extern "C" fn raw_finalize_unchecked( REFERENCE_MAP.with(|reference_map| reference_map.borrow_mut().remove(&finalize_data)) { let finalize_callbacks_rc = unsafe { Rc::from_raw(finalize_callbacks_ptr) }; + debug_assert!(Rc::strong_count(&finalize_callbacks_rc) == 1); + debug_assert!(Rc::weak_count(&finalize_callbacks_rc) == 0); let finalize = unsafe { Box::from_raw(finalize_callbacks_rc.get()) }; finalize(); let delete_reference_status = unsafe { sys::napi_delete_reference(env, ref_val) }; diff --git a/memory-testing/Cargo.toml b/memory-testing/Cargo.toml index 0ad743b1..26839a62 100644 --- a/memory-testing/Cargo.toml +++ b/memory-testing/Cargo.toml @@ -16,7 +16,9 @@ napi = { path = "../crates/napi", features = [ "latin1", "compat-mode", ] } -napi-derive = { path = "../crates/macro", features = ["compat-mode"] } +napi-derive = { path = "../crates/macro", default-features = false, features = [ + "compat-mode", +] } serde = "1" serde_bytes = "0.11" serde_derive = "1" diff --git a/memory-testing/index.mjs b/memory-testing/index.mjs index 65309afe..8f6c4fd1 100644 --- a/memory-testing/index.mjs +++ b/memory-testing/index.mjs @@ -1,4 +1,5 @@ import { createSuite } from './test-util.mjs' +await createSuite('reference') await createSuite('tokio-future') await createSuite('serde') diff --git a/memory-testing/package.json b/memory-testing/package.json index c278da30..a73d94a8 100644 --- a/memory-testing/package.json +++ b/memory-testing/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "scripts": { - "build": "node ../cli/scripts/index.js build --release" + "build": "node ../cli/scripts/index.js build --release --js false" }, "dependencies": { "colorette": "^2.0.16", diff --git a/memory-testing/reference.mjs b/memory-testing/reference.mjs new file mode 100644 index 00000000..046bfc4c --- /dev/null +++ b/memory-testing/reference.mjs @@ -0,0 +1,32 @@ +import { createRequire } from 'module' + +import { displayMemoryUsageFromNode } from './util.mjs' + +const initialMemoryUsage = process.memoryUsage() + +const require = createRequire(import.meta.url) + +const { MemoryHolder } = require(`./index.node`) + +const sleep = () => + new Promise((resolve) => { + setTimeout(() => { + resolve() + }, 1000) + }) + +let i = 1 +// eslint-disable-next-line no-constant-condition +while (true) { + const holder = new MemoryHolder(1024 * 1024) + for (const _ of Array.from({ length: 100 })) { + const child = holder.createReference() + child.count() + } + if (i % 100 === 0) { + displayMemoryUsageFromNode(initialMemoryUsage) + await sleep() + global.gc() + } + i++ +} diff --git a/memory-testing/serde.js b/memory-testing/serde.mjs similarity index 82% rename from memory-testing/serde.js rename to memory-testing/serde.mjs index 6325a66a..7128206d 100644 --- a/memory-testing/serde.js +++ b/memory-testing/serde.mjs @@ -1,7 +1,11 @@ -const displayMemoryUsageFromNode = require('./util') +import { createRequire } from 'module' + +import { displayMemoryUsageFromNode } from './util.mjs' const initialMemoryUsage = process.memoryUsage() +const require = createRequire(import.meta.url) + const api = require(`./index.node`) const data = { @@ -27,7 +31,7 @@ const data = { let i = 1 // eslint-disable-next-line no-constant-condition while (true) { - api.convertFromJS(data) + api.fromJs(data) if (i % 100000 === 0) { displayMemoryUsageFromNode(initialMemoryUsage) } diff --git a/memory-testing/src/lib.rs b/memory-testing/src/lib.rs index a16fdb21..e9ac5814 100644 --- a/memory-testing/src/lib.rs +++ b/memory-testing/src/lib.rs @@ -1,3 +1,5 @@ +use napi::{bindgen_prelude::*, Env}; + #[macro_use] extern crate napi_derive; #[macro_use] @@ -34,8 +36,8 @@ pub struct Room { name: String, } -#[js_function] -fn test_async(ctx: napi::CallContext) -> napi::Result { +#[napi] +pub fn test_async(env: Env) -> napi::Result { let data = serde_json::json!({ "findFirstBooking": { "id": "ckovh15xa104945sj64rdk8oas", @@ -57,26 +59,62 @@ fn test_async(ctx: napi::CallContext) -> napi::Result { "room": { "id": "ckovh15xa104955sj6r2tqaw1c", "name": "38683b87f2664" } } }); - - ctx.env.execute_tokio_future( + env.execute_tokio_future( async move { Ok(serde_json::to_string(&data).unwrap()) }, - |env, response| { - env.adjust_external_memory(response.len() as i64)?; - env.create_string_from_std(response) + |env, res| { + env.adjust_external_memory(res.len() as i64)?; + env.create_string_from_std(res) }, ) } -#[js_function(1)] -fn from_js(ctx: napi::CallContext) -> napi::Result { - let input_object = ctx.get::(0)?; - let a: Welcome = ctx.env.from_js_value(&input_object)?; - ctx.env.create_string_from_std(serde_json::to_string(&a)?) +#[napi] +pub fn from_js(env: Env, input_object: Object) -> napi::Result { + let a: Welcome = env.from_js_value(&input_object)?; + Ok(serde_json::to_string(&a)?) } -#[module_exports] -fn init(mut exports: napi::JsObject) -> napi::Result<()> { - exports.create_named_method("testAsync", test_async)?; - exports.create_named_method("convertFromJS", from_js)?; - Ok(()) +pub struct ChildHolder { + inner: &'static MemoryHolder, +} + +impl ChildHolder { + fn count(&self) -> usize { + self.inner.0.len() + } +} + +#[napi] +pub struct MemoryHolder(Vec); + +#[napi] +impl MemoryHolder { + #[napi(constructor)] + pub fn new(mut env: Env, len: u32) -> Result { + env.adjust_external_memory(len as i64)?; + Ok(Self(vec![42; len as usize])) + } + + #[napi] + pub fn create_reference( + &self, + env: Env, + holder_ref: Reference, + ) -> Result { + let child_holder = + holder_ref.share_with(env, |holder_ref| Ok(ChildHolder { inner: holder_ref }))?; + Ok(ChildReference(child_holder)) + } +} + +#[napi] +pub struct ChildReference(SharedReference); + +#[napi] + +impl ChildReference { + #[napi] + pub fn count(&self) -> u32 { + self.0.count() as u32 + } } diff --git a/memory-testing/test-util.mjs b/memory-testing/test-util.mjs index 089f81c4..8e120e5d 100644 --- a/memory-testing/test-util.mjs +++ b/memory-testing/test-util.mjs @@ -14,7 +14,7 @@ export async function createSuite(testFile, maxMemoryUsage) { const container = await client.createContainer({ Image: 'node:lts-slim', - Cmd: ['/bin/bash', '-c', `node memory-testing/${testFile}.js`], + Cmd: ['/bin/bash', '-c', `node --expose-gc memory-testing/${testFile}.mjs`], AttachStdout: true, AttachStderr: true, Tty: true, diff --git a/memory-testing/tokio-future.js b/memory-testing/tokio-future.mjs similarity index 72% rename from memory-testing/tokio-future.js rename to memory-testing/tokio-future.mjs index 2d125afd..e725d154 100644 --- a/memory-testing/tokio-future.js +++ b/memory-testing/tokio-future.mjs @@ -1,7 +1,11 @@ -const displayMemoryUsageFromNode = require('./util') +import { createRequire } from 'module' + +import { displayMemoryUsageFromNode } from './util.mjs' const initialMemoryUsage = process.memoryUsage() +const require = createRequire(import.meta.url) + const api = require(`./index.node`) async function main() { diff --git a/memory-testing/util.js b/memory-testing/util.mjs similarity index 73% rename from memory-testing/util.js rename to memory-testing/util.mjs index a9e1eac8..1cfb2607 100644 --- a/memory-testing/util.js +++ b/memory-testing/util.mjs @@ -1,8 +1,8 @@ -const { whiteBright, red, green, gray } = require('colorette') -const prettyBytes = require('pretty-bytes') -const { table } = require('table') +import { whiteBright, red, green, gray } from 'colorette' +import prettyBytes from 'pretty-bytes' +import { table } from 'table' -module.exports = function displayMemoryUsageFromNode(initialMemoryUsage) { +export function displayMemoryUsageFromNode(initialMemoryUsage) { const finalMemoryUsage = process.memoryUsage() const titles = Object.keys(initialMemoryUsage).map((k) => whiteBright(k)) const tableData = [titles]