setup-gradle: GUH cache key collision when used in a reusable workflow
- Dominant language
- TypeScript
- Stars
- 340
- Forks
- 117
- Avg merge
- 1d 9h
- Merged PRs (30d)
- 11
Description
Repro: https://github.com/cloudshiftinc/gradle-setup-action-cache-repro/actions/runs/10067337462
That repository consists of a small "Hello World" Gradle / Kotlin project, with two build files:
1) Build with Kotlin 1.8.22;
2) Build with Kotlin 1.9.25;
There's a GHA workflow that runs two steps, sequentially, using each of the above build files.
Each step checks out the code, calls `setup-gradle` (v4 beta 1), and executes the respective Gradle build file (build.gradle.kts or build-second.gradle.kts).
The second build "cleans up" artifacts it didn't use from the first build:
```
Build cache (/home/runner/.gradle/caches/build-cache-1) removing files not accessed on or after Tue Jul 23 22:45:31 UTC 2024.
Build cache (/home/runner/.gradle/caches/build-cache-1) cleanup deleted 0 files/directories.
Build cache (/home/runner/.gradle/caches/build-cache-1) cleaned up in 0.001 secs.
groovy-dsl (/home/runner/.gradle/caches/8.9/groovy-dsl) removing files not accessed on or after Tue Jul 23 22:45:31 UTC 2024.
groovy-dsl (/home/runner/.gradle/caches/8.9/groovy-dsl) cleanup deleted 2 files/directories.
groovy-dsl (/home/runner/.gradle/caches/8.9/groovy-dsl) cleaned up in 0.01 secs.
Artifact transforms cache (/home/runner/.gradle/caches/8.9/transforms) removing files not accessed on or after Tue Jul 23 22:45:31 UTC 2024.
Artifact transforms cache (/home/runner/.gradle/caches/8.9/transforms) cleanup deleted 0 files/directories.
Artifact transforms cache (/home/runner/.gradle/caches/8.9/transforms) cleaned up in 0.027 secs.
jars (/home/runner/.gradle/caches/jars-9) cleanup deleted 0 files/directories.
jars (/home/runner/.gradle/caches/jars-9) removing files not accessed on or after Tue Jul 23 22:45:31 UTC 2024.
jars (/home/runner/.gradle/caches/jars-9) cleanup deleted 1 files/directories.
jars (/home/runner/.gradle/caches/jars-9) cleaned up in 0.003 secs.
artifact cache (/home/runner/.gradle/caches/modules-2) cleanup deleted 0 files/directories.
artifact cache (/home/runner/.gradle/caches/modules-2) [subdir: /home/runner/.gradle/caches/modules-2/resources-2.1] cleanup deleted 0 files/directories.
artifact cache (/home/runner/.gradle/caches/modules-2) [subdir: /home/runner/.gradle/caches/modules-2/resources-2.1] removing files not accessed on or after Tue Jul 23 22:45:31 UTC 2024.
artifact cache (/home/runner/.gradle/caches/modules-2) [subdir: /home/runner/.gradle/caches/modules-2/resources-2.1] cleanup deleted 0 files/directories.
artifact cache (/home/runner/.gradle/caches/modules-2) [subdir: /home/runner/.gradle/caches/modules-2/files-2.1] cleanup deleted 0 files/directories.
artifact cache (/home/runner/.gradle/caches/modules-2) [subdir: /home/runner/.gradle/caches/modules-2/files-2.1] removing files not accessed on or after Tue Jul 23 22:45:31 UTC 2024.
artifact cache (/home/runner/.gradle/caches/modules-2) [subdir: /home/runner/.gradle/caches/modules-2/files-2.1] cleanup deleted 42 files/directories.
artifact cache (/home/runner/.gradle/caches/modules-2) [subdir: /home/runner/.gradle/caches/modules-2/metadata-2.106] cleanup deleted 0 files/directories.
artifact cache (/home/runner/.gradle/caches/modules-2) cleanup deleted 0 files/directories.
artifact cache (/home/runner/.gradle/caches/modules-2) cleaned up in 0.041 secs.
```
This isn't desirable in a multi-step setup - we'd want to retain _all_ the cached entries and only cleanup at the end.
For reasons I don't currently understand the cache isn't updated after the cleanup in the second build, even though the cleanup removed entries:
```
Entry: /home/runner/.gradle/caches/modules-*/files-*/*/*/*/*
Requested Key : gradle-dependencies-v1-40c681dd67f1cd4e62478d3e197f1e6d
Restored Key : gradle-dependencies-v1-40c681dd67f1cd4e62478d3e197f1e6d
Size: 88 MB (92509727 B)
(Entry restored: exact match found)
Saved Key :
Size:
(Entry not saved: referencing 'Gradle User Home' cache entry not saved)
```
The issue starts [here](https://github.com/gradle/actions/blob/fd87365911aa12c016c307ea21313f351dc53551/sources/src/caching/cache-cleaner.ts#L17) where `setup-action` stores the current timestamp, and then in the post-build cleanup removes everything older than that. This assumes a closed-loop where it's a self-contained build, which doesn't work for shared caches (dependencies, wrappers, etc) that other builds will use/update/cleanup.
Not sure what the best solution may be - perhaps loosen the cleanup windows to "older than a day"; not thrilled about making it configurable, as one wouldn't know what to reasonably put (is 10m too long? what if the execution time varies? and so on).
If there's a way to get the "start of the workflow" timestamp that may be more deterministic, though that still leaves holes where there are different workflows running that share those caches - one may wish to save the shared caches for longer periods (e.g. workflows that use those caches that run weekly, for example).
Contributor guide
Research direction
Start with sources/src/caching/cache-cleaner.ts at the timestamp and cleanup logic linked in the report, then reproduce the behavior using the linked Gradle/Kotlin workflow. Compare the cache entries before and after both sequential builds; done means the shared cache is retained correctly and the reported cache-key collision no longer occurs.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- github-actions, kotlin, typescript
- Domain
- build-system, ci-cd
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100