Front-load caching and rear-load saving to disk

This implementation saves CPU and I/O by not trying to write to the file on every set-cache.
This commit is contained in:
Mario Campos
2026-06-19 17:45:48 -05:00
parent 805b59092e
commit 895ffe7eb5
4 changed files with 40 additions and 40 deletions

13
lib/entry-points.js generated
View File

@@ -149934,9 +149934,12 @@ function readCommandCacheFile() {
}
return {};
}
function writeCommandCacheFile(data) {
function writeCommandCacheFile() {
try {
fs6.writeFileSync(getCommandCacheFilePath(), JSON.stringify(data));
fs6.writeFileSync(
getCommandCacheFilePath(),
JSON.stringify(Object.fromEntries(inMemoryCache))
);
} catch (e) {
logger.warning(`Failed to write command cache file: ${e}`);
}
@@ -149944,9 +149947,6 @@ function writeCommandCacheFile(data) {
function cacheCommandOutput(key, cmd, output) {
const entry = { cmd, output };
inMemoryCache.set(key, entry);
const data = readCommandCacheFile();
data[key] = entry;
writeCommandCacheFile(data);
}
function getCachedCommandOutput(key, cmd, validate) {
const memoized = inMemoryCache.get(key);
@@ -159013,6 +159013,8 @@ async function run3(startedAt) {
toolsVersion = initCodeQLResult.toolsVersion;
toolsSource = initCodeQLResult.toolsSource;
zstdAvailability = initCodeQLResult.zstdAvailability;
await codeql.getVersion();
await codeql.resolveLanguages();
await checkWorkflow(logger2, codeql);
if (
// Only enable the experimental features env variable for Rust analysis if the user has explicitly
@@ -159322,6 +159324,7 @@ exec ${goBinaryPath} "$@"`
await saveConfig(config, logger2);
core20.setOutput("codeql-path", config.codeQLCmd);
core20.setOutput("codeql-version", (await codeql.getVersion()).version);
writeCommandCacheFile();
} catch (unwrappedError) {
const error3 = wrapError(unwrappedError);
core20.setFailed(error3.message);

View File

@@ -170,32 +170,19 @@ test.serial(
},
);
test.serial(
"cacheCommandOutput persists the output to both the memo and the file",
async (t) => {
await withCacheDir((cacheFilePath) => {
cacheCommandOutput(CommandCacheKey.Version, "/path/to/codeql", {
hello: "world",
});
// Tier 2: the temporary file contains the entry.
const onDisk = JSON.parse(
fs.readFileSync(cacheFilePath, "utf8"),
) as Record<string, unknown>;
t.deepEqual(onDisk[CommandCacheKey.Version], {
cmd: "/path/to/codeql",
output: { hello: "world" },
});
// Tier 1: the value is served from the memo even after the file is gone.
fs.rmSync(cacheFilePath);
t.deepEqual(
getCachedCommandOutput(CommandCacheKey.Version, "/path/to/codeql"),
{ hello: "world" },
);
test.serial("cacheCommandOutput persists the output to the memo", async (t) => {
await withCacheDir(() => {
cacheCommandOutput(CommandCacheKey.Version, "/path/to/codeql", {
hello: "world",
});
},
);
// Tier 1: the value is immediately available from the memo.
t.deepEqual(
getCachedCommandOutput(CommandCacheKey.Version, "/path/to/codeql"),
{ hello: "world" },
);
});
});
test.serial(
"getCachedCommandOutput prefers the in-memory memo over the file",
@@ -207,7 +194,10 @@ test.serial(
// Overwrite the file with a different value; the memo (tier 1) should win.
writeCacheFile(cacheFilePath, {
[CommandCacheKey.Version]: { cmd: "/path/to/codeql", output: { value: 2 } },
[CommandCacheKey.Version]: {
cmd: "/path/to/codeql",
output: { value: 2 },
},
});
t.deepEqual(
getCachedCommandOutput(CommandCacheKey.Version, "/path/to/codeql"),

View File

@@ -65,20 +65,22 @@ function readCommandCacheFile(): Record<string, CommandCacheEntry> {
}
/**
* Persists the cache to the temporary file. Best-effort: a failure to write
* Persists the in-memory cache to the temporary file. Best-effort: a failure to write
* just means a later step re-runs the CLI.
*/
function writeCommandCacheFile(data: Record<string, CommandCacheEntry>): void {
export function writeCommandCacheFile(): void {
try {
fs.writeFileSync(getCommandCacheFilePath(), JSON.stringify(data));
fs.writeFileSync(
getCommandCacheFilePath(),
JSON.stringify(Object.fromEntries(inMemoryCache)),
);
} catch (e) {
logger.warning(`Failed to write command cache file: ${e}`);
}
}
/**
* Stores the output of a command under `key`, writing it to both the in-memory
* memo (tier 1) and the temporary file (tier 2).
* Stores the output of a CLI command under `key` in a module-global object.
*/
export function cacheCommandOutput(
key: CommandCacheKey,
@@ -87,10 +89,6 @@ export function cacheCommandOutput(
): void {
const entry: CommandCacheEntry = { cmd, output };
inMemoryCache.set(key, entry);
const data = readCommandCacheFile();
data[key] = entry;
writeCommandCacheFile(data);
}
/**

View File

@@ -19,6 +19,7 @@ import {
} from "./actions-util";
import { AnalysisKind, getAnalysisKinds } from "./analyses";
import { getGitHubVersion, GitHubApiCombinedDetails } from "./api-client";
import { writeCommandCacheFile } from "./cache";
import {
getDependencyCachingEnabled,
getTotalCacheSize,
@@ -323,6 +324,11 @@ async function run(startedAt: Date) {
toolsSource = initCodeQLResult.toolsSource;
zstdAvailability = initCodeQLResult.zstdAvailability;
// Populate the in-memory command cache with CLI version and languages.
// These results will be persisted to disk at the end of the init action.
await codeql.getVersion();
await codeql.resolveLanguages();
// Check the workflow for problems. If there are any problems, they are reported
// to the workflow log. No exceptions are thrown.
await checkWorkflow(logger, codeql);
@@ -766,6 +772,9 @@ async function run(startedAt: Date) {
core.setOutput("codeql-path", config.codeQLCmd);
core.setOutput("codeql-version", (await codeql.getVersion()).version);
// Persist the command cache to disk at the end of a successful init.
writeCommandCacheFile();
} catch (unwrappedError) {
const error = wrapError(unwrappedError);
core.setFailed(error.message);