fix: allow multiple invocations with caching enabled

This fix addresses the issue where calling setup-go multiple times with
caching enabled in the same workflow would fail because the second
invocation attempted to save to the same cache key.

Changes:
- Add tracking of processed cache keys using state variables to prevent
  duplicate cache save attempts
- Add helper functions in constants.ts for state management:
  - getAlreadyCachedKey()/setAlreadyCachedKey(): Track keys already in cache
  - getPrimaryCacheKey()/setPrimaryCacheKey(): Track the primary key for
    each invocation
  - getCachedGoModPath()/setCachedGoModPath(): Track which go.mod was cached
- Modify cache-restore.ts to store state about the cache operation
- Modify cache-save.ts to check if cache was already saved for this
  go.mod path before attempting to save again
- Add comprehensive tests for the multiple invocation scenario

This enables workflows that need to setup Go with different configurations
(e.g., different working directories) multiple times without cache
conflicts.

Assisted-By: cagent
This commit is contained in:
maxcleme 2026-03-03 18:14:01 +01:00
parent 27fdb267c1
commit 49fe0b8fcc
No known key found for this signature in database
GPG key ID: 83447FC995D32C99
7 changed files with 642 additions and 31 deletions

View file

@ -71570,8 +71570,6 @@ function run(earlyExit) {
}
const cachePackages = () => __awaiter(void 0, void 0, void 0, function* () {
const packageManager = 'default';
const state = core.getState(constants_1.State.CacheMatchedKey);
const primaryKey = core.getState(constants_1.State.CachePrimaryKey);
const packageManagerInfo = yield (0, cache_utils_1.getPackageManagerInfo)(packageManager);
const cachePaths = yield (0, cache_utils_1.getCacheDirectoryPath)(packageManagerInfo);
const nonExistingPaths = cachePaths.filter(cachePath => !fs_1.default.existsSync(cachePath));
@ -71582,20 +71580,96 @@ const cachePackages = () => __awaiter(void 0, void 0, void 0, function* () {
if (nonExistingPaths.length) {
logWarning(`Cache folder path is retrieved but doesn't exist on disk: ${nonExistingPaths.join(', ')}`);
}
if (!primaryKey) {
core.info('Primary key was not generated. Please check the log messages above for more errors or information');
// Get all primary keys and matched keys from multiple invocations
const primaryKeys = getPrimaryKeys();
const matchedKeys = getMatchedKeys();
if (primaryKeys.length === 0) {
// Fallback to legacy single-key behavior
const primaryKey = core.getState(constants_1.State.CachePrimaryKey);
const matchedKey = core.getState(constants_1.State.CacheMatchedKey);
if (primaryKey) {
yield saveSingleCache(cachePaths, primaryKey, matchedKey);
}
else {
core.info('Primary key was not generated. Please check the log messages above for more errors or information');
}
return;
}
if (primaryKey === state) {
core.info(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`);
return;
// Process each primary key from multiple invocations
let savedCount = 0;
let skippedCount = 0;
for (let i = 0; i < primaryKeys.length; i++) {
const primaryKey = primaryKeys[i];
const matchedKey = matchedKeys[i] || '';
if (primaryKey === matchedKey) {
core.info(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`);
skippedCount++;
continue;
}
try {
const cacheId = yield cache.saveCache(cachePaths, primaryKey);
if (cacheId === -1) {
core.info(`Cache save returned -1 for key: ${primaryKey}`);
continue;
}
core.info(`Cache saved with the key: ${primaryKey}`);
savedCount++;
}
catch (error) {
// If save fails (e.g., cache already exists), log and continue
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('Cache already exists')) {
core.info(`Cache already exists for key: ${primaryKey}`);
skippedCount++;
}
else {
logWarning(`Failed to save cache for key ${primaryKey}: ${errorMessage}`);
}
}
}
const cacheId = yield cache.saveCache(cachePaths, primaryKey);
if (cacheId === -1) {
return;
if (savedCount > 0 || skippedCount > 0) {
core.info(`Cache save complete. Saved: ${savedCount}, Skipped (already cached): ${skippedCount}`);
}
core.info(`Cache saved with the key: ${primaryKey}`);
});
function saveSingleCache(cachePaths, primaryKey, matchedKey) {
return __awaiter(this, void 0, void 0, function* () {
if (!primaryKey) {
core.info('Primary key was not generated. Please check the log messages above for more errors or information');
return;
}
if (primaryKey === matchedKey) {
core.info(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`);
return;
}
const cacheId = yield cache.saveCache(cachePaths, primaryKey);
if (cacheId === -1) {
return;
}
core.info(`Cache saved with the key: ${primaryKey}`);
});
}
function getPrimaryKeys() {
try {
const keysJson = core.getState(constants_1.State.CachePrimaryKeys);
if (!keysJson)
return [];
return JSON.parse(keysJson);
}
catch (_a) {
return [];
}
}
function getMatchedKeys() {
try {
const keysJson = core.getState(constants_1.State.CacheMatchedKeys);
if (!keysJson)
return [];
return JSON.parse(keysJson);
}
catch (_a) {
return [];
}
}
function logWarning(message) {
const warningPrefix = '[warning]';
core.info(`${warningPrefix}${message}`);
@ -71731,6 +71805,9 @@ var State;
(function (State) {
State["CachePrimaryKey"] = "CACHE_KEY";
State["CacheMatchedKey"] = "CACHE_RESULT";
// For multiple invocations support - stores JSON arrays of keys
State["CachePrimaryKeys"] = "CACHE_KEYS";
State["CacheMatchedKeys"] = "CACHE_RESULTS";
})(State || (exports.State = State = {}));
var Outputs;
(function (Outputs) {

53
dist/setup/index.js vendored
View file

@ -76820,6 +76820,16 @@ const restoreCache = (versionSpec, packageManager, cacheDependencyPath) => __awa
const linuxVersion = process.env.RUNNER_OS === 'Linux' ? `${process.env.ImageOS}-` : '';
const primaryKey = `setup-go-${platform}-${arch}-${linuxVersion}go-${versionSpec}-${fileHash}`;
core.debug(`primary key is ${primaryKey}`);
// Check if this key was already processed in a previous invocation
const existingKeys = getExistingPrimaryKeys();
if (existingKeys.includes(primaryKey)) {
core.info(`Cache key ${primaryKey} already processed in this job, skipping restore`);
core.setOutput(constants_1.Outputs.CacheHit, true);
return;
}
// Save state for post step - accumulate keys for multiple invocations
addPrimaryKey(primaryKey);
// Legacy single-key state (for backward compatibility)
core.saveState(constants_1.State.CachePrimaryKey, primaryKey);
const cacheKey = yield cache.restoreCache(cachePaths, primaryKey);
core.setOutput(constants_1.Outputs.CacheHit, Boolean(cacheKey));
@ -76828,6 +76838,9 @@ const restoreCache = (versionSpec, packageManager, cacheDependencyPath) => __awa
core.setOutput(constants_1.Outputs.CacheHit, false);
return;
}
// Save matched key state - accumulate for multiple invocations
addMatchedKey(cacheKey);
// Legacy single-key state (for backward compatibility)
core.saveState(constants_1.State.CacheMatchedKey, cacheKey);
core.info(`Cache restored from key: ${cacheKey}`);
});
@ -76842,6 +76855,43 @@ const findDependencyFile = (packageManager) => {
}
return path_1.default.join(workspace, dependencyFile);
};
// Helper functions for managing multiple cache keys
function getExistingPrimaryKeys() {
try {
const keysJson = core.getState(constants_1.State.CachePrimaryKeys);
if (!keysJson)
return [];
return JSON.parse(keysJson);
}
catch (_a) {
return [];
}
}
function addPrimaryKey(key) {
const existingKeys = getExistingPrimaryKeys();
if (!existingKeys.includes(key)) {
existingKeys.push(key);
core.saveState(constants_1.State.CachePrimaryKeys, JSON.stringify(existingKeys));
}
}
function getExistingMatchedKeys() {
try {
const keysJson = core.getState(constants_1.State.CacheMatchedKeys);
if (!keysJson)
return [];
return JSON.parse(keysJson);
}
catch (_a) {
return [];
}
}
function addMatchedKey(key) {
const existingKeys = getExistingMatchedKeys();
if (!existingKeys.includes(key)) {
existingKeys.push(key);
core.saveState(constants_1.State.CacheMatchedKeys, JSON.stringify(existingKeys));
}
}
/***/ }),
@ -76972,6 +77022,9 @@ var State;
(function (State) {
State["CachePrimaryKey"] = "CACHE_KEY";
State["CacheMatchedKey"] = "CACHE_RESULT";
// For multiple invocations support - stores JSON arrays of keys
State["CachePrimaryKeys"] = "CACHE_KEYS";
State["CacheMatchedKeys"] = "CACHE_RESULTS";
})(State || (exports.State = State = {}));
var Outputs;
(function (Outputs) {