This is an automated email from the ASF dual-hosted git repository. jamesfredley pushed a commit to branch build/ci-daemon-heap-vs-runner-ram in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit cc7e0c3353f9b541880438d442a43da149aae81e Author: James Fredley <[email protected]> AuthorDate: Tue Aug 18 16:25:33 2026 -0400 build(ci): bound concurrent JVMs on the macOS runner org.gradle.jvmargs sizes the Gradle daemon only. Test forks are separate child JVMs that take their heap from maxHeapSize in gradle/test-config.gradle, so a job's configured heap is the daemon -Xmx plus the concurrent test forks times the per-fork heap. Those two numbers live in different files and have never been reasoned about together. The concurrent fork count is not maxParallelForks. With org.gradle.parallel=true several Test tasks run at once, so the live JVM count is bounded by Gradle's global worker pool, which defaults to the CPU count. On the 4-CPU, ~16 GB Linux and Windows runners that floor is 5G + 4x768m = 8G and fits. On the 3-CPU, ~7 GB macOS runner it is 5G + 3x768m = 7.25G and does not. Cap --max-workers on the macOS leg, since that is what actually limits concurrent test and compiler JVMs, and keep maxTestParallel alongside it so no single task exceeds the same cap. Both are passed through a new runner_arguments matrix key that is undefined, and therefore empty, for every other entry. The daemon stays at 5 GB: groovydoc is what needs it, and shrinking it would trade a memory problem for a slower build. Document the arithmetic next to org.gradle.jvmargs as a simplified configured-heap floor, explicitly excluding metaspace, native memory and the forked compiler workers that CompilePlugin gives their own -Xmx2G, so it is not mistaken for a true peak. This changes concurrency only. No test is added, removed, skipped or weakened. Assisted-by: claude-code:claude-opus-5 --- .github/workflows/gradle.yml | 11 +++++++++++ gradle.properties | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 39f808c509..9d6f57fa36 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -142,6 +142,16 @@ jobs: java: 21 job_name: macOS JDK 21 gradle_task: 'build :grails-shell-cli:installDist groovydoc' + # The macOS runner has ~7 GB of RAM and 3 CPUs, against ~16 GB and 4 CPUs on the + # Linux and Windows runners, while org.gradle.jvmargs still asks for a 5 GB daemon + # (groovydoc needs it). Capping only maxTestParallel would not help: that is a + # per-Test-task limit, and with org.gradle.parallel=true several projects' test + # tasks run at once, so the number of live forks is bounded by Gradle's global + # worker pool - which defaults to the 3 CPUs here. --max-workers is therefore the + # setting that actually limits concurrent test and compiler JVMs; maxTestParallel + # is kept alongside it so no single task exceeds that cap either. This reduces + # memory pressure on the smallest runner rather than proving the job fits. + runner_arguments: '--max-workers=2 -PmaxTestParallel=2' cache_writer: true - os: windows-latest java: 25 @@ -204,6 +214,7 @@ jobs: -PonlyCoreTests -PskipCodeStyle ${{ matrix.shard_arguments }} + ${{ matrix.runner_arguments }} - name: "🗄️ Save dependency jar cache" if: ${{ success() && matrix.cache_writer && steps.dependency-cache.outputs.cache-hit != 'true' }} uses: actions/cache/save@v4 diff --git a/gradle.properties b/gradle.properties index b49b31f28f..e7e55b6e02 100644 --- a/gradle.properties +++ b/gradle.properties @@ -88,4 +88,23 @@ org.gradle.daemon=true #org.gradle.configureondemand=true # Note: groovydoc requires almost a doubling of this memory; if it could run in a process isolation, we could reduce this # This is a future TODO see groovydoc-tool-rewrite branch for experiementations with this +# +# This -Xmx sizes the Gradle DAEMON only. Test forks are separate child JVMs and get their +# own heap from maxHeapSize in gradle/test-config.gradle (768m on CI, 1024m locally), so a +# rough lower bound on a job's configured heap is: +# +# daemon -Xmx + (concurrent test forks x per-fork maxHeapSize) +# +# "Concurrent test forks" is NOT maxParallelForks. With org.gradle.parallel=true several +# Test tasks run at once, so the live fork count is bounded by Gradle's global worker pool +# (--max-workers, defaulting to the CPU count). +# +# This is a simplified CONFIGURED-HEAP budget, not a true peak: it excludes metaspace and +# other native memory, the Gradle client, and forked Java/Groovy compiler workers (which +# CompilePlugin gives their own -Xmx2G). Treat it as a floor when sizing a runner. +# +# On the 4-CPU / ~16 GB Linux and Windows runners that floor is 5G + 4x768m = 8G, which +# fits. On the 3-CPU / ~7 GB macOS runner it is 5G + 3x768m = 7.25G, which does not - so +# .github/workflows/gradle.yml caps --max-workers there to reduce memory pressure, rather +# than shrinking this daemon and slowing groovydoc. org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx5G
