mirror of
https://fastgit.cc/github.com/openclaw/openclaw
synced 2026-05-01 06:36:23 +08:00
perf(infra): cache login shell env probes
This commit is contained in:
@@ -187,6 +187,40 @@ describe("shell env fallback", () => {
|
||||
expect(exec2).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reuses the cached login-shell env probe across repeated fallback reads", () => {
|
||||
resetShellPathCacheForTests();
|
||||
const env: NodeJS.ProcessEnv = {};
|
||||
const exec = vi.fn(() =>
|
||||
Buffer.from("OPENAI_API_KEY=from-shell\0ANTHROPIC_API_KEY=from-shell-anthropic\0"),
|
||||
);
|
||||
|
||||
expect(
|
||||
loadShellEnvFallback({
|
||||
enabled: true,
|
||||
env,
|
||||
expectedKeys: ["OPENAI_API_KEY"],
|
||||
exec: exec as unknown as Parameters<typeof loadShellEnvFallback>[0]["exec"],
|
||||
}),
|
||||
).toEqual({
|
||||
ok: true,
|
||||
applied: ["OPENAI_API_KEY"],
|
||||
});
|
||||
|
||||
expect(
|
||||
loadShellEnvFallback({
|
||||
enabled: true,
|
||||
env,
|
||||
expectedKeys: ["ANTHROPIC_API_KEY"],
|
||||
exec: exec as unknown as Parameters<typeof loadShellEnvFallback>[0]["exec"],
|
||||
}),
|
||||
).toEqual({
|
||||
ok: true,
|
||||
applied: ["ANTHROPIC_API_KEY"],
|
||||
});
|
||||
|
||||
expect(exec).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("tracks last applied keys across success, skip, and failure paths", () => {
|
||||
const successEnv: NodeJS.ProcessEnv = {};
|
||||
const successExec = vi.fn(() =>
|
||||
|
||||
@@ -12,6 +12,9 @@ const DEFAULT_SHELL = "/bin/sh";
|
||||
let lastAppliedKeys: string[] = [];
|
||||
let cachedShellPath: string | null | undefined;
|
||||
let cachedEtcShells: Set<string> | null | undefined;
|
||||
let nextExecCacheId = 1;
|
||||
const loginShellEnvProbeCache = new Map<string, Array<[string, string]>>();
|
||||
const execCacheIds = new WeakMap<object, number>();
|
||||
|
||||
function resolveShellExecEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
||||
const execEnv = sanitizeHostExecEnv({ baseEnv: env });
|
||||
@@ -111,6 +114,52 @@ function parseShellEnv(stdout: Buffer): Map<string, string> {
|
||||
return shellEnv;
|
||||
}
|
||||
|
||||
function resolveExecCacheId(exec: typeof execFileSync | undefined): string {
|
||||
if (!exec) {
|
||||
return "default";
|
||||
}
|
||||
const key = exec as object;
|
||||
let id = execCacheIds.get(key);
|
||||
if (!id) {
|
||||
id = nextExecCacheId;
|
||||
nextExecCacheId += 1;
|
||||
execCacheIds.set(key, id);
|
||||
}
|
||||
return `exec:${id}`;
|
||||
}
|
||||
|
||||
function createLoginShellEnvCacheKey(params: {
|
||||
shell: string;
|
||||
timeoutMs: number;
|
||||
exec?: typeof execFileSync;
|
||||
execEnv: NodeJS.ProcessEnv;
|
||||
}): string {
|
||||
const startupEnvEntries = Object.entries(params.execEnv)
|
||||
.filter(([key]) => {
|
||||
if (
|
||||
key === "HOME" ||
|
||||
key === "PATH" ||
|
||||
key === "TERM" ||
|
||||
key === "LANG" ||
|
||||
key === "LC_ALL" ||
|
||||
key === "LC_CTYPE" ||
|
||||
key === "USER" ||
|
||||
key === "LOGNAME" ||
|
||||
key === "TMPDIR"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return key.startsWith("XDG_") || key.startsWith("OPENCLAW_");
|
||||
})
|
||||
.toSorted(([left], [right]) => left.localeCompare(right));
|
||||
return JSON.stringify([
|
||||
params.shell,
|
||||
params.timeoutMs,
|
||||
resolveExecCacheId(params.exec),
|
||||
startupEnvEntries,
|
||||
]);
|
||||
}
|
||||
|
||||
type LoginShellEnvProbeResult =
|
||||
| { ok: true; shellEnv: Map<string, string> }
|
||||
| { ok: false; error: string };
|
||||
@@ -124,10 +173,22 @@ function probeLoginShellEnv(params: {
|
||||
const timeoutMs = resolveTimeoutMs(params.timeoutMs);
|
||||
const shell = resolveShell(params.env);
|
||||
const execEnv = resolveShellExecEnv(params.env);
|
||||
const cacheKey = createLoginShellEnvCacheKey({
|
||||
shell,
|
||||
timeoutMs,
|
||||
exec: params.exec,
|
||||
execEnv,
|
||||
});
|
||||
const cached = loginShellEnvProbeCache.get(cacheKey);
|
||||
if (cached) {
|
||||
return { ok: true, shellEnv: new Map(cached) };
|
||||
}
|
||||
|
||||
try {
|
||||
const stdout = execLoginShellEnvZero({ shell, env: execEnv, exec, timeoutMs });
|
||||
return { ok: true, shellEnv: parseShellEnv(stdout) };
|
||||
const shellEnv = parseShellEnv(stdout);
|
||||
loginShellEnvProbeCache.set(cacheKey, [...shellEnv.entries()]);
|
||||
return { ok: true, shellEnv };
|
||||
} catch (err) {
|
||||
return { ok: false, error: formatErrorMessage(err) };
|
||||
}
|
||||
@@ -242,6 +303,8 @@ export function getShellPathFromLoginShell(opts: {
|
||||
export function resetShellPathCacheForTests(): void {
|
||||
cachedShellPath = undefined;
|
||||
cachedEtcShells = undefined;
|
||||
loginShellEnvProbeCache.clear();
|
||||
nextExecCacheId = 1;
|
||||
}
|
||||
|
||||
export function getShellEnvAppliedKeys(): string[] {
|
||||
|
||||
Reference in New Issue
Block a user