Files
bun.sh/test/js/node/http2/node-http2-memory-leak.js
Jarred Sumner c0ba7e9e34 Unskip some tests (#22116)
### What does this PR do?

### How did you verify your code works?

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-08-27 06:39:11 -07:00

146 lines
3.9 KiB
JavaScript

import { heapStats } from "bun:jsc";
// This file is meant to be able to run in node and bun
const http2 = require("http2");
const { TLS_OPTIONS, nodeEchoServer } = require("./http2-helpers.cjs");
function getHeapStats() {
if (globalThis.Bun) {
const heapStats = require("bun:jsc").heapStats;
return heapStats().objectTypeCounts;
} else {
return {
objectTypeCounts: {
H2FrameParser: 0,
TLSSocket: 0,
},
};
}
}
const gc = globalThis.gc || globalThis.Bun?.gc || (() => {});
const sleep = dur => new Promise(resolve => setTimeout(resolve, dur));
const ASAN_MULTIPLIER = process.env.ASAN_OPTIONS ? 1 / 10 : 1;
// X iterations should be enough to detect a leak
const ITERATIONS = 20 * ASAN_MULTIPLIER;
// lets send a bigish payload
// const PAYLOAD = Buffer.from("BUN".repeat((1024 * 128) / 3));
const PAYLOAD = Buffer.alloc(1024 * 128, "b");
const MULTIPLEX = 50 * ASAN_MULTIPLIER;
async function main() {
let info;
let tls;
if (process.env.HTTP2_SERVER_INFO) {
info = JSON.parse(process.env.HTTP2_SERVER_INFO);
} else {
info = await nodeEchoServer();
console.log("Starting server", info.url);
}
if (process.env.HTTP2_SERVER_TLS) {
tls = JSON.parse(process.env.HTTP2_SERVER_TLS);
} else {
tls = TLS_OPTIONS;
}
async function runRequests(iterations) {
for (let j = 0; j < iterations; j++) {
let client = http2.connect(info.url, tls);
let promises = [];
for (let i = 0; i < MULTIPLEX; i++) {
const { promise, resolve, reject } = Promise.withResolvers();
const req = client.request({ ":path": "/post", ":method": "POST", "x-no-echo": "1" });
req.setEncoding("utf8");
req.on("response", (headers, flags) => {
req.on("data", chunk => {
if (JSON.parse(chunk) !== PAYLOAD.length) {
console.log("Got wrong data", chunk);
reject(new Error("wrong data"));
return;
}
resolve();
});
});
req.end(PAYLOAD, err => {
if (err) reject(err);
});
promises.push(promise);
}
try {
await Promise.all(promises);
} catch (e) {
console.log(e);
}
try {
client.close();
} catch (e) {
console.log(e);
}
client = null;
promises = null;
gc(true);
}
}
try {
const startStats = getHeapStats();
// warm up
await runRequests(ITERATIONS);
await sleep(10);
gc(true);
// take a baseline
const baseline = process.memoryUsage.rss();
// run requests
await runRequests(ITERATIONS);
gc(true);
await sleep(10);
// take an end snapshot
const end = process.memoryUsage.rss();
const delta = end - baseline;
const deltaMegaBytes = (delta / 1024 / 1024) | 0;
console.error("Memory delta", deltaMegaBytes, "MB");
// we executed 100 requests per iteration, memory usage should not go up by 10 MB
if (deltaMegaBytes > 20) {
console.error("Too many bodies leaked", deltaMegaBytes);
process.exit(1);
}
const endStats = getHeapStats();
info?.subprocess?.kill?.();
// check for H2FrameParser leaks
const pendingH2Parsers = (endStats.H2FrameParser || 0) - (startStats.H2FrameParser || 0);
if (pendingH2Parsers > 5) {
console.log("Too many pending H2FrameParsers", pendingH2Parsers);
process.exit(pendingH2Parsers);
}
// check for TLSSocket leaks
const pendingTLSSockets = (endStats.TLSSocket || 0) - (startStats.TLSSocket || 0);
if (pendingTLSSockets > 5) {
console.log("Too many pending TLSSockets", pendingTLSSockets);
process.exit(pendingTLSSockets);
}
process.exit(0);
} catch (err) {
console.log(err);
info?.subprocess?.kill?.();
process.exit(99); // 99 exception
}
}
main().then(
() => {},
err => {
console.error(err);
process.exit(99);
},
);