Skip to content

Move the extracted JDK into the tool-cache instead of copying it - #1206

Merged
brunoborges merged 1 commit into
mainfrom
brunoborges-urban-telegram
Aug 5, 2026
Merged

Move the extracted JDK into the tool-cache instead of copying it#1206
brunoborges merged 1 commit into
mainfrom
brunoborges-urban-telegram

Conversation

@brunoborges

Copy link
Copy Markdown
Contributor

Description:

Installing a JDK writes it to disk twice. Every distribution extracts the archive into RUNNER_TEMP and then calls tc.cacheDir, which recursively copies the whole tree into RUNNER_TOOL_CACHE. For a 200-600MB JDK that is a few hundred megabytes of pure redundant I/O on every cache miss, and it is the single most expensive step in the install path - considerably more than the vendor metadata requests people usually suspect.

This PR addresses that, plus the extraction step next to it.

1. Move instead of copy (cacheJdkDir)

New helper in src/util.ts, replacing tc.cacheDir at all 15 distribution installers. The extraction directory and the tool-cache normally live on the same filesystem, so the copy can just be a fs.renameSync. The helper mirrors the destination layout tc.cacheDir produces exactly (including semver.clean(version) || version and the arch default) and writes the .complete marker itself, so tc.findAllVersions / findInToolcache keep resolving entries unchanged.

It falls back to the original tc.cacheDir copy when:

  • RUNNER_TOOL_CACHE is unset,
  • the source is not a real directory. lstat is deliberate here: renaming a symlinked source would put the link itself in the tool-cache, leaving a dangling JAVA_HOME once RUNNER_TEMP is cleaned, and the .complete marker would make it look healthy forever,
  • the rename fails. The upstream toolkit explicitly comments "do not move. move can fail on Windows due to anti-virus software having an open handle on a file", and a cross-device tool-cache raises EXDEV. A rename is atomic, so a failure leaves the source fully intact for the fallback to copy.

2. Faster extraction

extractJdkFile now hands tarball decompression to pigz when the runner provides it, and extracts Windows zips with the bundled %SystemRoot%\System32\tar.exe instead of tc.extractZip, which shells out to PowerShell's Expand-Archive (typically several times slower for a JDK-sized archive). Both paths fall back to the stock extraction, cleaning up the abandoned directory first so a failure does not double peak temp usage.

Worth a careful look:

  • The move changes a copy-children-into-dest into a rename-dest semantic. I diffed this against the real cacheDir / _createToolPath / _completeToolPath in @actions/tool-cache to confirm the resulting layout is identical, and the symlink case above is the one place they genuinely differ, which is why it falls back.
  • pigz is skipped if its resolved path contains whitespace, since tar word-splits --use-compress-program.
  • Verified end to end by running the built action against Temurin 21 and Zulu 17: the extraction directory drops to 0B (proving the move), the .complete marker and directory layout match, java -version works, and a subsequent run resolves from the tool-cache with no network.

Related issue:
N/A

Check list:

  • Ran npm run check locally (format, lint, build, test) and all checks pass.
  • Mark if documentation changes are required.
  • Mark if tests were added or updated to cover the changes.

Tests: 20 new cases in __tests__/util-install.test.ts covering the move, marker creation, replacing an existing entry, version/arch normalization parity with tc.cacheDir, and each fallback path (missing tool-cache root, symlinked source, rename failure with no stale marker left behind, pigz failure and cleanup, whitespace in the pigz path, Windows tar.exe failure and cleanup). Full suite is 1272 passing.

Two wall-clock optimizations on the JDK install path.

`tc.cacheDir` recursively copies the extracted tree into RUNNER_TOOL_CACHE,
so a 200-600MB JDK is written to disk twice. The extraction directory and
the tool-cache normally share a filesystem, so `cacheJdkDir` renames it
instead and writes the `.complete` marker itself, mirroring the destination
layout `tc.cacheDir` produces. It falls back to the copy when the tool-cache
location is unknown, when the source is not a real directory (a symlinked
source would otherwise leave a dangling entry once RUNNER_TEMP is cleaned),
or when the rename fails - a cross-device tool-cache, or anti-virus holding
a handle on Windows. The rename is atomic, so the source is still intact
for the fallback.

Extraction now uses `pigz` for tarballs when the runner provides it, and
Windows zips go through the bundled `tar.exe` rather than `tc.extractZip`,
which shells out to PowerShell's much slower `Expand-Archive`. Both fall
back to the stock extraction and clean up the abandoned directory first.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b76d8cb0-f629-46e1-bf9a-ffde06948644
@brunoborges
brunoborges requested a review from a team as a code owner August 5, 2026 03:02
Copilot AI lite review requested due to automatic review settings August 5, 2026 03:02
@brunoborges
brunoborges merged commit 885218c into main Aug 5, 2026
86 checks passed
@brunoborges
brunoborges deleted the brunoborges-urban-telegram branch August 5, 2026 03:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the JDK installation path in setup-java to reduce redundant disk I/O on cache misses by (1) moving extracted JDK directories into the GitHub Actions tool-cache instead of recursively copying them, and (2) speeding up archive extraction using faster tools when available (pigz for .tar.gz, tar.exe for Windows .zip).

Changes:

  • Added cacheJdkDir() to move (rename) extracted JDKs into RUNNER_TOOL_CACHE with a safe fallback to tc.cacheDir when move isn’t possible.
  • Updated JDK extraction logic to prefer pigz for .tar.gz and Windows’ %SystemRoot%\System32\tar.exe for .zip, with cleanup and fallbacks to existing @actions/tool-cache extraction.
  • Updated installers across distributions to use cacheJdkDir, and added a dedicated Jest test suite for the new move/extraction behaviors.
Show a summary per file
File Description
src/util.ts Introduces cacheJdkDir (move-based tool-cache) and faster extraction paths for tar.gz/zip with fallbacks.
src/distributions/*/installer.ts Switches distribution installers from tc.cacheDir to cacheJdkDir to avoid copy-based caching.
tests/util-install.test.ts Adds tests covering move semantics, .complete marker behavior, and extraction fast-path fallbacks/cleanup.
dist/setup/, dist/cleanup/ Updates compiled/bundled output to reflect the new util logic and installer wiring.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 17/34 changed files
  • Comments generated: 0
  • Review effort level: Lite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants