mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-08 02:52:15 +00:00
fix(package): kill candidate resolver trees on windows
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
// Normalizes package-acceptance inputs into the tarball shape consumed by Docker E2E.
|
||||
import { spawn } from "node:child_process";
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { lookup as dnsLookupCb } from "node:dns";
|
||||
import { lookup as dnsLookup } from "node:dns/promises";
|
||||
@@ -197,17 +197,7 @@ function run(command, args, options = {}) {
|
||||
let timedOut = false;
|
||||
let killTimer;
|
||||
let forceKillAt;
|
||||
const killChild = (signal) => {
|
||||
if (useProcessGroup && child.pid) {
|
||||
try {
|
||||
process.kill(-child.pid, signal);
|
||||
return;
|
||||
} catch {
|
||||
// The process group can disappear between timeout and cleanup.
|
||||
}
|
||||
}
|
||||
child.kill(signal);
|
||||
};
|
||||
const killChild = (signal) => signalChildProcessTree(child, signal, { useProcessGroup });
|
||||
const terminateChild = () => {
|
||||
killChild("SIGTERM");
|
||||
const killAfterMs = options.killAfterMs ?? COMMAND_TIMEOUT_KILL_AFTER_MS;
|
||||
@@ -312,6 +302,36 @@ async function finishTimedOutProcessTree(
|
||||
}
|
||||
}
|
||||
|
||||
export function signalChildProcessTree(
|
||||
child,
|
||||
signal,
|
||||
{
|
||||
platform = process.platform,
|
||||
runTaskkill = spawnSync,
|
||||
useProcessGroup = platform !== "win32",
|
||||
} = {},
|
||||
) {
|
||||
if (useProcessGroup && child.pid) {
|
||||
try {
|
||||
process.kill(-child.pid, signal);
|
||||
return;
|
||||
} catch {
|
||||
// The process group can disappear between timeout and cleanup.
|
||||
}
|
||||
}
|
||||
if (platform === "win32" && typeof child.pid === "number") {
|
||||
const args = ["/PID", String(child.pid), "/T"];
|
||||
if (signal === "SIGKILL") {
|
||||
args.push("/F");
|
||||
}
|
||||
const result = runTaskkill("taskkill", args, { stdio: "ignore" });
|
||||
if (!result?.error && result?.status === 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
child.kill(signal);
|
||||
}
|
||||
|
||||
function childHasExited(child) {
|
||||
return child.exitCode !== null || child.signalCode !== null;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { access, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promise
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
ARTIFACT_TARBALL_SCAN_MAX_ENTRIES,
|
||||
assertExpectedSha256ForTest,
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
readPackageBuildSourceSha,
|
||||
resolveNpmPackageCandidatePackRunner,
|
||||
runCommandForTest,
|
||||
signalChildProcessTree,
|
||||
validateOpenClawPackageSpec,
|
||||
} from "../../scripts/resolve-openclaw-package-candidate.mjs";
|
||||
|
||||
@@ -214,6 +215,31 @@ describe("resolve-openclaw-package-candidate", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("signals Windows package runner process trees with taskkill", () => {
|
||||
const child = {
|
||||
kill: vi.fn(),
|
||||
pid: 12345,
|
||||
};
|
||||
const runTaskkill = vi.fn(() => ({ error: undefined, status: 0 }));
|
||||
|
||||
signalChildProcessTree(child, "SIGTERM", {
|
||||
platform: "win32",
|
||||
runTaskkill,
|
||||
});
|
||||
expect(runTaskkill).toHaveBeenNthCalledWith(1, "taskkill", ["/PID", "12345", "/T"], {
|
||||
stdio: "ignore",
|
||||
});
|
||||
|
||||
signalChildProcessTree(child, "SIGKILL", {
|
||||
platform: "win32",
|
||||
runTaskkill,
|
||||
});
|
||||
expect(runTaskkill).toHaveBeenNthCalledWith(2, "taskkill", ["/PID", "12345", "/T", "/F"], {
|
||||
stdio: "ignore",
|
||||
});
|
||||
expect(child.kill).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps npm pack filenames inside the package candidate output directory", async () => {
|
||||
const dir = await mkdtemp(path.join(tmpdir(), "openclaw-package-npm-pack-"));
|
||||
tempDirs.push(dir);
|
||||
@@ -688,26 +714,30 @@ describe("resolve-openclaw-package-candidate", () => {
|
||||
const requestHeaders: Array<Record<string, string> | undefined> = [];
|
||||
|
||||
try {
|
||||
await downloadUrl("https://packages.internal:8443/artifactory/openclaw/openclaw.tgz", target, {
|
||||
fetchImpl: async (_url: URL, init?: RequestInit) => {
|
||||
requestHeaders.push(init?.headers as Record<string, string> | undefined);
|
||||
if (requestHeaders.length === 1) {
|
||||
return new Response(null, {
|
||||
headers: {
|
||||
location: "https://mirror.internal:8443/artifactory/openclaw/openclaw.tgz",
|
||||
},
|
||||
status: 302,
|
||||
await downloadUrl(
|
||||
"https://packages.internal:8443/artifactory/openclaw/openclaw.tgz",
|
||||
target,
|
||||
{
|
||||
fetchImpl: async (_url: URL, init?: RequestInit) => {
|
||||
requestHeaders.push(init?.headers as Record<string, string> | undefined);
|
||||
if (requestHeaders.length === 1) {
|
||||
return new Response(null, {
|
||||
headers: {
|
||||
location: "https://mirror.internal:8443/artifactory/openclaw/openclaw.tgz",
|
||||
},
|
||||
status: 302,
|
||||
});
|
||||
}
|
||||
return new Response(new Uint8Array([4, 5, 6]), {
|
||||
headers: { "content-length": "3" },
|
||||
status: 200,
|
||||
});
|
||||
}
|
||||
return new Response(new Uint8Array([4, 5, 6]), {
|
||||
headers: { "content-length": "3" },
|
||||
status: 200,
|
||||
});
|
||||
},
|
||||
lookupHost: lookupAddresses([{ address: "10.0.0.8", family: 4 }]),
|
||||
maxBytes: 3,
|
||||
trustedSource,
|
||||
},
|
||||
lookupHost: lookupAddresses([{ address: "10.0.0.8", family: 4 }]),
|
||||
maxBytes: 3,
|
||||
trustedSource,
|
||||
});
|
||||
);
|
||||
} finally {
|
||||
if (previousToken === undefined) {
|
||||
delete process.env.OPENCLAW_TRUSTED_PACKAGE_TOKEN;
|
||||
|
||||
Reference in New Issue
Block a user