Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refigure gc tests #612

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
6 changes: 0 additions & 6 deletions test/package.json

This file was deleted.

81 changes: 0 additions & 81 deletions test/pnpm-lock.yaml

This file was deleted.

4 changes: 3 additions & 1 deletion test/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"extends": "../tsconfig.json",
"include": ["**/*.ts"]
"include": [
"**/*.ts"
]
}
40 changes: 40 additions & 0 deletions test/unit/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>
(options: {
execution?: "async" | "sync"
flavor?: "regular" | "last-resort"
type?: "major-snapshot" | "major" | "minor"
filename?: string
}): void | Promise<void>
}

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
}
64 changes: 20 additions & 44 deletions test/unit/socket-close-test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/// <reference lib="ESNext" />

/* 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")) {
Expand Down Expand Up @@ -107,75 +109,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")
rotu marked this conversation as resolved.
Show resolved Hide resolved
let weakRef: undefined | WeakRef<any>

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<any>
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())
})
})
})
Expand Down
Loading
Loading