From ed04c373b81cb7e848ccbe5c97046d4eb0681ba8 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 13 Jul 2026 15:56:51 +0000 Subject: [PATCH 1/2] fix(ci): serialize :amethyst Kotlin compilations to stop daemon OOMs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI runs die with java.lang.OutOfMemoryError (GC overhead limit exceeded / Java heap space) inside the shared Kotlin compile daemon when two variant compilations of :amethyst run at the same time — e.g. compilePlayDebugKotlin and compilePlayBenchmarkKotlin in the test-and-build-android job, which demands four :amethyst variants (unit tests, lint, assembleBenchmark) in one invocation. KotlinCompile submits its work asynchronously via the Worker API, so the project lock is released while compilation is still running in the daemon and Gradle happily starts the next variant's compile task in parallel; two full :amethyst codegen passes no longer fit the daemon's -Xmx8g heap (see runs 29260711359 and 29248908870; removing Kover in #3539 helped but was not sufficient). Register a no-op shared build service with maxParallelUsages = 1 and make every :amethyst KotlinCompile task use it. The scheduler then runs this module's Kotlin compilations one at a time — a task holding the service is not complete until its async worker finishes — while all other work (other modules' compilation, JVM tests, lint analysis, packaging) keeps running in parallel. Verified with Worker-API probe tasks: unconstrained same-project tasks start simultaneously; with usesService they run strictly one after the other. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018ifbUGCADckUb3zaox9wBo --- amethyst/build.gradle.kts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/amethyst/build.gradle.kts b/amethyst/build.gradle.kts index e4df9eb8bf..730f044c4e 100644 --- a/amethyst/build.gradle.kts +++ b/amethyst/build.gradle.kts @@ -1,4 +1,7 @@ +import org.gradle.api.services.BuildService +import org.gradle.api.services.BuildServiceParameters import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { alias(libs.plugins.androidApplication) @@ -342,6 +345,25 @@ kotlin { } } +// Gradle schedules Kotlin compilations of different variants of this module +// concurrently (e.g. playDebug + playBenchmark when CI runs unit tests, lint, +// and assembleBenchmark in one invocation), but they all share a single Kotlin +// daemon whose heap (kotlin.daemon.jvmargs) cannot fit two full :amethyst +// codegen passes — CI runs died with "GC overhead limit exceeded" inside the +// daemon. This no-op shared build service with maxParallelUsages = 1 tells the +// scheduler to run this module's Kotlin compile tasks one at a time; other +// projects' tasks (JVM tests, lint analysis, packaging) still run in parallel. +abstract class AmethystKotlinCompileLimiter : BuildService + +val kotlinCompileLimiter = + gradle.sharedServices.registerIfAbsent("amethystKotlinCompileLimiter", AmethystKotlinCompileLimiter::class) { + maxParallelUsages.set(1) + } + +tasks.withType().configureEach { + usesService(kotlinCompileLimiter) +} + composeCompiler { reportsDestination = layout.buildDirectory.dir("compose_compiler") metricsDestination = layout.buildDirectory.dir("compose_compiler") From 899ada61e47b5377c4bf896e2b72c2f10555546b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 13 Jul 2026 18:00:28 +0000 Subject: [PATCH 2/2] fix(ci): apply the :amethyst Kotlin compile limiter only on CI The daemon OOM needs a cache-cold compile of several :amethyst variants in one invocation, which only CI's combined test/lint/assembleBenchmark run produces; local builds are incremental and usually build a single variant, and have never shown the problem. Gate the maxParallelUsages=1 service on the CI env var (always set on GitHub Actions) so local multi-variant builds keep their parallelism. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018ifbUGCADckUb3zaox9wBo --- amethyst/build.gradle.kts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/amethyst/build.gradle.kts b/amethyst/build.gradle.kts index 730f044c4e..63cf319a46 100644 --- a/amethyst/build.gradle.kts +++ b/amethyst/build.gradle.kts @@ -353,15 +353,20 @@ kotlin { // daemon. This no-op shared build service with maxParallelUsages = 1 tells the // scheduler to run this module's Kotlin compile tasks one at a time; other // projects' tasks (JVM tests, lint analysis, packaging) still run in parallel. +// +// CI-only: the OOM needs a cache-cold compile of several variants at once, +// which local builds (incremental, usually one variant) don't produce. abstract class AmethystKotlinCompileLimiter : BuildService -val kotlinCompileLimiter = - gradle.sharedServices.registerIfAbsent("amethystKotlinCompileLimiter", AmethystKotlinCompileLimiter::class) { - maxParallelUsages.set(1) - } +if (System.getenv("CI") != null) { + val kotlinCompileLimiter = + gradle.sharedServices.registerIfAbsent("amethystKotlinCompileLimiter", AmethystKotlinCompileLimiter::class) { + maxParallelUsages.set(1) + } -tasks.withType().configureEach { - usesService(kotlinCompileLimiter) + tasks.withType().configureEach { + usesService(kotlinCompileLimiter) + } } composeCompiler {