Cache resolved JDK releases to remove the vendor API from warm jobs - #1208
Merged
Conversation
Only Temurin is preinstalled in the runner tool cache, so for every other distribution `findInToolcache()` misses on essentially every job. That forces a call to the distribution's metadata API before the JDK cache key can even be computed, which makes the vendor a hard per-job dependency even when the JDK bytes are already cached, and turns a vendor 403, 429, or outage into a job failure. Store the resolved release in a small companion cache entry keyed only on inputs known before any network call: runner OS, architecture, distribution, package type, requested version, and stability. A job that finds a current entry installs the JDK without contacting the metadata API at all. `@actions/cache` derives a cache version by hashing the requested paths, so save and restore paths must match. The entry therefore uses a path that excludes the date bucket while the key includes it, which lets restore keys fall back to an older bucket. An entry older than the current day is not used directly: the metadata API is still queried so floating requests such as `java-version: 21` keep picking up new releases, and the older entry is used only when that query fails. Because the entry also carries the download URL and checksum, that fallback works even when the JDK itself is not cached. Releases whose URL is not content-addressed are never stored. Oracle JDK and Oracle GraalVM build a `/latest/` URL for a major-only version, and its bytes change when a new build is published, so the URL and checksum are only consistent at the moment they are resolved. Mark those releases floating and skip recording them. Restored payloads are validated as untrusted input, and the post-job save rewrites the payload the key was computed for rather than uploading whatever is on disk, since a restore in a later step targets the same path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b76d8cb0-f629-46e1-bf9a-ffde06948644
Contributor
There was a problem hiding this comment.
Pull request overview
This pull request adds a companion cache for “resolved JDK release metadata” so setup-java can avoid calling vendor metadata APIs on warm runs (when JDK caching is enabled), and can fall back to a previously-resolved release during vendor outages/rate limiting.
Changes:
- Introduces a new
jdk-resolution-cachemodule to restore/register/save resolvedJavaDownloadReleasepayloads keyed by inputs available pre-network. - Wires resolution-cache restore/register into
JavaBaseresolution flow, and saves the companion cache in the post-job cleanup step. - Marks vendor
/latest/style releases asfloating(Oracle JDK, Oracle GraalVM) so they are never stored as reusable cached resolutions; updates docs and tests accordingly.
Show a summary per file
| File | Description |
|---|---|
| src/jdk-resolution-cache.ts | New module implementing restore/register/save of resolved JDK release metadata with validation and date-bucket logic. |
| src/distributions/oracle/installer.ts | Marks major-only /latest/ Oracle JDK URL resolutions as floating. |
| src/distributions/graalvm/installer.ts | Marks GraalVM /latest/ URL resolutions as floating for major-only requests. |
| src/distributions/base-models.ts | Extends JavaDownloadRelease with optional floating flag. |
| src/distributions/base-installer.ts | Adds resolveJavaRelease() to prefer resolution-cache, register non-floating resolutions, and fall back to stale cached resolution on vendor API failure. |
| src/cleanup-java.ts | Adds post-job save of JDK resolution caches when cache-jdk is enabled. |
| docs/advanced-usage.md | Documents “Caching release resolution” behavior, gating, validation, and floating-release exclusions. |
| tests/jdk-resolution-cache.test.ts | Adds unit tests for restore/register/save behavior and payload validation. |
| tests/distributors/oracle-installer.test.ts | Verifies floating is set only for Oracle /latest/ URLs. |
| tests/distributors/graalvm-installer.test.ts | Updates expected releases to include floating and validates /latest/ behavior. |
| tests/distributors/base-installer.test.ts | Adds coverage for resolution-cache integration in JavaBase.setupJava(). |
| dist/setup/968.index.js | Compiled output updates for GraalVM floating flag. |
| dist/setup/348.index.js | Compiled output for new resolution-cache chunk (setup side). |
| dist/setup/242.index.js | Compiled output updates for JavaBase.resolveJavaRelease() integration. |
| dist/setup/182.index.js | Compiled output updates for Oracle floating flag. |
| dist/cleanup/index.js | Compiled output updates to save resolution cache in cleanup step. |
| dist/cleanup/348.index.js | Compiled output for new resolution-cache chunk (cleanup side). |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 11/17 changed files
- Comments generated: 1
- Review effort level: Lite
A daily window gives no benefit to the repositories that need it most. A repository whose workflows run once a day would re-resolve on every job, and one running weekly would never see a current entry at all, yet those are exactly the repositories with nothing warm in the tool cache. Seven days is also the ceiling. GitHub removes cache entries that have not been accessed for seven days, so a longer window would leave the previous entry evicted by the time the window rolls over, removing the stale fallback at the moment it is most likely to be needed. It comfortably covers JDK release cadence, which is monthly at its fastest and usually quarterly. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b76d8cb0-f629-46e1-bf9a-ffde06948644
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
The pre-commit hook runs `eslint --fix` after `npm run check` has already built `dist/`, so the fix it applied to the resolution fallback warning in `base-installer.ts` never reached the bundle. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b76d8cb0-f629-46e1-bf9a-ffde06948644
The autofix accepted on the pull request edited the resolution fallback warning in `base-installer.ts` through the GitHub UI, which does not run `npm run build`, so `dist/` still carried the pre-fix bundle. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b76d8cb0-f629-46e1-bf9a-ffde06948644
…ges-urban-telegram
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description:
Only Temurin is preinstalled in the runner tool cache. For every other distribution (Zulu, Corretto, Liberica, SapMachine, Microsoft, Semeru, ...)
findInToolcache()misses on essentially every job, so setup-java must call the distribution's metadata API to learn which release satisfiesjava-versionbefore it can even compute the JDK cache key. That makes the vendor a hard per-job dependency even when the JDK bytes are already in the Actions cache, and turns a vendor 403, 429, or outage into a job failure.This stores the resolved release itself in a small companion cache entry, keyed only on inputs known before any network call: runner OS, architecture, distribution, package type, requested version, and stability. A job that finds a current entry installs the JDK without contacting the metadata API at all.
Why the key and path are shaped this way
@actions/cachederives a cache version by hashing the requestedpaths(getCacheVersionincacheUtils), andgetCacheEntry(keys, paths)matches on it, so save and restore paths must be identical. That rules out any scheme keyed on the resolved version, since the path would have to contain a version we do not know yet. Instead the entry uses a path that excludes the freshness window while the key includes it:An entry from an earlier window is deliberately not used directly. setup-java still queries the metadata API, so a floating request such as
java-version: 21keeps picking up new releases; the older entry is used only when that query fails, which is what keeps a job working through an outage or rate limit. Because the entry also carries the download URL and checksum, that fallback works even when the JDK itself is not cached and still has to be downloaded. The fallback is reported with a warning.The window is seven days. GitHub removes cache entries that have not been accessed for seven days, so anything longer would leave the previous entry evicted by the time the window rolls over, removing the fallback exactly when it is most likely to be needed. Anything shorter penalizes low-frequency repositories, which are the ones least likely to have a warm tool cache: a daily window would give no benefit at all to a workflow that runs weekly. Seven days also comfortably covers JDK release cadence, which is monthly at its fastest and usually quarterly.
Things worth a careful look
/latest/URL whenjava-versionnames only a major version, and the bytes behind it change whenever a new build is published, so its URL and its.sha256sibling are only consistent with each other at the moment they are resolved. Caching that pair could produce a checksum mismatch that looks like tampering. Those releases are markedfloatingand skipped. A more specific version resolves an archived URL and is stored normally.check-latest: true,java-version: latest, andforce-download: true, and it is coupled tocache-jdkso nothing is written unless the user opted into caching.Every cache interaction degrades to today's behavior on failure.
Related issue:
N/A
Check list:
npm run checklocally (format, lint, build, test) and all checks pass.