From 5b45ef1b1f6f65229d6714ab2c3d1dd70ea186ed Mon Sep 17 00:00:00 2001 From: Dan Rose Date: Tue, 11 Jun 2024 10:31:57 -0500 Subject: [PATCH 1/5] test: use WeakRef instead of weak-napi for GC tests --- CONTRIBUTING.md | 10 --- package.json | 7 +- test/package.json | 6 -- test/pnpm-lock.yaml | 81 ----------------------- test/tsconfig.json | 4 +- test/unit/helpers.ts | 40 ++++++++++++ test/unit/socket-close-test.ts | 65 ++++++------------- test/unit/socket-send-receive-test.ts | 92 ++++++++++----------------- tsconfig.json | 1 + 9 files changed, 101 insertions(+), 205 deletions(-) delete mode 100644 test/package.json delete mode 100644 test/pnpm-lock.yaml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e0d38f4..5d7bfc51 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,13 +38,3 @@ tool similar to Cmake. GYP was originally created to generate native IDE project files (Visual Studio, Xcode) for building Chromium. The `.gyp` file is structured as a Python dictionary. - -## Weak-napi - -https://www.npmjs.com/package/weak-napi On certain rarer occasions, you run into -the need to be notified when a JavaScript object is going to be garbage -collected. This feature is exposed to V8's C++ API, but not to JavaScript. - -That's where weak-napi comes in! This module exports the JS engine's GC tracking -functionality to JavaScript. This allows you to create weak references, and -optionally attach a callback function to any arbitrary JS object. diff --git a/package.json b/package.json index c97df50b..8450ba26 100644 --- a/package.json +++ b/package.json @@ -95,10 +95,9 @@ "build.native.debug": "node-gyp configure --debug && node-gyp configure --debug -- -f compile_commands_json && cross-env CMAKE_BUILD_TYPE=Debug node-gyp build --debug", "build": "run-s build.js build.native", "build.debug": "run-s build.js build.native.debug", - "test.deps": "cd test && pnpm install && cd ..", - "test": "run-s clean.temp test.deps build && mocha", - "test.skip_gc_tests": "run-s clean.temp test.deps build.debug && cross-env SKIP_GC_TESTS=true mocha", - "test.electron.main": "run-s clean.temp test.deps build && electron-mocha", + "test": "run-s clean.temp build && mocha", + "test.skip_gc_tests": "run-s clean.temp build.debug && cross-env SKIP_GC_TESTS=true mocha", + "test.electron.main": "run-s clean.temp build && electron-mocha", "format": "prettier --write .", "test.electron.renderer": "run-s build && electron-mocha --renderer", "lint.clang-format": "clang-format -i -style=file ./src/*.cc ./src/*.h ./src/util/*.h", diff --git a/test/package.json b/test/package.json deleted file mode 100644 index 755aa163..00000000 --- a/test/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "devDependencies": { - "@types/weak-napi": "^2.0.3", - "weak-napi": "https://github.com/aminya/weak-napi#ea97678fc4c9304eea7f3fb1ddc88eaf59cccc6f" - } -} diff --git a/test/pnpm-lock.yaml b/test/pnpm-lock.yaml deleted file mode 100644 index 78ff61a8..00000000 --- a/test/pnpm-lock.yaml +++ /dev/null @@ -1,81 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - devDependencies: - '@types/weak-napi': - specifier: ^2.0.3 - version: 2.0.3 - weak-napi: - specifier: https://github.com/aminya/weak-napi#ea97678fc4c9304eea7f3fb1ddc88eaf59cccc6f - version: https://codeload.github.com/aminya/weak-napi/tar.gz/ea97678fc4c9304eea7f3fb1ddc88eaf59cccc6f - -packages: - - '@types/node@20.14.2': - resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==} - - '@types/weak-napi@2.0.3': - resolution: {integrity: sha512-hNh8wxRTaQC8gLT6BKkG5Kokwp4hEyWw4RshGTLwa1K4S1o6kX7G0bJ85kKr+2fK3fuF8HZuz1+Dy8YtICZDqg==} - - get-symbol-from-current-process-h@1.0.2: - resolution: {integrity: sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw==} - - get-uv-event-loop-napi-h@1.0.6: - resolution: {integrity: sha512-t5c9VNR84nRoF+eLiz6wFrEp1SE2Acg0wS+Ysa2zF0eROes+LzOfuTaVHxGy8AbS8rq7FHEJzjnCZo1BupwdJg==} - - node-addon-api@8.0.0: - resolution: {integrity: sha512-ipO7rsHEBqa9STO5C5T10fj732ml+5kLN1cAG8/jdHd56ldQeGj3Q7+scUS+VHK/qy1zLEwC4wMK5+yM0btPvw==} - engines: {node: ^18 || ^20 || >= 21} - - node-gyp-build@4.8.1: - resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==} - hasBin: true - - setimmediate-napi@1.0.6: - resolution: {integrity: sha512-sdNXN15Av1jPXuSal4Mk4tEAKn0+8lfF9Z50/negaQMrAIO9c1qM0eiCh8fT6gctp0RiCObk+6/Xfn5RMGdZoA==} - - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - - weak-napi@https://codeload.github.com/aminya/weak-napi/tar.gz/ea97678fc4c9304eea7f3fb1ddc88eaf59cccc6f: - resolution: {tarball: https://codeload.github.com/aminya/weak-napi/tar.gz/ea97678fc4c9304eea7f3fb1ddc88eaf59cccc6f} - version: 2.0.2 - -snapshots: - - '@types/node@20.14.2': - dependencies: - undici-types: 5.26.5 - - '@types/weak-napi@2.0.3': - dependencies: - '@types/node': 20.14.2 - - get-symbol-from-current-process-h@1.0.2: {} - - get-uv-event-loop-napi-h@1.0.6: - dependencies: - get-symbol-from-current-process-h: 1.0.2 - - node-addon-api@8.0.0: {} - - node-gyp-build@4.8.1: {} - - setimmediate-napi@1.0.6: - dependencies: - get-symbol-from-current-process-h: 1.0.2 - get-uv-event-loop-napi-h: 1.0.6 - - undici-types@5.26.5: {} - - weak-napi@https://codeload.github.com/aminya/weak-napi/tar.gz/ea97678fc4c9304eea7f3fb1ddc88eaf59cccc6f: - dependencies: - node-addon-api: 8.0.0 - node-gyp-build: 4.8.1 - setimmediate-napi: 1.0.6 diff --git a/test/tsconfig.json b/test/tsconfig.json index e516b7fa..105efec3 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -1,4 +1,6 @@ { "extends": "../tsconfig.json", - "include": ["**/*.ts"] + "include": [ + "**/*.ts" + ] } diff --git a/test/unit/helpers.ts b/test/unit/helpers.ts index 2b15e99d..c640fc32 100644 --- a/test/unit/helpers.ts +++ b/test/unit/helpers.ts @@ -235,3 +235,43 @@ export async function captureEventsUntil( return events } + +// REAL typings for global.gc per +// https://github.com/nodejs/node/blob/v20.0.0/deps/v8/src/extensions/gc-extension.cc +interface GCFunction { + (options: { + execution?: "sync" + flavor?: "regular" | "last-resort" + type?: "major-snapshot" | "major" | "minor" + filename?: string + }): void + (options: { + execution?: "async" + flavor?: "regular" | "last-resort" + type?: "major-snapshot" | "major" | "minor" + filename?: string + }): Promise + (options: { + execution?: "async" | "sync" + flavor?: "regular" | "last-resort" + type?: "major-snapshot" | "major" | "minor" + filename?: string + }): void | Promise +} + +export function getGcOrSkipTest(test: Mocha.Context) { + if (process.env.SKIP_GC_TESTS === "true") { + test.skip() + } + + const gc = globalThis.gc as undefined | GCFunction + if (typeof gc !== "function") { + throw new Error( + "Garbage collection is not exposed. It may be enabled by the node --expose-gc flag. To skip GC tests, set the environment variable `SKIP_GC_TESTS`", + ) + } + // https://github.com/nodejs/node/blob/v20.0.0/deps/v8/src/extensions/gc-extension.h + // per docs, we we're using use case 2 (Test that certain objects indeed are reclaimed) + const asyncMajorGc = () => gc({type: "major", execution: "async"}) + return asyncMajorGc +} diff --git a/test/unit/socket-close-test.ts b/test/unit/socket-close-test.ts index fd76ccf8..e0d83def 100644 --- a/test/unit/socket-close-test.ts +++ b/test/unit/socket-close-test.ts @@ -1,8 +1,9 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ +/// + import * as zmq from "../../src" import {assert} from "chai" -import {testProtos, uniqAddress} from "./helpers" +import {testProtos, uniqAddress, getGcOrSkipTest} from "./helpers" import {isFullError} from "../../src/errors" for (const proto of testProtos("tcp", "ipc", "inproc")) { @@ -107,75 +108,49 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { }) it("should release reference to context", async function () { - if (process.env.SKIP_GC_TESTS === "true") { - this.skip() - } - if (global.gc === undefined) { - console.warn("gc is not exposed by the runtime") - this.skip() - } - + const gc = getGcOrSkipTest(this) this.slow(200) - const weak = require("weak-napi") as typeof import("weak-napi") + let weakRef: undefined | WeakRef - let released = false const task = async () => { - let context: zmq.Context | undefined = new zmq.Context() + const context: zmq.Context | undefined = new zmq.Context() const socket = new zmq.Dealer({context, linger: 0}) + weakRef = new WeakRef(context) - weak(context, () => { - released = true - }) - context = undefined - - global.gc!() socket.connect(await uniqAddress(proto)) await socket.send(Buffer.from("foo")) socket.close() } await task() - global.gc() - await new Promise(resolve => { - setTimeout(resolve, 5) - }) - assert.equal(released, true) + await gc() + + assert.isDefined(weakRef) + assert.isUndefined(weakRef!.deref()) }) }) describe("in gc finalizer", function () { it("should release reference to context", async function () { - if (process.env.SKIP_GC_TESTS === "true") { - this.skip() - } - if (global.gc === undefined) { - console.warn("gc is not exposed by the runtime") + const gc = getGcOrSkipTest(this) + if (process.env.SKIP_GC_FINALIZER_TESTS) { this.skip() } this.slow(200) - const weak = require("weak-napi") as typeof import("weak-napi") - - let released = false + let weakRef: undefined | WeakRef const task = async () => { - let context: zmq.Context | undefined = new zmq.Context() - + const context: zmq.Context | undefined = new zmq.Context() const _dealer = new zmq.Dealer({context, linger: 0}) - - weak(context, () => { - released = true - }) - context = undefined - global.gc!() + weakRef = new WeakRef(context) } await task() - global.gc() - await new Promise(resolve => { - setTimeout(resolve, 5) - }) - assert.equal(released, true) + await gc() + + assert.isDefined(weakRef) + assert.isUndefined(weakRef!.deref()) }) }) }) diff --git a/test/unit/socket-send-receive-test.ts b/test/unit/socket-send-receive-test.ts index 45e4bbb2..d3d95241 100644 --- a/test/unit/socket-send-receive-test.ts +++ b/test/unit/socket-send-receive-test.ts @@ -1,8 +1,9 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ +/// + import * as zmq from "../../src" import {assert} from "chai" -import {testProtos, uniqAddress} from "./helpers" +import {testProtos, uniqAddress, getGcOrSkipTest} from "./helpers" import {isFullError} from "../../src/errors" for (const proto of testProtos("tcp", "ipc", "inproc")) { @@ -91,51 +92,36 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { }) it("should copy and release small buffers", async function () { - if (process.env.SKIP_GC_TESTS) { - this.skip() - } - const weak = require("weak-napi") - - let released = false + const gc = getGcOrSkipTest(this) + let weakRef: undefined | WeakRef sockA.connect(await uniqAddress(proto)) const send = async (size: number) => { const msg = Buffer.alloc(size) - weak(msg, () => { - released = true - }) + weakRef = new WeakRef(msg) await sockA.send(msg) } await send(16) - global.gc?.() - await new Promise(resolve => { - setTimeout(resolve, 5) - }) - assert.equal(released, true) + await gc() + assert.isDefined(weakRef) + assert.isUndefined(weakRef!.deref()) }) it("should retain large buffers", async function () { - if (process.env.SKIP_GC_TESTS) { - this.skip() - } - const weak = require("weak-napi") + const gc = getGcOrSkipTest(this) + let weakRef: undefined | WeakRef - let released = false sockA.connect(await uniqAddress(proto)) const send = async (size: number) => { const msg = Buffer.alloc(size) - weak(msg, () => { - released = true - }) + weakRef = new WeakRef(msg) await sockA.send(msg) } await send(1025) - global.gc?.() - await new Promise(resolve => { - setTimeout(resolve, 5) - }) - assert.equal(released, false) + await gc() + assert.isDefined(weakRef) + assert.isDefined(weakRef.deref()) }) }) @@ -342,20 +328,16 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { }) it("should release buffers", async function () { - if (process.env.SKIP_GC_TESTS) { - this.skip() - } - const weak = require("weak-napi") + const gc = await getGcOrSkipTest(this) + + const weakRefs: WeakRef[] = [] const n = 10 - let released = 0 const send = async (size: number) => { for (let i = 0; i < n; i++) { const msg = Buffer.alloc(size) - weak(msg, () => { - released++ - }) + weakRefs.push(new WeakRef(msg)) await sockA.send(msg) } } @@ -363,9 +345,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { const receive = async () => { for (let i = 0; i < n; i++) { const msg = await sockB.receive() - weak(msg, () => { - released++ - }) + weakRefs.push(new WeakRef(msg)) } } @@ -373,46 +353,41 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { /* Repeated GC to allow inproc messages from being collected. */ for (let i = 0; i < 5; i++) { - global.gc?.() + await gc() + await new Promise(resolve => { setTimeout(resolve, 2) }) } - assert.equal(released, n * 2) + assert.equal(weakRefs.length, n * 2) + const unreleased = weakRefs.filter(x => x.deref() !== undefined) + assert.isEmpty(unreleased) }) it("should release buffers after echo", async function () { - if (process.env.SKIP_GC_TESTS) { - this.skip() - } - const weak = require("weak-napi") + const gc = getGcOrSkipTest(this) + + const weakRefs: WeakRef[] = [] const n = 10 - let released = 0 const echo = async () => { for (let i = 0; i < n; i++) { const [msg] = await sockB.receive() await sockB.send(msg) - weak(msg, () => { - released++ - }) + weakRefs.push(new WeakRef(msg)) } } const send = async (size: number) => { for (let i = 0; i < n; i++) { const msg = Buffer.alloc(size) - weak(msg, () => { - released++ - }) + weakRefs.push(new WeakRef(msg)) await sockA.send(msg) const [rep] = await sockA.receive() - weak(rep, () => { - released++ - }) + weakRefs.push(new WeakRef(rep)) } sockA.close() @@ -423,13 +398,14 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { /* Repeated GC to allow inproc messages from being collected. */ for (let i = 0; i < 5; i++) { - global.gc?.() await new Promise(resolve => { setTimeout(resolve, 2) }) + await gc() } - assert.equal(released, n * 3) + assert.lengthOf(weakRefs, n * 3) + assert.isEmpty(weakRefs.filter(r => r.deref() !== undefined)) }) if (proto === "inproc") { diff --git a/tsconfig.json b/tsconfig.json index 02c2001c..163f04fe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,5 @@ { + "root": true, "compilerOptions": { "allowJs": false, "target": "es2020", From 51ded1f69b695b44e9a8dd73e09d77e3652fc02e Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Mon, 17 Jun 2024 21:31:12 -0700 Subject: [PATCH 2/5] test: add WeakRef TypeScript lib --- test/unit/socket-close-test.ts | 2 -- test/unit/socket-send-receive-test.ts | 2 -- tsconfig.json | 11 +++++++++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/test/unit/socket-close-test.ts b/test/unit/socket-close-test.ts index e0d83def..b23412e5 100644 --- a/test/unit/socket-close-test.ts +++ b/test/unit/socket-close-test.ts @@ -1,5 +1,3 @@ -/// - import * as zmq from "../../src" import {assert} from "chai" diff --git a/test/unit/socket-send-receive-test.ts b/test/unit/socket-send-receive-test.ts index d3d95241..853f8fc1 100644 --- a/test/unit/socket-send-receive-test.ts +++ b/test/unit/socket-send-receive-test.ts @@ -1,5 +1,3 @@ -/// - import * as zmq from "../../src" import {assert} from "chai" diff --git a/tsconfig.json b/tsconfig.json index 163f04fe..ca487d00 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,10 @@ "target": "es2020", "declaration": true, "module": "commonjs", - "types": ["node", "mocha"], + "types": [ + "node", + "mocha" + ], "strictPropertyInitialization": false, // TODO "strict": true, "strictNullChecks": true, @@ -18,6 +21,10 @@ "incremental": true, "sourceMap": true, "esModuleInterop": true, - "lib": ["ES2020", "dom"] + "lib": [ + "ES2020", + // only used in tests + "ES2021.WeakRef" + ] } } From 76e1a0259180dd79a1263570f355d50869a9086f Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Mon, 17 Jun 2024 21:45:12 -0700 Subject: [PATCH 3/5] test: improve the type of the weak refs --- .vscode/settings.json | 5 ++++- test/unit/helpers.ts | 4 ++-- test/unit/socket-close-test.ts | 9 +++++---- test/unit/socket-send-receive-test.ts | 5 +++-- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5a360237..90a5849a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,7 @@ { "mochaExplorer.parallel": true, - "mochaExplorer.globImplementation": "vscode" + "mochaExplorer.globImplementation": "vscode", + "mochaExplorer.nodeArgv": [ + "--expose-gc" + ] } diff --git a/test/unit/helpers.ts b/test/unit/helpers.ts index c640fc32..bfe5207a 100644 --- a/test/unit/helpers.ts +++ b/test/unit/helpers.ts @@ -264,10 +264,10 @@ export function getGcOrSkipTest(test: Mocha.Context) { test.skip() } - const gc = globalThis.gc as undefined | GCFunction + const gc = global.gc as undefined | GCFunction if (typeof gc !== "function") { throw new Error( - "Garbage collection is not exposed. It may be enabled by the node --expose-gc flag. To skip GC tests, set the environment variable `SKIP_GC_TESTS`", + "Garbage collection is not exposed. It may be enabled by the node --expose-gc flag or v8-expose-gc flag in Mocha. To skip GC tests, set the environment variable `SKIP_GC_TESTS`", ) } // https://github.com/nodejs/node/blob/v20.0.0/deps/v8/src/extensions/gc-extension.h diff --git a/test/unit/socket-close-test.ts b/test/unit/socket-close-test.ts index b23412e5..a8366fbe 100644 --- a/test/unit/socket-close-test.ts +++ b/test/unit/socket-close-test.ts @@ -100,6 +100,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { await promise assert.ok(false) } catch (err) { + console.log(err) /* Ignore */ } assert.equal(sock.closed, true) @@ -109,7 +110,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { const gc = getGcOrSkipTest(this) this.slow(200) - let weakRef: undefined | WeakRef + let weakRef: undefined | WeakRef const task = async () => { const context: zmq.Context | undefined = new zmq.Context() @@ -125,7 +126,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { await gc() assert.isDefined(weakRef) - assert.isUndefined(weakRef!.deref()) + assert.isUndefined(weakRef.deref()) }) }) @@ -137,7 +138,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { } this.slow(200) - let weakRef: undefined | WeakRef + let weakRef: undefined | WeakRef const task = async () => { const context: zmq.Context | undefined = new zmq.Context() const _dealer = new zmq.Dealer({context, linger: 0}) @@ -148,7 +149,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { await gc() assert.isDefined(weakRef) - assert.isUndefined(weakRef!.deref()) + assert.isUndefined(weakRef.deref()) }) }) }) diff --git a/test/unit/socket-send-receive-test.ts b/test/unit/socket-send-receive-test.ts index 853f8fc1..bdd22871 100644 --- a/test/unit/socket-send-receive-test.ts +++ b/test/unit/socket-send-receive-test.ts @@ -91,7 +91,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { it("should copy and release small buffers", async function () { const gc = getGcOrSkipTest(this) - let weakRef: undefined | WeakRef + let weakRef: undefined | WeakRef sockA.connect(await uniqAddress(proto)) const send = async (size: number) => { const msg = Buffer.alloc(size) @@ -107,7 +107,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { it("should retain large buffers", async function () { const gc = getGcOrSkipTest(this) - let weakRef: undefined | WeakRef + let weakRef: undefined | WeakRef sockA.connect(await uniqAddress(proto)) const send = async (size: number) => { @@ -232,6 +232,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { it("should deliver messages coercible to string", async function () { const messages = [ null, + // eslint-disable-next-line no-empty-function function () {}, 16.19, true, From f2536861afaff45688faf53c46878864754ab21c Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Mon, 17 Jun 2024 23:29:26 -0700 Subject: [PATCH 4/5] test: disable gc finalizer test due to global sharing of the context --- test/unit/socket-close-test.ts | 46 ++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/test/unit/socket-close-test.ts b/test/unit/socket-close-test.ts index a8366fbe..83f145b5 100644 --- a/test/unit/socket-close-test.ts +++ b/test/unit/socket-close-test.ts @@ -130,27 +130,29 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { }) }) - describe("in gc finalizer", function () { - it("should release reference to context", async function () { - const gc = getGcOrSkipTest(this) - if (process.env.SKIP_GC_FINALIZER_TESTS) { - this.skip() - } - this.slow(200) - - let weakRef: undefined | WeakRef - const task = async () => { - const context: zmq.Context | undefined = new zmq.Context() - const _dealer = new zmq.Dealer({context, linger: 0}) - weakRef = new WeakRef(context) - } - - await task() - await gc() - - assert.isDefined(weakRef) - assert.isUndefined(weakRef.deref()) - }) - }) + // // Because context is shared in the global module, it is not GC'd until the end of the process + // // Unless dealer is closed explicitly. + // describe("in gc finalizer", function () { + // it("should release reference to context", async function () { + // const gc = getGcOrSkipTest(this) + // if (process.env.SKIP_GC_FINALIZER_TESTS) { + // this.skip() + // } + // this.slow(200) + + // let weakRef: undefined | WeakRef + // const task = async () => { + // const context: zmq.Context | undefined = new zmq.Context() + // const _dealer = new zmq.Dealer({context, linger: 0}) + // weakRef = new WeakRef(context) + // } + + // await task() + // await gc() + + // assert.isDefined(weakRef) + // assert.isUndefined(weakRef.deref()) + // }) + // }) }) } From d1cae972766bf853e6dd26ddc2b3ad6160317617 Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Mon, 17 Jun 2024 23:31:26 -0700 Subject: [PATCH 5/5] chore: add debugging configuration for VsCode --- .vscode/extensions.json | 3 ++- .vscode/launch.json | 46 +++++++++++++++++++++++++++++++++++++++++ .vscode/settings.json | 3 ++- .vscode/tasks.json | 10 +++++++++ package.json | 1 + test/debug.ts | 26 +++++++++++++++++++++++ test/unit/helpers.ts | 4 ++-- 7 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 test/debug.ts diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 5ae661ce..ea2764d4 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -5,6 +5,7 @@ "llvm-vs-code-extensions.vscode-clangd", "xadillax.gyp", "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode" + "esbenp.prettier-vscode", + "vadimcn.vscode-lldb" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..ef6e22bb --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,46 @@ +{ + "configurations": [ + { + "name": "JS-Attach", + "type": "node", + "request": "attach", + "port": 9229, + "continueOnAttach": true, + "autoAttachChildProcesses": true, + "resolveSourceMapLocations": [ + "!**/node_modules/**", + "!**/.vscode/extensions/hbenl.vscode-mocha-test-adapter-*/**" + ], + "skipFiles": [ + "/**" + ], + }, + { + "type": "lldb", + "request": "launch", + "name": "Native-Launch", + "preLaunchTask": "clean_build_debug", + "program": "node", + "suppressMultipleSessionWarning": true, + "sourceLanguages": [ + "cpp" + ], + "args": [ + "--inspect-brk=9229", + "--expose-gc", + "-r", + "ts-node/register", + "${workspaceFolder}/test/debug.ts" + ], + } + ], + "compounds": [ + { + "name": "Node-Launch", + "configurations": [ + "Native-Launch", + "JS-Attach", + ] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 90a5849a..989bd09a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,6 @@ "mochaExplorer.globImplementation": "vscode", "mochaExplorer.nodeArgv": [ "--expose-gc" - ] + ], + "mochaExplorer.debuggerConfig": "JS-Attach" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..779289e8 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,10 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "clean_build_debug", + "type": "shell", + "command": "pnpm clean.release && pnpm build.debug", + } + ] +} diff --git a/package.json b/package.json index 8450ba26..dbc91da7 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "scripts": { "install": "(shx test -f ./script/build.js || run-s build.js) && cross-env npm_config_build_from_source=true aminya-node-gyp-build", "clean": "shx rm -rf ./build ./lib/ ./prebuilds ./script/*.js ./script/*.js.map ./script/*.d.ts ./script/*.tsbuildinfo", + "clean.release": "shx rm -rf ./build/Release", "clean.temp": "shx rm -rf ./tmp && shx mkdir -p ./tmp", "build.library": "tsc -p ./src/tsconfig.json", "build.script": "tsc -p ./script/tsconfig.json && tsc -p ./script/tsconfig.esm.json", diff --git a/test/debug.ts b/test/debug.ts new file mode 100644 index 00000000..a14b1a83 --- /dev/null +++ b/test/debug.ts @@ -0,0 +1,26 @@ +import * as zmq from "../src" + +import {getGcOrSkipTest} from "./unit/helpers" + +async function main() { + const gc = getGcOrSkipTest() + + let weakRef: undefined | WeakRef + const task = async () => { + const context: zmq.Context | undefined = new zmq.Context() + const dealer = new zmq.Dealer({context, linger: 0}) + weakRef = new WeakRef(context) + + // dealer.close() + } + + await task() + await gc() + + console.log(weakRef?.deref()) +} + +main().catch(err => { + console.error(err) + process.exit(1) +}) diff --git a/test/unit/helpers.ts b/test/unit/helpers.ts index bfe5207a..5186f8de 100644 --- a/test/unit/helpers.ts +++ b/test/unit/helpers.ts @@ -259,9 +259,9 @@ interface GCFunction { }): void | Promise } -export function getGcOrSkipTest(test: Mocha.Context) { +export function getGcOrSkipTest(test?: Mocha.Context) { if (process.env.SKIP_GC_TESTS === "true") { - test.skip() + test?.skip() } const gc = global.gc as undefined | GCFunction