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

Reply via email to