From 48bb8d37e345d400a70fb32683e4e704c936f76c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 23:33:23 +0000 Subject: [PATCH] ci: cut the Android job's worker concurrency, not more heap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test-and-build-android died again with "the runner has received a shutdown signal" — the OOM killer taking the runner agent, about 20 minutes in, with every task up to that point green. No test failed and nothing failed to compile. The previous fix is not wrong, it was validated on the easy case. ea459bc5 changed only this file, so the run restored main's Gradle cache and rebuilt almost nothing; it passed in 54 minutes and I read that as confirmation. The first PR run since that touches `commons` invalidated the cache — this job's own comment above notes a quartz/commons change does exactly that and costs 2-3x — rebuilt from cold, and died the same way. Cold is the case the 4g cap had never actually faced. So the lever is concurrency rather than ceilings. Those heap numbers are per-JVM limits; what tips a 16GB runner over is how many heavy JVMs are resident at once, and Gradle defaults max-workers to the core count with org.gradle.parallel on. The log shows the peak: R8 and both lintAnalyze tasks behind it, a cold compileFdroidDebugKotlin, and a forked test JVM starting, alongside the 6g Gradle and 4g Kotlin daemons. --max-workers=3, not 2. This job is mostly a chain of single-task module compiles, so it loses little real parallelism, and halving it risks the timeout on a cold run instead — the cap goes to 120 for that reason, which costs nothing on runs that finish early. Dropping the Kotlin daemon further was the obvious alternative and is the wrong one: cdb4f1f7 already lost that trade, starving R8 and lintAnalyze into "java.lang.OutOfMemoryError: Java heap space". If this still dies early, the job is simply too big for one 16GB runner and the answer is splitting lint out into its own job — not another number. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01AXvKXakvup4inNFfAhhr4L --- .github/workflows/build.yml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1a123e7e16..72b2548016 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -347,7 +347,7 @@ jobs: # workflow, which puts this one past the cap and gets it killed mid-step # with no test report. Raising the cap costs nothing on runs that finish # early — `timeout-minutes` bounds a job, it does not reserve the time. - timeout-minutes: 90 + timeout-minutes: 120 steps: - name: Checkout code uses: actions/checkout@v7 @@ -396,10 +396,31 @@ jobs: # # Overridden here rather than in gradle.properties so local builds on # bigger machines keep the headroom. + # + # `--max-workers` is the second half, and it is about concurrency rather + # than ceilings. The heap numbers above are per-JVM limits; what actually + # tips a 16GB runner over is how many heavy JVMs are live at once. Gradle + # defaults max-workers to the core count (4 on ubuntu-latest) and + # org.gradle.parallel is on, so a cold run can have several kotlinc + # workers, a lint fork and a forked test JVM resident alongside the two + # daemons. + # + # Cold is the case that matters. The 4g cap was first validated on a run + # that only changed this file, so it restored main's Gradle cache and + # built almost nothing; the next PR run that touched `commons` + # invalidated that cache, rebuilt from cold, and died the same way at + # ~20 minutes. Fewer workers is what makes the cold path fit — dropping + # heap further would start starving R8 again, which is the trade the + # previous attempt already lost. + # + # 3 rather than 2: this job is mostly a chain of single-task module + # compiles, so the parallelism it loses is small, and halving it risks + # the timeout on a cold run. The cap goes to 120 for the same reason. - name: Test + Build Android (gradle) run: | ./gradlew \ -Dkotlin.daemon.jvmargs="-Xmx4g -XX:MaxMetaspaceSize=1g" \ + --max-workers=3 \ :amethyst:lintFdroidBenchmark \ :amethyst:lintPlayBenchmark \ :quartz:jvmTest \