Files
scriptc/packages/runtime/src/scr_alloc.test.ts
T
Malte Ubl 211b3f22b3 runtime: return an exiting worker thread's free pages to the system
A script thread that exits parks its allocator arena with every freed
block still resident, so the memory of a finished worker stays with the
process until another thread claims the arena. On Linux, a parking thread
that carved at least 16 MiB now hands back every page of its arena that
holds only free blocks (MADV_DONTNEED), relinks the remaining free blocks
in address order, and keeps the returned page runs as zeroed regions that
the next owner carves before the slice frontier. It also trims glibc
malloc. SCR_SA_STATS=1 reports what each park returned; SCR_SA_TRIM=0
turns it off.
2026-10-11 07:08:22 -07:00

52 lines
1.9 KiB
TypeScript

import { execFile } from "node:child_process";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { promisify } from "node:util";
import { expect, test } from "vitest";
const exec = promisify(execFile);
// The size-class allocator is compiled out under AddressSanitizer, so the
// UBSan-only build is the one that exercises it; the ASan build pins the
// system-allocator bypass the sanitizer lane relies on.
// Worker executables (-DSCR_WORKERS) give each script thread its own arena;
// the ThreadSanitizer build checks the cross-thread handoff for races.
test.each([
["size-class", ["-fsanitize=undefined"]],
["system", ["-fsanitize=address,undefined"]],
["size-class workers", ["-fsanitize=undefined", "-DSCR_WORKERS", "-pthread"]],
["size-class workers", ["-fsanitize=thread", "-DSCR_WORKERS", "-pthread"]],
])("small-object allocator contracts hold (%s, %j)", async (mode, sanitize) => {
const scratch = await mkdtemp(join(tmpdir(), "scriptc-alloc-"));
try {
const binary = join(scratch, "alloc");
await exec("clang", [
"-std=c11",
"-O1",
"-Wall",
"-Wextra",
// Linux runtime packs compile with _GNU_SOURCE, which exposes the
// madvise advice the page policy uses; build the checks the same way.
...(process.platform === "linux" ? ["-D_GNU_SOURCE"] : []),
...sanitize,
join(import.meta.dirname, "scr_alloc.test.c"),
join(import.meta.dirname, "scr_alloc.c"),
"-o",
binary,
]);
const result = await exec(binary, [], {
env: {
...process.env,
ASAN_OPTIONS: "halt_on_error=1",
TSAN_OPTIONS: "halt_on_error=1",
UBSAN_OPTIONS: "halt_on_error=1",
},
});
expect(result.stdout).toBe(`allocator checks passed: ${mode}\n`);
expect(result.stderr).toBe("");
} finally {
await rm(scratch, { recursive: true, force: true });
}
});