Skip to content

Commit 37e0fae

Browse files
pbemberjuliusmarminge
authored andcommitted
fix(server): launch contained ACP providers from packaged runtimes (#12791)
1 parent 1e24061 commit 37e0fae

2 files changed

Lines changed: 88 additions & 13 deletions

File tree

‎apps/server/src/provider/acp/AcpSessionRuntime.processTree.test.ts‎

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as NodeFS from "node:fs";
44
import * as NodePath from "node:path";
55
import * as NodeURL from "node:url";
66

7-
import { describe, expect, it } from "@effect/vitest";
7+
import { describe, expect, it, vi } from "@effect/vitest";
88
import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
99
import * as Clock from "effect/Clock";
1010
import * as Effect from "effect/Effect";
@@ -242,6 +242,80 @@ describe("terminatePosixOwnedProcessTree", () => {
242242
}),
243243
);
244244

245+
it("contains a packaged-runtime command without requiring cgroup delegation", () => {
246+
if (HostProcessPlatform.defaultValue() !== "linux") return;
247+
const scratchRoot = NodePath.join(process.cwd(), "tmp");
248+
NodeFS.mkdirSync(scratchRoot, { recursive: true });
249+
const scratch = NodeFS.mkdtempSync(NodePath.join(scratchRoot, "acp-cgroup-wrapper-fake-"));
250+
const leasePath = NodePath.join(scratch, "lease with spaces");
251+
const outputPath = NodePath.join(scratch, "argv output");
252+
NodeFS.mkdirSync(leasePath);
253+
NodeFS.writeFileSync(NodePath.join(leasePath, "cgroup.procs"), "");
254+
const relativePath = parseUnifiedCgroupPath(NodeFS.readFileSync("/proc/self/cgroup", "utf8"));
255+
expect(relativePath).toBeDefined();
256+
const lease: AcpLinuxCgroupLease = {
257+
contains: () => false,
258+
exists: () => true,
259+
path: leasePath,
260+
relativePath: relativePath!,
261+
kill: () => undefined,
262+
populated: () => false,
263+
remove: () => undefined,
264+
};
265+
const packagedExecPath = vi.spyOn(process, "execPath", "get").mockReturnValue("/bin/sh");
266+
try {
267+
const wrapped = wrapCommandForLinuxCgroup(lease, "/bin/sh", [
268+
"-c",
269+
'printf "%s\\n" "$0" "$1" "$2" "$3" "${ELECTRON_RUN_AS_NODE-}" "${T3_ACP_CGROUP_WRAPPER-}" > "$4"',
270+
"packaged-target",
271+
"space value",
272+
"single'quote",
273+
'double"quote',
274+
outputPath,
275+
]);
276+
const result = NodeChildProcess.spawnSync(wrapped.command, wrapped.args, {
277+
encoding: "utf8",
278+
env: {
279+
...process.env,
280+
ELECTRON_RUN_AS_NODE: "1",
281+
T3_ACP_CGROUP_WRAPPER: "1",
282+
},
283+
});
284+
expect(result.status, result.stderr).toBe(0);
285+
expect(NodeFS.readFileSync(outputPath, "utf8")).toBe(
286+
"packaged-target\nspace value\nsingle'quote\ndouble\"quote\n\n\n",
287+
);
288+
expect(NodeFS.readFileSync(NodePath.join(leasePath, "cgroup.procs"), "utf8")).toMatch(
289+
/^\d+\n$/,
290+
);
291+
292+
const mismatch = wrapCommandForLinuxCgroup(
293+
{ ...lease, relativePath: "/not-the-current-cgroup" },
294+
"/bin/sh",
295+
["-c", 'printf executed > "$0"', outputPath],
296+
);
297+
NodeFS.rmSync(outputPath);
298+
const mismatchResult = NodeChildProcess.spawnSync(mismatch.command, mismatch.args, {
299+
encoding: "utf8",
300+
});
301+
expect(mismatchResult.status, mismatchResult.stderr).toBe(126);
302+
expect(NodeFS.existsSync(outputPath)).toBe(false);
303+
304+
const missingTarget = wrapCommandForLinuxCgroup(lease, "/nonexistent-t3-probe", []);
305+
const missingTargetResult = NodeChildProcess.spawnSync(
306+
missingTarget.command,
307+
missingTarget.args,
308+
{
309+
encoding: "utf8",
310+
},
311+
);
312+
expect(missingTargetResult.status, missingTargetResult.stderr).toBe(125);
313+
} finally {
314+
packagedExecPath.mockRestore();
315+
NodeFS.rmSync(scratch, { recursive: true, force: true });
316+
}
317+
});
318+
245319
it.live("kills a post-TERM detached double fork without touching an unrelated sentinel", () =>
246320
Effect.gen(function* () {
247321
if ((yield* HostProcessPlatform) !== "linux") return;

‎apps/server/src/provider/acp/AcpSessionRuntime.ts‎

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -264,21 +264,22 @@ export function wrapCommandForLinuxCgroup(
264264
args: ReadonlyArray<string>,
265265
): { readonly command: string; readonly args: ReadonlyArray<string> } {
266266
return {
267-
command: process.execPath,
267+
command: "/bin/sh",
268268
args: [
269-
"-e",
269+
"-c",
270270
[
271-
'const fs = require("node:fs");',
272-
"try {",
273-
' fs.writeFileSync(process.argv[1] + "/cgroup.procs", String(process.pid) + "\\n");',
274-
' const actual = fs.readFileSync("/proc/self/cgroup", "utf8").split("\\n").find((line) => line.startsWith("0::"))?.slice(3);',
275-
" if (actual !== process.argv[2]) process.exit(126);",
276-
" const env = { ...process.env };",
277-
" delete env.ELECTRON_RUN_AS_NODE;",
278-
" delete env.T3_ACP_CGROUP_WRAPPER;",
279-
" process.execve(process.argv[3], process.argv.slice(3), env);",
280-
"} catch { process.exit(125); }",
271+
"lease_path=$1; expected=$2; shift 2",
272+
'printf "%s\\n" "$$" > "$lease_path/cgroup.procs" || exit 125',
273+
"actual=",
274+
"while IFS= read -r line; do",
275+
' case "$line" in 0::*) [ -z "$actual" ] || exit 126; actual=${line#0::};; esac',
276+
"done < /proc/self/cgroup || exit 125",
277+
'[ "$actual" = "$expected" ] || exit 126',
278+
"unset ELECTRON_RUN_AS_NODE T3_ACP_CGROUP_WRAPPER",
279+
"trap 'exit 125' 0",
280+
'exec "$@"',
281281
].join("\n"),
282+
"t3-acp-cgroup-wrapper",
282283
lease.path,
283284
lease.relativePath,
284285
command,

0 commit comments

Comments
 (0)