mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 13:26:04 -06:00
fix(git-commit): cap commit resolution cache with FIFO eviction (#108856)
* fix(git-commit): cap commit resolution cache with FIFO eviction The module-level cachedGitCommitBySearchDir Map grows without bound. A 256-entry FIFO cap prevents unbounded memory growth from repeated lookups across distinct search directories. * fix(git-commit): use shared LRU cache pruning --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -288,6 +288,45 @@ describe("git commit resolution", () => {
|
||||
expect(readGitCommit.mock.calls.length).toBe(firstCallReads);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ name: "successful", result: "abcdef0" },
|
||||
{ name: "failed", result: null },
|
||||
])("bounds $name git probe entries with LRU eviction", async ({ result }) => {
|
||||
const temp = await makeTempDir(`git-commit-${result ? "success" : "failure"}-lru`);
|
||||
const coldDir = path.join(temp, "cold");
|
||||
const hotDir = path.join(temp, "hot");
|
||||
const newestDir = path.join(temp, "newest");
|
||||
const readGitCommit = vi.fn((_searchDir: string, _packageRoot: string | null) => result);
|
||||
const resolve = (cwd: string) =>
|
||||
resolveCommitHash({
|
||||
cwd,
|
||||
env: {},
|
||||
readers: {
|
||||
readGitCommit,
|
||||
readBuildInfoCommit: () => null,
|
||||
readPackageJsonCommit: () => null,
|
||||
},
|
||||
});
|
||||
const callsFor = (cwd: string) =>
|
||||
readGitCommit.mock.calls.filter(([searchDir]) => searchDir === cwd).length;
|
||||
|
||||
expect(resolve(coldDir)).toBe(result);
|
||||
expect(resolve(hotDir)).toBe(result);
|
||||
for (let index = 0; index < 254; index += 1) {
|
||||
expect(resolve(path.join(temp, `filler-${index}`))).toBe(result);
|
||||
}
|
||||
|
||||
expect(resolve(hotDir)).toBe(result);
|
||||
expect(resolve(newestDir)).toBe(result);
|
||||
expect(resolve(newestDir)).toBe(result);
|
||||
expect(resolve(hotDir)).toBe(result);
|
||||
expect(resolve(coldDir)).toBe(result);
|
||||
|
||||
expect(callsFor(newestDir)).toBe(1);
|
||||
expect(callsFor(hotDir)).toBe(1);
|
||||
expect(callsFor(coldDir)).toBe(2);
|
||||
});
|
||||
|
||||
it("formats env-provided commit strings consistently", async () => {
|
||||
const temp = await makeTempDir("git-commit-env");
|
||||
expect(resolveCommitHash({ cwd: temp, env: { GIT_COMMIT: "ABCDEF0123456789" } })).toBe(
|
||||
|
||||
@@ -5,6 +5,7 @@ import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
|
||||
import { resolveGitHeadPath } from "./git-root.js";
|
||||
import { pruneMapToMaxSize } from "./map-size.js";
|
||||
import { resolveOpenClawPackageRootSync } from "./openclaw-root.js";
|
||||
|
||||
const formatCommit = (value?: string | null) => {
|
||||
@@ -23,6 +24,7 @@ const formatCommit = (value?: string | null) => {
|
||||
};
|
||||
|
||||
const cachedGitCommitBySearchDir = new Map<string, string | null>();
|
||||
const GIT_COMMIT_CACHE_LIMIT = 256;
|
||||
|
||||
type CommitMetadataReaders = {
|
||||
readGitCommit?: (searchDir: string, packageRoot: string | null) => string | null | undefined;
|
||||
@@ -66,6 +68,7 @@ const safeReadFilePrefix = (filePath: string, limit = 256) => {
|
||||
|
||||
const cacheGitCommit = (searchDir: string, commit: string | null) => {
|
||||
cachedGitCommitBySearchDir.set(searchDir, commit);
|
||||
pruneMapToMaxSize(cachedGitCommitBySearchDir, GIT_COMMIT_CACHE_LIMIT);
|
||||
return commit;
|
||||
};
|
||||
const resolveGitLookupDepth = (searchDir: string, packageRoot: string | null) => {
|
||||
@@ -224,7 +227,12 @@ export const resolveCommitHash = (
|
||||
}
|
||||
const searchDir = resolveCommitSearchDir(options);
|
||||
if (cachedGitCommitBySearchDir.has(searchDir)) {
|
||||
return cachedGitCommitBySearchDir.get(searchDir) ?? null;
|
||||
const cached = cachedGitCommitBySearchDir.get(searchDir) ?? null;
|
||||
// Git discovery reads multiple files; keep active directories ahead of cold entries when
|
||||
// the shared insertion-order pruning helper enforces the bound.
|
||||
cachedGitCommitBySearchDir.delete(searchDir);
|
||||
cachedGitCommitBySearchDir.set(searchDir, cached);
|
||||
return cached;
|
||||
}
|
||||
const packageRoot = resolveOpenClawPackageRootSync({
|
||||
cwd: options.cwd,
|
||||
|
||||
Reference in New Issue
Block a user