mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-26 10:13:27 +08:00
perf(gateway): cache compiled entry startup in source checkouts (#170514)
Select compile-cache policy from the executing entry file instead of probing the installation for source checkout markers. Preserve TypeScript source respawn behavior and reuse the existing build-scoped cache owner for compiled entries. In a 5,000-session synthetic Linux fixture, repeated process-start to readiness fell from 8.31s to 7.04s; bootstrap fell from 2.96s to 2.44s. Focused entry, diagnostic and compiled-helper tests passed, with independent review clean. Cold-cache population remains a separate one-time cost.
This commit is contained in:
1 parent
ebf0a83c6b
commit
e517de8ca2
7 files changed
+49
-36
No files matched your search
@@ -77,7 +77,7 @@ Gateway config reload watches the active config file path (resolved from profile
|
||||
|
||||
### Module compile cache
|
||||
|
||||
The packaged CLI and the compiled `dist/index.js` entry automatically enable
|
||||
The packaged CLI and the compiled `dist/entry.js` and `dist/index.js` entries automatically enable
|
||||
Node's on-disk module compile cache. This includes compiled source deployments
|
||||
that retain their Git checkout. Cache entries live outside the release directory,
|
||||
under the operating system's temporary directory by default. Set
|
||||
|
||||
@@ -143,7 +143,9 @@ describe("entry compile cache", () => {
|
||||
|
||||
async function markSourceCheckout() {
|
||||
await fs.mkdir(path.join(root, "src"), { recursive: true });
|
||||
await fs.writeFile(path.join(root, "src", "entry.ts"), "export {};\n", "utf8");
|
||||
entryFile = path.join(root, "src", "entry.ts");
|
||||
argv[1] = entryFile;
|
||||
await fs.writeFile(entryFile, "export {};\n", "utf8");
|
||||
}
|
||||
|
||||
it("resolves install roots from source and dist entry paths", () => {
|
||||
@@ -153,26 +155,37 @@ describe("entry compile cache", () => {
|
||||
});
|
||||
|
||||
it.each(["git", "source", "package"])(
|
||||
"activates compile cache only for an enabled %s install",
|
||||
"activates compiled-entry cache for an enabled %s install",
|
||||
async (kind) => {
|
||||
if (kind === "git") {
|
||||
await fs.writeFile(path.join(root, ".git"), "gitdir: .git/worktrees/openclaw\n", "utf8");
|
||||
} else if (kind === "source") {
|
||||
await markSourceCheckout();
|
||||
await fs.mkdir(path.join(root, "src"));
|
||||
await fs.writeFile(path.join(root, "src", "entry.ts"), "export {};\n");
|
||||
}
|
||||
enableOpenClawCompileCache({ env: {}, installRoot: root });
|
||||
expect(enableCompileCache).toHaveBeenCalledTimes(kind === "package" ? 1 : 0);
|
||||
enableOpenClawCompileCache({ env: { NODE_DISABLE_COMPILE_CACHE: "1" }, installRoot: root });
|
||||
enableOpenClawCompileCache({ currentFile: entryFile, env: {}, installRoot: root });
|
||||
expect(enableCompileCache).toHaveBeenCalledTimes(1);
|
||||
enableOpenClawCompileCache({
|
||||
currentFile: entryFile,
|
||||
env: { NODE_DISABLE_COMPILE_CACHE: "1" },
|
||||
installRoot: root,
|
||||
});
|
||||
setTestEnvValue("NODE_DISABLE_COMPILE_CACHE", "1");
|
||||
enableOpenClawCompileCache({ installRoot: root });
|
||||
expect(enableCompileCache).toHaveBeenCalledTimes(kind === "package" ? 1 : 0);
|
||||
enableOpenClawCompileCache({ currentFile: entryFile, installRoot: root });
|
||||
expect(enableCompileCache).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
);
|
||||
|
||||
it("leaves source entry compilation uncached", async () => {
|
||||
await markSourceCheckout();
|
||||
enableOpenClawCompileCache({ currentFile: entryFile, env: {}, installRoot: root });
|
||||
expect(enableCompileCache).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips cache activation with a warning when Windows TEMP makes the path too long", () => {
|
||||
vi.spyOn(os, "tmpdir").mockReturnValue(path.join(root, "x".repeat(200)));
|
||||
withMockedPlatform("win32", () => {
|
||||
enableOpenClawCompileCache({ env: {}, installRoot: root });
|
||||
enableOpenClawCompileCache({ currentFile: entryFile, env: {}, installRoot: root });
|
||||
});
|
||||
expect(enableCompileCache).not.toHaveBeenCalled();
|
||||
expect(writeStderr).toHaveBeenCalledExactlyOnceWith(
|
||||
@@ -200,7 +213,7 @@ describe("entry compile cache", () => {
|
||||
const packageJsonPath = path.join(root, "package.json");
|
||||
const env = { NODE_COMPILE_CACHE: path.join(root, ".node-cache") };
|
||||
await fs.writeFile(packageJsonPath, '{"version":"2026.4.29"}\n', "utf8");
|
||||
enableOpenClawCompileCache({ env, installRoot: root });
|
||||
enableOpenClawCompileCache({ currentFile: entryFile, env, installRoot: root });
|
||||
const originalDirectory = enabledDirectory();
|
||||
expect(originalDirectory).toContain(path.join(".node-cache", "openclaw"));
|
||||
expect(originalDirectory).toContain("2026.4.29");
|
||||
@@ -210,7 +223,7 @@ describe("entry compile cache", () => {
|
||||
'{"version":"2026.4.29","installation":"replacement"}\n',
|
||||
"utf8",
|
||||
);
|
||||
enableOpenClawCompileCache({ env, installRoot: root });
|
||||
enableOpenClawCompileCache({ currentFile: entryFile, env, installRoot: root });
|
||||
const replacementDirectory = enabledDirectory(1);
|
||||
expect(replacementDirectory).toContain(path.join("openclaw", "2026.4.29"));
|
||||
expect(replacementDirectory).not.toBe(originalDirectory);
|
||||
@@ -224,7 +237,7 @@ describe("entry compile cache", () => {
|
||||
deleteTestEnvValue("NODE_COMPILE_CACHE");
|
||||
getCompileCacheDir.mockReturnValue(path.join(root, ".active-cache"));
|
||||
}
|
||||
const { completion } = await startRespawn({ currentFile: entryFile, installRoot: root });
|
||||
const { completion } = await startRespawn({ currentFile: entryFile });
|
||||
expect(spawn).toHaveBeenCalledOnce();
|
||||
const [command, args, options] = expectDefined(spawn.mock.calls[0], "respawn call");
|
||||
expect(command).toBe(process.execPath);
|
||||
@@ -261,11 +274,14 @@ describe("entry compile cache", () => {
|
||||
["gmail", "win32", "source", false],
|
||||
["gateway", "linux", "source", false],
|
||||
["status", "linux", "package", false],
|
||||
["status", "linux", "compiled", false],
|
||||
["status", "linux", "respawned", false],
|
||||
] as const)(
|
||||
"respects %s/%s respawn policy for %s",
|
||||
async (command, platform, install, respawns) => {
|
||||
if (install !== "package") {
|
||||
if (install === "compiled") {
|
||||
await fs.writeFile(path.join(root, ".git"), "gitdir: fixture\n");
|
||||
} else if (install !== "package") {
|
||||
await markSourceCheckout();
|
||||
}
|
||||
if (install === "respawned") {
|
||||
@@ -278,7 +294,6 @@ describe("entry compile cache", () => {
|
||||
await withMockedPlatform(platform, async () => {
|
||||
const completion = respawnWithoutOpenClawCompileCacheIfNeeded({
|
||||
currentFile: entryFile,
|
||||
installRoot: root,
|
||||
});
|
||||
if (respawns) {
|
||||
await spawned.promise;
|
||||
@@ -293,7 +308,7 @@ describe("entry compile cache", () => {
|
||||
it.each([[], ["tui"]])("keeps interactive no-cache respawns attached: %j", async (...command) => {
|
||||
await markSourceCheckout();
|
||||
argv = [process.execPath, entryFile, ...command];
|
||||
const { completion } = await startRespawn({ currentFile: entryFile, installRoot: root });
|
||||
const { completion } = await startRespawn({ currentFile: entryFile });
|
||||
expect(expectDefined(spawn.mock.calls[0], "respawn call")[2]?.detached).toBe(false);
|
||||
child.emit("close", 0, null);
|
||||
await completion;
|
||||
@@ -309,7 +324,6 @@ describe("entry compile cache", () => {
|
||||
await withMockedPlatform(platform, async () => {
|
||||
const { completion } = await startRespawn({
|
||||
currentFile: entryFile,
|
||||
installRoot: root,
|
||||
});
|
||||
const [, options] = expectDefined(attachChildProcessBridge.mock.calls[0], "bridge call");
|
||||
expectDefined(options?.onSignal, "signal handler")("SIGTERM");
|
||||
@@ -346,7 +360,6 @@ describe("entry compile cache", () => {
|
||||
const prepareWriteError = vi.fn(() => writer.promise);
|
||||
const diagnosticCompletion = respawnWithoutOpenClawCompileCacheIfNeeded({
|
||||
currentFile: entryFile,
|
||||
installRoot: root,
|
||||
prepareWriteError,
|
||||
});
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Manages compile-cache respawn behavior for the CLI entrypoint.
|
||||
import { spawn } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import { getCompileCacheDir } from "node:module";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
@@ -25,11 +24,8 @@ export function resolveEntryInstallRoot(entryFile: string): string {
|
||||
return entryParent === "dist" || entryParent === "src" ? path.dirname(entryDir) : entryDir;
|
||||
}
|
||||
|
||||
function isSourceCheckoutInstallRoot(installRoot: string): boolean {
|
||||
return (
|
||||
existsSync(path.join(installRoot, ".git")) ||
|
||||
existsSync(path.join(installRoot, "src", "entry.ts"))
|
||||
);
|
||||
function isSourceEntry(currentFile: string): boolean {
|
||||
return path.extname(currentFile) === ".ts";
|
||||
}
|
||||
|
||||
function isNodeCompileCacheRequested(env: NodeJS.ProcessEnv): boolean {
|
||||
@@ -45,7 +41,6 @@ type OpenClawCompileCacheRespawnPlan = {
|
||||
|
||||
function buildOpenClawCompileCacheRespawnPlan(params: {
|
||||
currentFile: string;
|
||||
installRoot: string;
|
||||
compileCacheDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): OpenClawCompileCacheRespawnPlan | undefined {
|
||||
@@ -59,7 +54,7 @@ function buildOpenClawCompileCacheRespawnPlan(params: {
|
||||
if (isForegroundGmailRunArgv(argv) || shouldKeepNativeHookRelayInProcess(argv, platform)) {
|
||||
return undefined;
|
||||
}
|
||||
if (!isSourceCheckoutInstallRoot(params.installRoot)) {
|
||||
if (!isSourceEntry(params.currentFile)) {
|
||||
return undefined;
|
||||
}
|
||||
if (env[COMPILE_CACHE_DISABLED_RESPAWNED_ENV] === "1") {
|
||||
@@ -84,13 +79,11 @@ function buildOpenClawCompileCacheRespawnPlan(params: {
|
||||
|
||||
export async function respawnWithoutOpenClawCompileCacheIfNeeded(params: {
|
||||
currentFile: string;
|
||||
installRoot: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
prepareWriteError?: () => Promise<(message: string) => void | Promise<void>>;
|
||||
}): Promise<boolean> {
|
||||
const plan = buildOpenClawCompileCacheRespawnPlan({
|
||||
currentFile: params.currentFile,
|
||||
installRoot: params.installRoot,
|
||||
compileCacheDir: getCompileCacheDir?.(),
|
||||
env: params.env,
|
||||
});
|
||||
@@ -119,10 +112,11 @@ export async function respawnWithoutOpenClawCompileCacheIfNeeded(params: {
|
||||
}
|
||||
|
||||
export function enableOpenClawCompileCache(params: {
|
||||
currentFile: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
installRoot: string;
|
||||
}): void {
|
||||
if (!isSourceCheckoutInstallRoot(params.installRoot)) {
|
||||
if (!isSourceEntry(params.currentFile)) {
|
||||
enableInstallCompileCache({
|
||||
directory: resolveOpenClawCompileCacheDirectory(params),
|
||||
env: params.env,
|
||||
|
||||
+1
-1
@@ -166,7 +166,6 @@ async function runEntryMain(): Promise<void> {
|
||||
!isNodeHostLauncherChild() &&
|
||||
(await respawnWithoutOpenClawCompileCacheIfNeeded({
|
||||
currentFile: entryFile,
|
||||
installRoot,
|
||||
env: startupEnv,
|
||||
prepareWriteError: async () => {
|
||||
// The child environment was already snapshotted. Load dotenv only to format
|
||||
@@ -177,6 +176,7 @@ async function runEntryMain(): Promise<void> {
|
||||
}));
|
||||
if (!waitingForCompileCacheRespawn) {
|
||||
enableOpenClawCompileCache({
|
||||
currentFile: entryFile,
|
||||
installRoot,
|
||||
});
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ fs.writeFileSync(path.join(installRoot, "package.json"), '{"version":"2026.9.6"}
|
||||
assert.equal(getCompileCacheDir(), undefined);
|
||||
const beforeEnable = { ...process.env };
|
||||
enableOpenClawCompileCache({
|
||||
currentFile: path.join(installRoot, "dist", "entry.js"),
|
||||
installRoot,
|
||||
env: { ...process.env, NODE_COMPILE_CACHE: path.join(root, "native-cache") },
|
||||
});
|
||||
|
||||
@@ -36,9 +36,10 @@ fs.mkdirSync(installRoot);
|
||||
fs.writeFileSync(path.join(installRoot, "package.json"), '{"version":"2026.9.6"}');
|
||||
assert.equal(getCompileCacheDir(), undefined, "fixture must start without a native cache");
|
||||
const initialEnv = { ...process.env };
|
||||
if (testCase.owner === "source") {
|
||||
fs.writeFileSync(path.join(installRoot, ".git"), "gitdir: fixture");
|
||||
}
|
||||
const currentFile = path.join(
|
||||
installRoot,
|
||||
testCase.owner === "source" ? "src/entry.ts" : "dist/entry.js",
|
||||
);
|
||||
if (testCase.owner === "foreign") {
|
||||
// Negative control: a direct Node enable is NOT OpenClaw ownership.
|
||||
const enabled = enableCompileCache(path.join(root, "foreign"));
|
||||
@@ -47,10 +48,11 @@ if (testCase.owner === "foreign") {
|
||||
if (testCase.owner === "failed") {
|
||||
const blocked = path.join(root, "not-a-directory");
|
||||
fs.writeFileSync(blocked, "fixture");
|
||||
enableOpenClawCompileCache({ installRoot, env: { NODE_COMPILE_CACHE: blocked } });
|
||||
enableOpenClawCompileCache({ currentFile, installRoot, env: { NODE_COMPILE_CACHE: blocked } });
|
||||
} else {
|
||||
// Positive control goes through the real OpenClaw entry enable owner.
|
||||
enableOpenClawCompileCache({
|
||||
currentFile,
|
||||
installRoot,
|
||||
env: { ...process.env, NODE_COMPILE_CACHE: path.join(root, "native-cache") },
|
||||
});
|
||||
@@ -72,7 +74,10 @@ if (owned) {
|
||||
// A later enable attempt must not replace the first owner's input.
|
||||
const otherRoot = path.join(root, "other-install");
|
||||
fs.mkdirSync(otherRoot);
|
||||
enableOpenClawCompileCache({ installRoot: otherRoot });
|
||||
enableOpenClawCompileCache({
|
||||
currentFile: path.join(otherRoot, "dist", "entry.js"),
|
||||
installRoot: otherRoot,
|
||||
});
|
||||
assert.equal(resolveNodeCompileCacheEnv({}).NODE_COMPILE_CACHE, handoff.NODE_COMPILE_CACHE);
|
||||
assert.equal(getCompileCacheDir(), parentDirectory);
|
||||
} else {
|
||||
|
||||
@@ -1851,7 +1851,7 @@ console.log(JSON.stringify({ parent: process.pid, bun: process.versions.bun, mar
|
||||
import { enableOpenClawCompileCache } from "./cache-owner.mjs";
|
||||
const before = { ...process.env };
|
||||
// First enable for direct entry; ALREADY_ENABLED after the real launcher.
|
||||
enableOpenClawCompileCache({ installRoot: ${JSON.stringify(fixtureRoot)} });
|
||||
enableOpenClawCompileCache({ currentFile: ${JSON.stringify(path.join(fixtureRoot, mode === "source" ? "src/entry.ts" : "dist/entry.js"))}, installRoot: ${JSON.stringify(fixtureRoot)} });
|
||||
const parentDirectory = getCompileCacheDir() ?? null;
|
||||
const a = resolveA();
|
||||
const b = resolveB();
|
||||
|
||||
Reference in new issue
Block a user