mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-03 13:22:14 +00:00
219 lines
6.0 KiB
JavaScript
219 lines
6.0 KiB
JavaScript
import { spawnSync } from "node:child_process";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
const DEFAULT_LOCAL_GO_GC = "30";
|
|
const DEFAULT_LOCAL_GO_MEMORY_LIMIT = "3GiB";
|
|
const DEFAULT_LOCK_TIMEOUT_MS = 10 * 60 * 1000;
|
|
const DEFAULT_LOCK_POLL_MS = 500;
|
|
const DEFAULT_STALE_LOCK_MS = 30 * 1000;
|
|
const SLEEP_BUFFER = new Int32Array(new SharedArrayBuffer(4));
|
|
|
|
export function isLocalCheckEnabled(env) {
|
|
const raw = env.OPENCLAW_LOCAL_CHECK?.trim().toLowerCase();
|
|
return raw !== "0" && raw !== "false";
|
|
}
|
|
|
|
export function hasFlag(args, name) {
|
|
return args.some((arg) => arg === name || arg.startsWith(`${name}=`));
|
|
}
|
|
|
|
export function applyLocalTsgoPolicy(args, env) {
|
|
const nextEnv = { ...env };
|
|
const nextArgs = [...args];
|
|
|
|
if (!isLocalCheckEnabled(nextEnv)) {
|
|
return { env: nextEnv, args: nextArgs };
|
|
}
|
|
|
|
insertBeforeSeparator(nextArgs, "--singleThreaded");
|
|
insertBeforeSeparator(nextArgs, "--checkers", "1");
|
|
|
|
if (!nextEnv.GOGC) {
|
|
nextEnv.GOGC = DEFAULT_LOCAL_GO_GC;
|
|
}
|
|
if (!nextEnv.GOMEMLIMIT) {
|
|
nextEnv.GOMEMLIMIT = DEFAULT_LOCAL_GO_MEMORY_LIMIT;
|
|
}
|
|
if (nextEnv.OPENCLAW_TSGO_PPROF_DIR && !hasFlag(nextArgs, "--pprofDir")) {
|
|
insertBeforeSeparator(nextArgs, "--pprofDir", nextEnv.OPENCLAW_TSGO_PPROF_DIR);
|
|
}
|
|
|
|
return { env: nextEnv, args: nextArgs };
|
|
}
|
|
|
|
export function applyLocalOxlintPolicy(args, env) {
|
|
const nextEnv = { ...env };
|
|
const nextArgs = [...args];
|
|
|
|
insertBeforeSeparator(nextArgs, "--type-aware");
|
|
insertBeforeSeparator(nextArgs, "--tsconfig", "tsconfig.oxlint.json");
|
|
|
|
if (isLocalCheckEnabled(nextEnv)) {
|
|
insertBeforeSeparator(nextArgs, "--threads=1");
|
|
}
|
|
|
|
return { env: nextEnv, args: nextArgs };
|
|
}
|
|
|
|
export function acquireLocalHeavyCheckLockSync(params) {
|
|
const env = params.env ?? process.env;
|
|
|
|
if (!isLocalCheckEnabled(env)) {
|
|
return () => {};
|
|
}
|
|
|
|
const commonDir = resolveGitCommonDir(params.cwd);
|
|
const locksDir = path.join(commonDir, "openclaw-local-checks");
|
|
const lockDir = path.join(locksDir, `${params.lockName ?? "heavy-check"}.lock`);
|
|
const ownerPath = path.join(lockDir, "owner.json");
|
|
const timeoutMs = readPositiveInt(
|
|
env.OPENCLAW_HEAVY_CHECK_LOCK_TIMEOUT_MS,
|
|
DEFAULT_LOCK_TIMEOUT_MS,
|
|
);
|
|
const pollMs = readPositiveInt(env.OPENCLAW_HEAVY_CHECK_LOCK_POLL_MS, DEFAULT_LOCK_POLL_MS);
|
|
const staleLockMs = readPositiveInt(
|
|
env.OPENCLAW_HEAVY_CHECK_STALE_LOCK_MS,
|
|
DEFAULT_STALE_LOCK_MS,
|
|
);
|
|
const startedAt = Date.now();
|
|
let waitingLogged = false;
|
|
|
|
fs.mkdirSync(locksDir, { recursive: true });
|
|
|
|
for (;;) {
|
|
try {
|
|
fs.mkdirSync(lockDir);
|
|
writeOwnerFile(ownerPath, {
|
|
pid: process.pid,
|
|
tool: params.toolName,
|
|
cwd: params.cwd,
|
|
hostname: os.hostname(),
|
|
createdAt: new Date().toISOString(),
|
|
});
|
|
return () => {
|
|
fs.rmSync(lockDir, { recursive: true, force: true });
|
|
};
|
|
} catch (error) {
|
|
if (!isAlreadyExistsError(error)) {
|
|
throw error;
|
|
}
|
|
|
|
const owner = readOwnerFile(ownerPath);
|
|
if (shouldReclaimLock({ owner, lockDir, staleLockMs })) {
|
|
fs.rmSync(lockDir, { recursive: true, force: true });
|
|
continue;
|
|
}
|
|
|
|
const elapsedMs = Date.now() - startedAt;
|
|
if (elapsedMs >= timeoutMs) {
|
|
const ownerLabel = describeOwner(owner);
|
|
throw new Error(
|
|
`[${params.toolName}] timed out waiting for the local heavy-check lock at ${lockDir}${
|
|
ownerLabel ? ` (${ownerLabel})` : ""
|
|
}. If no local heavy checks are still running, remove the stale lock and retry.`,
|
|
{ cause: error },
|
|
);
|
|
}
|
|
|
|
if (!waitingLogged) {
|
|
const ownerLabel = describeOwner(owner);
|
|
console.error(
|
|
`[${params.toolName}] waiting for the local heavy-check lock${
|
|
ownerLabel ? ` held by ${ownerLabel}` : ""
|
|
}...`,
|
|
);
|
|
waitingLogged = true;
|
|
}
|
|
|
|
sleepSync(pollMs);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function resolveGitCommonDir(cwd) {
|
|
const result = spawnSync("git", ["rev-parse", "--git-common-dir"], {
|
|
cwd,
|
|
encoding: "utf8",
|
|
stdio: ["ignore", "pipe", "ignore"],
|
|
});
|
|
|
|
if (result.status === 0) {
|
|
const raw = result.stdout.trim();
|
|
if (raw.length > 0) {
|
|
return path.resolve(cwd, raw);
|
|
}
|
|
}
|
|
|
|
return path.join(cwd, ".git");
|
|
}
|
|
|
|
function insertBeforeSeparator(args, ...items) {
|
|
if (items.length > 0 && hasFlag(args, items[0])) {
|
|
return;
|
|
}
|
|
|
|
const separatorIndex = args.indexOf("--");
|
|
const insertIndex = separatorIndex === -1 ? args.length : separatorIndex;
|
|
args.splice(insertIndex, 0, ...items);
|
|
}
|
|
|
|
function readPositiveInt(rawValue, fallback) {
|
|
const parsed = Number.parseInt(rawValue ?? "", 10);
|
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
}
|
|
|
|
function writeOwnerFile(ownerPath, owner) {
|
|
fs.writeFileSync(ownerPath, `${JSON.stringify(owner, null, 2)}\n`, "utf8");
|
|
}
|
|
|
|
function readOwnerFile(ownerPath) {
|
|
try {
|
|
return JSON.parse(fs.readFileSync(ownerPath, "utf8"));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function isAlreadyExistsError(error) {
|
|
return Boolean(error && typeof error === "object" && "code" in error && error.code === "EEXIST");
|
|
}
|
|
|
|
function shouldReclaimLock({ owner, lockDir, staleLockMs }) {
|
|
if (owner && typeof owner.pid === "number") {
|
|
return !isProcessAlive(owner.pid);
|
|
}
|
|
|
|
try {
|
|
const stats = fs.statSync(lockDir);
|
|
return Date.now() - stats.mtimeMs >= staleLockMs;
|
|
} catch {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function isProcessAlive(pid) {
|
|
try {
|
|
process.kill(pid, 0);
|
|
return true;
|
|
} catch (error) {
|
|
return Boolean(error && typeof error === "object" && "code" in error && error.code === "EPERM");
|
|
}
|
|
}
|
|
|
|
function describeOwner(owner) {
|
|
if (!owner || typeof owner !== "object") {
|
|
return "";
|
|
}
|
|
|
|
const tool = typeof owner.tool === "string" ? owner.tool : "unknown-tool";
|
|
const pid = typeof owner.pid === "number" ? `pid ${owner.pid}` : "unknown pid";
|
|
const cwd = typeof owner.cwd === "string" ? owner.cwd : "unknown cwd";
|
|
return `${tool}, ${pid}, cwd ${cwd}`;
|
|
}
|
|
|
|
function sleepSync(ms) {
|
|
Atomics.wait(SLEEP_BUFFER, 0, 0, ms);
|
|
}
|