// @effect-diagnostics nodeBuiltinImport:offimport * as NodeChildProcess from "node:child_process";import * as NodeFS from "node:fs";import * as NodePath from "node:path";import * as NodeURL from "node:url"; import { describe, expect, it } from "@effect/vitest";import { HostProcessPlatform } from "@t3tools/shared/hostProcess";import * as Clock from "effect/Clock";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as Scope from "effect/Scope"; import { capturePosixOwnershipLedger, isSafeCgroupPath, makeLinuxCgroupController, makePosixProcessTreeController, observePosixOwnershipLedger, observePosixOwnershipLedgerContinuously, parseCgroup2Mount, parseCgroup2Mounts, parseUnifiedCgroupPath, posixProcessIsZombie, resolveLinuxCgroupTargetCommand, signalLinuxCgroupRootTerm, sweepStaleLinuxCgroupSiblings, terminateLinuxCgroupLease, terminatePosixOwnedProcessTree, wrapCommandForLinuxCgroup, type AcpOwnedPosixProcess, type AcpLinuxCgroupLease, type AcpPosixOwnershipRoot, type AcpPosixProcessIdentity, type AcpPosixProcessTreeController,} from "./AcpSessionRuntime.ts"; const threadSpawnHelperSource = NodeURL.fileURLToPath( new URL("../../../scripts/acp-thread-spawn-helper.c", import.meta.url),); const identity = ( pid: number, ppid: number, pgid: number, sid: number, startTime = String(pid), state = "S",): AcpPosixProcessIdentity => ({ executable: `/proc/${pid}/exe`, pgid, pid, ppid, sid, startTime, state,}); function makeController(input: { readonly processes: ReadonlyArray<AcpPosixProcessIdentity>; readonly onProcess?: ( processes: Map<number, AcpPosixProcessIdentity>, pid: number, signal: NodeJS.Signals, ) => void;}) { const processes = new Map(input.processes.map((process) => [process.pid, process])); const signals: Array<string> = []; const controller: AcpPosixProcessTreeController = { childPidsOf: (pid) => [...processes.values()] .filter((process) => process.ppid === pid) .map((process) => process.pid), childrenOf: (pid) => [...processes.values()].filter((process) => process.ppid === pid), identity: (pid) => processes.get(pid), snapshot: () => [...processes.values()], signalProcess: (pid, signal) => { signals.push(`process:${pid}:${signal}`); if (input.onProcess) return input.onProcess(processes, pid, signal); processes.delete(pid); }, }; return { controller, processes, signals };} const server = () => identity(process.pid, 1, process.pid, process.pid, "server"); function processExists(pid: number): boolean { try { process.kill(pid, 0); return true; } catch { return false; }} describe("terminatePosixOwnedProcessTree", () => { it("parses unified cgroup paths and escaped cgroup2 mountinfo", () => { expect(parseUnifiedCgroupPath("0::/user.slice/app.scope\n")).toBe("/user.slice/app.scope"); expect(parseUnifiedCgroupPath("0::/one\n0::/two\n")).toBeUndefined(); expect(isSafeCgroupPath("/user.slice/app.scope")).toBe(true); expect(isSafeCgroupPath("/user.slice/../escape")).toBe(false); expect(isSafeCgroupPath("/user.slice/app.scope (deleted)")).toBe(false); expect( parseCgroup2Mount( "42 31 0:37 /user.slice /sys/fs/cgroup/user\\040mount rw - cgroup2 cgroup2 rw\n", ), ).toEqual({ mountPoint: "/sys/fs/cgroup/user mount", root: "/user.slice" }); expect(parseCgroup2Mount("42 31 0:37 / /sys/fs/cgroup rw - tmpfs tmpfs rw\n")).toBeUndefined(); expect( parseCgroup2Mounts( "41 31 0:36 /other /sys/fs/cgroup/other rw - cgroup2 cgroup2 rw\n" + "42 31 0:37 /user.slice /sys/fs/cgroup/user rw - cgroup2 cgroup2 rw\n", ), ).toHaveLength(2); }); it.live("sweeps empty t3-acp sibling leases owned by dead pids only", () => Effect.sync(() => { const scratchRoot = NodePath.join(process.cwd(), "tmp"); NodeFS.mkdirSync(scratchRoot, { recursive: true }); const parent = NodeFS.mkdtempSync(NodePath.join(scratchRoot, "acp-cgroup-stale-siblings-")); // Temp dirs are not cgroupfs: rmdir fails when control files exist, so the // test injects populated/remove while still exercising the real selection // and path-safety logic against a real parent directory tree. const populatedByPath = new Map<string, "0" | "1">(); const writeSibling = (name: string, populated: "0" | "1" | null) => { const path = NodePath.join(parent, name); NodeFS.mkdirSync(path); if (populated !== null) populatedByPath.set(path, populated); return path; }; const deadPid = 2_147_483_646; // Synthetic live foreign pid (not this process, still reported alive by the probe). const liveForeignPid = 2_147_483_645; const staleEmpty = writeSibling(`t3-acp-${deadPid}-aaaa`, "0"); const currentPidSibling = writeSibling(`t3-acp-${process.pid}-bbbb`, "0"); const liveForeignSibling = writeSibling(`t3-acp-${liveForeignPid}-cccc`, "0"); const stalePopulated = writeSibling(`t3-acp-${deadPid}-dddd`, "1"); const unrelated = writeSibling("other-lease", "0"); const isProcessAlive = (pid: number) => pid === process.pid || pid === liveForeignPid; try { sweepStaleLinuxCgroupSiblings(parent, { isProcessAlive, readPopulated: (path) => populatedByPath.get(path), remove: (path) => { populatedByPath.delete(path); NodeFS.rmdirSync(path); }, }); expect(NodeFS.existsSync(staleEmpty)).toBe(false); expect(NodeFS.existsSync(currentPidSibling)).toBe(true); expect(NodeFS.existsSync(liveForeignSibling)).toBe(true); expect(NodeFS.existsSync(stalePopulated)).toBe(true); expect(NodeFS.existsSync(unrelated)).toBe(true); // Missing populated state must not remove a sibling. writeSibling(`t3-acp-${deadPid}-eeee`, null); sweepStaleLinuxCgroupSiblings(parent, { isProcessAlive, readPopulated: (path) => populatedByPath.get(path), remove: (path) => { populatedByPath.delete(path); NodeFS.rmdirSync(path); }, }); expect(NodeFS.existsSync(NodePath.join(parent, `t3-acp-${deadPid}-eeee`))).toBe(true); expect(NodeFS.existsSync(currentPidSibling)).toBe(true); } finally { NodeFS.rmSync(parent, { recursive: true, force: true }); } }), ); it.live("preserves exact argv and strips wrapper-only environment before exec", () => Effect.gen(function* () { if ((yield* HostProcessPlatform) !== "linux") return; const lease = makeLinuxCgroupController().create(); if (lease === undefined) return; const scratchRoot = NodePath.join(process.cwd(), "tmp"); NodeFS.mkdirSync(scratchRoot, { recursive: true }); const scratch = NodeFS.mkdtempSync(NodePath.join(scratchRoot, "acp-cgroup-wrapper-")); const linkedNode = NodePath.join(scratch, "T3 Code AppImage 'quoted'"); const bareGrok = NodePath.join(scratch, "grok"); const relativeBin = NodePath.join(scratch, "relative-bin"); const directoryBin = NodePath.join(scratch, "directory-bin"); const validBin = NodePath.join(scratch, "valid-bin"); const outputPath = NodePath.join(scratch, "argv.json"); NodeFS.mkdirSync(relativeBin); NodeFS.mkdirSync(NodePath.join(directoryBin, "grok"), { recursive: true }); NodeFS.mkdirSync(validBin); NodeFS.symlinkSync(process.execPath, linkedNode); NodeFS.symlinkSync(process.execPath, bareGrok); NodeFS.symlinkSync(process.execPath, NodePath.join(relativeBin, "grok")); NodeFS.symlinkSync(process.execPath, NodePath.join(validBin, "grok")); expect(resolveLinuxCgroupTargetCommand("grok", scratch, { PATH: scratch })).toBe(bareGrok); expect(resolveLinuxCgroupTargetCommand("grok", scratch, { PATH: "" })).toBe(bareGrok); // Undefined PATH uses Node's default search path (/usr/bin:/bin), not cwd alone // (bareGrok lives only in cwd / PATH=scratch). expect(resolveLinuxCgroupTargetCommand("grok", scratch, { PATH: undefined })).toBeUndefined(); expect(resolveLinuxCgroupTargetCommand("node", scratch, { PATH: undefined })).toBeDefined(); expect(resolveLinuxCgroupTargetCommand("grok", scratch, { PATH: "relative-bin" })).toBe( NodePath.join(relativeBin, "grok"), ); expect( resolveLinuxCgroupTargetCommand("grok", scratch, { PATH: `directory-bin${NodePath.delimiter}valid-bin`, }), ).toBe(NodePath.join(validBin, "grok")); const target = [ 'const fs = require("node:fs");', "fs.writeFileSync(process.argv[1], JSON.stringify({", " args: process.argv.slice(2),", " electron: process.env.ELECTRON_RUN_AS_NODE,", " wrapper: process.env.T3_ACP_CGROUP_WRAPPER,", "}));", ].join("\n"); const wrapped = wrapCommandForLinuxCgroup(lease, linkedNode, [ "-e", target, outputPath, "space value", "single'quote", 'double"quote', ]); try { const result = NodeChildProcess.spawnSync(wrapped.command, wrapped.args, { encoding: "utf8", env: { ...process.env, ELECTRON_RUN_AS_NODE: "1", T3_ACP_CGROUP_WRAPPER: "1", }, }); expect(result.status, result.stderr).toBe(0); expect(NodeFS.readFileSync(outputPath, "utf8")).toBe( '{"args":["space value","single\'quote","double\\"quote"]}', ); } finally { yield* terminateLinuxCgroupLease(lease).pipe(Effect.ignore); NodeFS.rmSync(scratch, { recursive: true, force: true }); } }), ); it.live("kills a post-TERM detached double fork without touching an unrelated sentinel", () => Effect.gen(function* () { if ((yield* HostProcessPlatform) !== "linux") return; const controller = makeLinuxCgroupController(); const lease = controller.create(); if (lease === undefined) return; expect(parseUnifiedCgroupPath(NodeFS.readFileSync("/proc/self/cgroup", "utf8"))).not.toBe( lease.relativePath, ); const scratchRoot = NodePath.join(process.cwd(), "tmp"); NodeFS.mkdirSync(scratchRoot, { recursive: true }); const scratch = NodeFS.mkdtempSync(NodePath.join(scratchRoot, "acp-cgroup-kill-")); const readyPath = NodePath.join(scratch, "ready"); const childPath = NodePath.join(scratch, "child"); const childProgram = "setInterval(() => {}, 1000);"; const launcherProgram = [ 'const fs = require("node:fs");', 'const cp = require("node:child_process");', "const child = cp.spawn(process.execPath, ['-e', process.argv[2]], { detached: true, stdio: 'ignore' });", "fs.writeFileSync(process.argv[1], String(child.pid));", "child.unref();", ].join("\n"); const providerProgram = [ 'const fs = require("node:fs");', 'const cp = require("node:child_process");', "fs.writeFileSync(process.argv[1], String(process.pid));", "process.on('SIGTERM', () => {", " const launcher = cp.spawn(process.execPath, ['-e', process.argv[3], process.argv[2], process.argv[4]], { detached: true, stdio: 'ignore' });", " launcher.unref();", " process.exit(0);", "});", "setInterval(() => {}, 1000);", ].join("\n"); const wrapped = wrapCommandForLinuxCgroup(lease, process.execPath, [ "-e", providerProgram, readyPath, childPath, launcherProgram, childProgram, ]); const provider = NodeChildProcess.spawn(wrapped.command, wrapped.args, { detached: true, env: { ...process.env, ELECTRON_RUN_AS_NODE: "1", T3_ACP_CGROUP_WRAPPER: "1" }, stdio: "ignore", }); provider.unref(); const sentinel = NodeChildProcess.spawn(process.execPath, ["-e", childProgram], { detached: true, stdio: "ignore", }); sentinel.unref(); let detachedChildPid: number | undefined; try { yield* Effect.gen(function* () { while (!NodeFS.existsSync(readyPath)) yield* Effect.sleep("10 millis"); }).pipe(Effect.timeout("2 seconds")); process.kill(provider.pid!, "SIGTERM"); detachedChildPid = yield* Effect.gen(function* () { while (true) { try { const pid = Number(NodeFS.readFileSync(childPath, "utf8")); if (Number.isSafeInteger(pid) && pid > 1) return pid; } catch { // The TERM-time launcher has not published its child yet. } yield* Effect.sleep("10 millis"); } }).pipe(Effect.timeout("2 seconds")); expect(processExists(detachedChildPid)).toBe(true); yield* terminateLinuxCgroupLease(lease); expect(processExists(sentinel.pid!)).toBe(true); yield* Effect.gen(function* () { while (processExists(detachedChildPid!)) yield* Effect.sleep("10 millis"); }).pipe(Effect.timeout("2 seconds")); expect(NodeFS.existsSync(lease.path)).toBe(false); const replacement = controller.create(); expect(replacement?.path).not.toBe(lease.path); if (replacement !== undefined) { NodeFS.mkdirSync(NodePath.join(replacement.path, "nested")); yield* terminateLinuxCgroupLease(replacement); expect(NodeFS.existsSync(replacement.path)).toBe(false); } } finally { yield* terminateLinuxCgroupLease(lease).pipe(Effect.ignore); try { process.kill(-provider.pid!, "SIGKILL"); } catch { // The contained provider already exited. } try { process.kill(-sentinel.pid!, "SIGKILL"); } catch { // The unrelated sentinel already exited. } if (detachedChildPid !== undefined && processExists(detachedChildPid)) { try { process.kill(detachedChildPid, "SIGKILL"); } catch { // The detached child already exited. } } NodeFS.rmSync(scratch, { recursive: true, force: true }); } }), ); it.live("re-kills repopulated cgroups and verifies root removal after retryable errors", () => Effect.gen(function* () { let exists = true; let killCalls = 0; let populated = false; let removeCalls = 0; const lease: AcpLinuxCgroupLease = { contains: () => false, exists: () => exists, path: "/test/t3-acp-repopulation", relativePath: "/test/t3-acp-repopulation", kill: () => { killCalls += 1; populated = false; }, populated: () => populated, remove: () => { removeCalls += 1; if (removeCalls === 1) { populated = true; const error = new Error("repopulated") as NodeJS.ErrnoException; error.code = "EBUSY"; throw error; } if (removeCalls === 2) { const error = new Error("nested path vanished") as NodeJS.ErrnoException; error.code = "ENOENT"; throw error; } exists = false; }, }; yield* terminateLinuxCgroupLease(lease); expect(killCalls).toBeGreaterThanOrEqual(3); expect(removeCalls).toBe(3); expect(exists).toBe(false); yield* terminateLinuxCgroupLease(lease); expect(removeCalls).toBe(3); }), ); it.live("reports persistent retryable removal failures as removal failures", () => Effect.gen(function* () { const removeError = Object.assign(new Error("cgroup remains busy"), { code: "EBUSY", }); const lease: AcpLinuxCgroupLease = { contains: () => false, exists: () => true, path: "/test/t3-acp-busy", relativePath: "/test/t3-acp-busy", kill: () => undefined, populated: () => false, remove: () => { throw removeError; }, }; const error = yield* Effect.flip(terminateLinuxCgroupLease(lease)); expect(error.detail).toBe("Failed to remove ACP cgroup /test/t3-acp-busy"); expect(error.cause).toBe(removeError); }), ); it("sends TERM only to the exact captured root while it remains in the child cgroup", () => { const fixture = makeController({ processes: [server(), identity(100, process.pid, 100, 100, "owned")], }); const ownedRoot: AcpOwnedPosixProcess = { ...identity(100, process.pid, 100, 100, "owned"), parentExecutable: server().executable, parentStartTime: server().startTime, }; const root: AcpPosixOwnershipRoot = { captureAttempted: true, value: ownedRoot }; const lease: AcpLinuxCgroupLease = { contains: () => true, exists: () => true, path: "/test/t3-acp-root", relativePath: "/test/t3-acp-root", kill: () => undefined, populated: () => true, remove: () => undefined, }; signalLinuxCgroupRootTerm({ controller: fixture.controller, lease, root }); expect(fixture.signals).toEqual(["process:100:SIGTERM"]); const reused = makeController({ processes: [server(), identity(100, process.pid, 100, 100, "reused")], }); signalLinuxCgroupRootTerm({ controller: reused.controller, lease, root }); expect(reused.signals).toEqual([]); const migrated = makeController({ processes: [server(), identity(100, process.pid, 100, 100, "owned")], }); signalLinuxCgroupRootTerm({ controller: migrated.controller, lease: { ...lease, contains: () => false }, root, }); expect(migrated.signals).toEqual([]); }); it.live("finds a child forked by a non-leader pthread", () => Effect.gen(function* () { if ((yield* HostProcessPlatform) !== "linux") return; const scratchRoot = NodePath.join(process.cwd(), "tmp"); NodeFS.mkdirSync(scratchRoot, { recursive: true }); const scratch = NodeFS.mkdtempSync(NodePath.join(scratchRoot, "acp-pthread-spawn-")); const binary = NodePath.join(scratch, "helper"); const pidPath = NodePath.join(scratch, "pids"); expect(NodeFS.statSync(threadSpawnHelperSource).isFile()).toBe(true); const compile = NodeChildProcess.spawnSync( "cc", ["-pthread", threadSpawnHelperSource, "-o", binary], { encoding: "utf8", }, ); if ((compile.error as NodeJS.ErrnoException | undefined)?.code === "ENOENT") { NodeFS.rmSync(scratch, { recursive: true, force: true }); return; } expect(compile.status, compile.stderr).toBe(0); const helper = NodeChildProcess.spawn(binary, [pidPath], { detached: true, stdio: "ignore" }); helper.unref(); try { const published = yield* Effect.gen(function* () { while (true) { try { const ids = NodeFS.readFileSync(pidPath, "utf8").trim().split(/\s+/).map(Number); if (ids.length === 2 && ids.every(Number.isSafeInteger)) return ids; } catch { // The worker has not published its TID and child yet. } yield* Effect.sleep("10 millis"); } }).pipe(Effect.timeout("2 seconds")); const [workerTid, childPid] = published; expect(workerTid).not.toBe(helper.pid); expect(makePosixProcessTreeController("linux").childrenOf(helper.pid!)).toContainEqual( expect.objectContaining({ pid: childPid, ppid: helper.pid }), ); } finally { try { process.kill(-helper.pid!, "SIGKILL"); } catch {Lines 1–500 of 1090. Load more
// @effect-diagnostics nodeBuiltinImport:off import * as NodeChildProcess from "node:child_process"; import * as NodeFS from "node:fs"; import * as NodePath from "node:path"; import * as NodeURL from "node:url"; import { describe, expect, it } from "@effect/vitest"; import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; import * as Clock from "effect/Clock"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as Scope from "effect/Scope"; import { capturePosixOwnershipLedger, isSafeCgroupPath, makeLinuxCgroupController, makePosixProcessTreeController, observePosixOwnershipLedger, observePosixOwnershipLedgerContinuously, parseCgroup2Mount, parseCgroup2Mounts, parseUnifiedCgroupPath, posixProcessIsZombie, resolveLinuxCgroupTargetCommand, signalLinuxCgroupRootTerm, sweepStaleLinuxCgroupSiblings, terminateLinuxCgroupLease, terminatePosixOwnedProcessTree, wrapCommandForLinuxCgroup, type AcpOwnedPosixProcess, type AcpLinuxCgroupLease, type AcpPosixOwnershipRoot, type AcpPosixProcessIdentity, type AcpPosixProcessTreeController, } from "./AcpSessionRuntime.ts"; const threadSpawnHelperSource = NodeURL.fileURLToPath( new URL("../../../scripts/acp-thread-spawn-helper.c", import.meta.url), ); const identity = ( pid: number, ppid: number, pgid: number, sid: number, startTime = String(pid), state = "S", ): AcpPosixProcessIdentity => ({ executable: `/proc/${pid}/exe`, pgid, pid, ppid, sid, startTime, state, }); function makeController(input: { readonly processes: ReadonlyArray<AcpPosixProcessIdentity>; readonly onProcess?: ( processes: Map<number, AcpPosixProcessIdentity>, pid: number, signal: NodeJS.Signals, ) => void; }) { const processes = new Map(input.processes.map((process) => [process.pid, process])); const signals: Array<string> = []; const controller: AcpPosixProcessTreeController = { childPidsOf: (pid) => [...processes.values()] .filter((process) => process.ppid === pid) .map((process) => process.pid), childrenOf: (pid) => [...processes.values()].filter((process) => process.ppid === pid), identity: (pid) => processes.get(pid), snapshot: () => [...processes.values()], signalProcess: (pid, signal) => { signals.push(`process:${pid}:${signal}`); if (input.onProcess) return input.onProcess(processes, pid, signal); processes.delete(pid); }, }; return { controller, processes, signals }; } const server = () => identity(process.pid, 1, process.pid, process.pid, "server"); function processExists(pid: number): boolean { try { process.kill(pid, 0); return true; } catch { return false; } } describe("terminatePosixOwnedProcessTree", () => { it("parses unified cgroup paths and escaped cgroup2 mountinfo", () => { expect(parseUnifiedCgroupPath("0::/user.slice/app.scope\n")).toBe("/user.slice/app.scope"); expect(parseUnifiedCgroupPath("0::/one\n0::/two\n")).toBeUndefined(); expect(isSafeCgroupPath("/user.slice/app.scope")).toBe(true); expect(isSafeCgroupPath("/user.slice/../escape")).toBe(false); expect(isSafeCgroupPath("/user.slice/app.scope (deleted)")).toBe(false); expect( parseCgroup2Mount( "42 31 0:37 /user.slice /sys/fs/cgroup/user\\040mount rw - cgroup2 cgroup2 rw\n", ), ).toEqual({ mountPoint: "/sys/fs/cgroup/user mount", root: "/user.slice" }); expect(parseCgroup2Mount("42 31 0:37 / /sys/fs/cgroup rw - tmpfs tmpfs rw\n")).toBeUndefined(); expect( parseCgroup2Mounts( "41 31 0:36 /other /sys/fs/cgroup/other rw - cgroup2 cgroup2 rw\n" + "42 31 0:37 /user.slice /sys/fs/cgroup/user rw - cgroup2 cgroup2 rw\n", ), ).toHaveLength(2); }); it.live("sweeps empty t3-acp sibling leases owned by dead pids only", () => Effect.sync(() => { const scratchRoot = NodePath.join(process.cwd(), "tmp"); NodeFS.mkdirSync(scratchRoot, { recursive: true }); const parent = NodeFS.mkdtempSync(NodePath.join(scratchRoot, "acp-cgroup-stale-siblings-")); // Temp dirs are not cgroupfs: rmdir fails when control files exist, so the // test injects populated/remove while still exercising the real selection // and path-safety logic against a real parent directory tree. const populatedByPath = new Map<string, "0" | "1">(); const writeSibling = (name: string, populated: "0" | "1" | null) => { const path = NodePath.join(parent, name); NodeFS.mkdirSync(path); if (populated !== null) populatedByPath.set(path, populated); return path; }; const deadPid = 2_147_483_646; // Synthetic live foreign pid (not this process, still reported alive by the probe). const liveForeignPid = 2_147_483_645; const staleEmpty = writeSibling(`t3-acp-${deadPid}-aaaa`, "0"); const currentPidSibling = writeSibling(`t3-acp-${process.pid}-bbbb`, "0"); const liveForeignSibling = writeSibling(`t3-acp-${liveForeignPid}-cccc`, "0"); const stalePopulated = writeSibling(`t3-acp-${deadPid}-dddd`, "1"); const unrelated = writeSibling("other-lease", "0"); const isProcessAlive = (pid: number) => pid === process.pid || pid === liveForeignPid; try { sweepStaleLinuxCgroupSiblings(parent, { isProcessAlive, readPopulated: (path) => populatedByPath.get(path), remove: (path) => { populatedByPath.delete(path); NodeFS.rmdirSync(path); }, }); expect(NodeFS.existsSync(staleEmpty)).toBe(false); expect(NodeFS.existsSync(currentPidSibling)).toBe(true); expect(NodeFS.existsSync(liveForeignSibling)).toBe(true); expect(NodeFS.existsSync(stalePopulated)).toBe(true); expect(NodeFS.existsSync(unrelated)).toBe(true); // Missing populated state must not remove a sibling. writeSibling(`t3-acp-${deadPid}-eeee`, null); sweepStaleLinuxCgroupSiblings(parent, { isProcessAlive, readPopulated: (path) => populatedByPath.get(path), remove: (path) => { populatedByPath.delete(path); NodeFS.rmdirSync(path); }, }); expect(NodeFS.existsSync(NodePath.join(parent, `t3-acp-${deadPid}-eeee`))).toBe(true); expect(NodeFS.existsSync(currentPidSibling)).toBe(true); } finally { NodeFS.rmSync(parent, { recursive: true, force: true }); } }), ); it.live("preserves exact argv and strips wrapper-only environment before exec", () => Effect.gen(function* () { if ((yield* HostProcessPlatform) !== "linux") return; const lease = makeLinuxCgroupController().create(); if (lease === undefined) return; const scratchRoot = NodePath.join(process.cwd(), "tmp"); NodeFS.mkdirSync(scratchRoot, { recursive: true }); const scratch = NodeFS.mkdtempSync(NodePath.join(scratchRoot, "acp-cgroup-wrapper-")); const linkedNode = NodePath.join(scratch, "T3 Code AppImage 'quoted'"); const bareGrok = NodePath.join(scratch, "grok"); const relativeBin = NodePath.join(scratch, "relative-bin"); const directoryBin = NodePath.join(scratch, "directory-bin"); const validBin = NodePath.join(scratch, "valid-bin"); const outputPath = NodePath.join(scratch, "argv.json"); NodeFS.mkdirSync(relativeBin); NodeFS.mkdirSync(NodePath.join(directoryBin, "grok"), { recursive: true }); NodeFS.mkdirSync(validBin); NodeFS.symlinkSync(process.execPath, linkedNode); NodeFS.symlinkSync(process.execPath, bareGrok); NodeFS.symlinkSync(process.execPath, NodePath.join(relativeBin, "grok")); NodeFS.symlinkSync(process.execPath, NodePath.join(validBin, "grok")); expect(resolveLinuxCgroupTargetCommand("grok", scratch, { PATH: scratch })).toBe(bareGrok); expect(resolveLinuxCgroupTargetCommand("grok", scratch, { PATH: "" })).toBe(bareGrok); // Undefined PATH uses Node's default search path (/usr/bin:/bin), not cwd alone // (bareGrok lives only in cwd / PATH=scratch). expect(resolveLinuxCgroupTargetCommand("grok", scratch, { PATH: undefined })).toBeUndefined(); expect(resolveLinuxCgroupTargetCommand("node", scratch, { PATH: undefined })).toBeDefined(); expect(resolveLinuxCgroupTargetCommand("grok", scratch, { PATH: "relative-bin" })).toBe( NodePath.join(relativeBin, "grok"), ); expect( resolveLinuxCgroupTargetCommand("grok", scratch, { PATH: `directory-bin${NodePath.delimiter}valid-bin`, }), ).toBe(NodePath.join(validBin, "grok")); const target = [ 'const fs = require("node:fs");', "fs.writeFileSync(process.argv[1], JSON.stringify({", " args: process.argv.slice(2),", " electron: process.env.ELECTRON_RUN_AS_NODE,", " wrapper: process.env.T3_ACP_CGROUP_WRAPPER,", "}));", ].join("\n"); const wrapped = wrapCommandForLinuxCgroup(lease, linkedNode, [ "-e", target, outputPath, "space value", "single'quote", 'double"quote', ]); try { const result = NodeChildProcess.spawnSync(wrapped.command, wrapped.args, { encoding: "utf8", env: { ...process.env, ELECTRON_RUN_AS_NODE: "1", T3_ACP_CGROUP_WRAPPER: "1", }, }); expect(result.status, result.stderr).toBe(0); expect(NodeFS.readFileSync(outputPath, "utf8")).toBe( '{"args":["space value","single\'quote","double\\"quote"]}', ); } finally { yield* terminateLinuxCgroupLease(lease).pipe(Effect.ignore); NodeFS.rmSync(scratch, { recursive: true, force: true }); } }), ); it.live("kills a post-TERM detached double fork without touching an unrelated sentinel", () => Effect.gen(function* () { if ((yield* HostProcessPlatform) !== "linux") return; const controller = makeLinuxCgroupController(); const lease = controller.create(); if (lease === undefined) return; expect(parseUnifiedCgroupPath(NodeFS.readFileSync("/proc/self/cgroup", "utf8"))).not.toBe( lease.relativePath, ); const scratchRoot = NodePath.join(process.cwd(), "tmp"); NodeFS.mkdirSync(scratchRoot, { recursive: true }); const scratch = NodeFS.mkdtempSync(NodePath.join(scratchRoot, "acp-cgroup-kill-")); const readyPath = NodePath.join(scratch, "ready"); const childPath = NodePath.join(scratch, "child"); const childProgram = "setInterval(() => {}, 1000);"; const launcherProgram = [ 'const fs = require("node:fs");', 'const cp = require("node:child_process");', "const child = cp.spawn(process.execPath, ['-e', process.argv[2]], { detached: true, stdio: 'ignore' });", "fs.writeFileSync(process.argv[1], String(child.pid));", "child.unref();", ].join("\n"); const providerProgram = [ 'const fs = require("node:fs");', 'const cp = require("node:child_process");', "fs.writeFileSync(process.argv[1], String(process.pid));", "process.on('SIGTERM', () => {", " const launcher = cp.spawn(process.execPath, ['-e', process.argv[3], process.argv[2], process.argv[4]], { detached: true, stdio: 'ignore' });", " launcher.unref();", " process.exit(0);", "});", "setInterval(() => {}, 1000);", ].join("\n"); const wrapped = wrapCommandForLinuxCgroup(lease, process.execPath, [ "-e", providerProgram, readyPath, childPath, launcherProgram, childProgram, ]); const provider = NodeChildProcess.spawn(wrapped.command, wrapped.args, { detached: true, env: { ...process.env, ELECTRON_RUN_AS_NODE: "1", T3_ACP_CGROUP_WRAPPER: "1" }, stdio: "ignore", }); provider.unref(); const sentinel = NodeChildProcess.spawn(process.execPath, ["-e", childProgram], { detached: true, stdio: "ignore", }); sentinel.unref(); let detachedChildPid: number | undefined; try { yield* Effect.gen(function* () { while (!NodeFS.existsSync(readyPath)) yield* Effect.sleep("10 millis"); }).pipe(Effect.timeout("2 seconds")); process.kill(provider.pid!, "SIGTERM"); detachedChildPid = yield* Effect.gen(function* () { while (true) { try { const pid = Number(NodeFS.readFileSync(childPath, "utf8")); if (Number.isSafeInteger(pid) && pid > 1) return pid; } catch { // The TERM-time launcher has not published its child yet. } yield* Effect.sleep("10 millis"); } }).pipe(Effect.timeout("2 seconds")); expect(processExists(detachedChildPid)).toBe(true); yield* terminateLinuxCgroupLease(lease); expect(processExists(sentinel.pid!)).toBe(true); yield* Effect.gen(function* () { while (processExists(detachedChildPid!)) yield* Effect.sleep("10 millis"); }).pipe(Effect.timeout("2 seconds")); expect(NodeFS.existsSync(lease.path)).toBe(false); const replacement = controller.create(); expect(replacement?.path).not.toBe(lease.path); if (replacement !== undefined) { NodeFS.mkdirSync(NodePath.join(replacement.path, "nested")); yield* terminateLinuxCgroupLease(replacement); expect(NodeFS.existsSync(replacement.path)).toBe(false); } } finally { yield* terminateLinuxCgroupLease(lease).pipe(Effect.ignore); try { process.kill(-provider.pid!, "SIGKILL"); } catch { // The contained provider already exited. } try { process.kill(-sentinel.pid!, "SIGKILL"); } catch { // The unrelated sentinel already exited. } if (detachedChildPid !== undefined && processExists(detachedChildPid)) { try { process.kill(detachedChildPid, "SIGKILL"); } catch { // The detached child already exited. } } NodeFS.rmSync(scratch, { recursive: true, force: true }); } }), ); it.live("re-kills repopulated cgroups and verifies root removal after retryable errors", () => Effect.gen(function* () { let exists = true; let killCalls = 0; let populated = false; let removeCalls = 0; const lease: AcpLinuxCgroupLease = { contains: () => false, exists: () => exists, path: "/test/t3-acp-repopulation", relativePath: "/test/t3-acp-repopulation", kill: () => { killCalls += 1; populated = false; }, populated: () => populated, remove: () => { removeCalls += 1; if (removeCalls === 1) { populated = true; const error = new Error("repopulated") as NodeJS.ErrnoException; error.code = "EBUSY"; throw error; } if (removeCalls === 2) { const error = new Error("nested path vanished") as NodeJS.ErrnoException; error.code = "ENOENT"; throw error; } exists = false; }, }; yield* terminateLinuxCgroupLease(lease); expect(killCalls).toBeGreaterThanOrEqual(3); expect(removeCalls).toBe(3); expect(exists).toBe(false); yield* terminateLinuxCgroupLease(lease); expect(removeCalls).toBe(3); }), ); it.live("reports persistent retryable removal failures as removal failures", () => Effect.gen(function* () { const removeError = Object.assign(new Error("cgroup remains busy"), { code: "EBUSY", }); const lease: AcpLinuxCgroupLease = { contains: () => false, exists: () => true, path: "/test/t3-acp-busy", relativePath: "/test/t3-acp-busy", kill: () => undefined, populated: () => false, remove: () => { throw removeError; }, }; const error = yield* Effect.flip(terminateLinuxCgroupLease(lease)); expect(error.detail).toBe("Failed to remove ACP cgroup /test/t3-acp-busy"); expect(error.cause).toBe(removeError); }), ); it("sends TERM only to the exact captured root while it remains in the child cgroup", () => { const fixture = makeController({ processes: [server(), identity(100, process.pid, 100, 100, "owned")], }); const ownedRoot: AcpOwnedPosixProcess = { ...identity(100, process.pid, 100, 100, "owned"), parentExecutable: server().executable, parentStartTime: server().startTime, }; const root: AcpPosixOwnershipRoot = { captureAttempted: true, value: ownedRoot }; const lease: AcpLinuxCgroupLease = { contains: () => true, exists: () => true, path: "/test/t3-acp-root", relativePath: "/test/t3-acp-root", kill: () => undefined, populated: () => true, remove: () => undefined, }; signalLinuxCgroupRootTerm({ controller: fixture.controller, lease, root }); expect(fixture.signals).toEqual(["process:100:SIGTERM"]); const reused = makeController({ processes: [server(), identity(100, process.pid, 100, 100, "reused")], }); signalLinuxCgroupRootTerm({ controller: reused.controller, lease, root }); expect(reused.signals).toEqual([]); const migrated = makeController({ processes: [server(), identity(100, process.pid, 100, 100, "owned")], }); signalLinuxCgroupRootTerm({ controller: migrated.controller, lease: { ...lease, contains: () => false }, root, }); expect(migrated.signals).toEqual([]); }); it.live("finds a child forked by a non-leader pthread", () => Effect.gen(function* () { if ((yield* HostProcessPlatform) !== "linux") return; const scratchRoot = NodePath.join(process.cwd(), "tmp"); NodeFS.mkdirSync(scratchRoot, { recursive: true }); const scratch = NodeFS.mkdtempSync(NodePath.join(scratchRoot, "acp-pthread-spawn-")); const binary = NodePath.join(scratch, "helper"); const pidPath = NodePath.join(scratch, "pids"); expect(NodeFS.statSync(threadSpawnHelperSource).isFile()).toBe(true); const compile = NodeChildProcess.spawnSync( "cc", ["-pthread", threadSpawnHelperSource, "-o", binary], { encoding: "utf8", }, ); if ((compile.error as NodeJS.ErrnoException | undefined)?.code === "ENOENT") { NodeFS.rmSync(scratch, { recursive: true, force: true }); return; } expect(compile.status, compile.stderr).toBe(0); const helper = NodeChildProcess.spawn(binary, [pidPath], { detached: true, stdio: "ignore" }); helper.unref(); try { const published = yield* Effect.gen(function* () { while (true) { try { const ids = NodeFS.readFileSync(pidPath, "utf8").trim().split(/\s+/).map(Number); if (ids.length === 2 && ids.every(Number.isSafeInteger)) return ids; } catch { // The worker has not published its TID and child yet. } yield* Effect.sleep("10 millis"); } }).pipe(Effect.timeout("2 seconds")); const [workerTid, childPid] = published; expect(workerTid).not.toBe(helper.pid); expect(makePosixProcessTreeController("linux").childrenOf(helper.pid!)).toContainEqual( expect.objectContaining({ pid: childPid, ppid: helper.pid }), ); } finally { try { process.kill(-helper.pid!, "SIGKILL"); } catch {