Skip to content

Commit 43e771f

Browse files
committed
build(ci): fix OOM errors on macOS
...by taking physical memory into account when deciding how many test processes to spawn. Even though `macos-14` has 3 CPUs, there is only 7GB of memory. Each test process has a max heap of 2GB, with the addition of Gradle this is enough to OOM the machine. So we limit the number of test processes based on RAM Now: * 3GB reserved for Gradle (org.gradle.jvmargs=-Xmx3072M) and the OS * 4GB remaining * Heap size: 2GB * => 2 forks Fixes 21168 Assisted-by: Claude Opus 4.8 - diagnosis & some code
1 parent a7cdde5 commit 43e771f

1 file changed

Lines changed: 27 additions & 6 deletions

File tree

build.gradle.kts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ val testSummaryService = System.getenv("GITHUB_STEP_SUMMARY")?.let { path ->
4444
}
4545
}
4646

47+
/**
48+
* Per-fork JVM heap for unit tests.
49+
* See [Test.setMaxHeapSize]
50+
*/
51+
val unitTestForkMaxHeapGb = 2
52+
4753
// Here we extract per-module "best practices" settings to a single top-level evaluation
4854
subprojects {
4955
apply(plugin = "org.jlleitschuh.gradle.ktlint")
@@ -61,7 +67,7 @@ subprojects {
6167
// tell backend to avoid rollover time, and disable interval fuzzing
6268
it.environment("ANKI_TEST_MODE", "1")
6369

64-
it.maxHeapSize = "2g"
70+
it.maxHeapSize = "${unitTestForkMaxHeapGb}g"
6571
it.minHeapSize = "1g"
6672

6773
it.useJUnitPlatform()
@@ -158,6 +164,7 @@ if (jvmVersion !in jvmVersionLowerBound..jvmVersionUpperBound) {
158164
}
159165

160166
val ciBuild by extra(System.getenv("CI") == "true") // true when running on GitHub Actions
167+
val isMacOs = System.getProperty("os.name") == "Mac OS X"
161168
// allows for -Dpre-dex=false to be set
162169
val preDexEnabled by extra("true" == System.getProperty("pre-dex", "true"))
163170
// allows for universal APKs to be generated
@@ -168,12 +175,26 @@ var androidTestVariantName by extra(
168175
if (testReleaseBuild) "Release" else "Debug"
169176
)
170177

178+
private fun sysctl(key: String): Long =
179+
providers.exec {
180+
commandLine("sysctl", "-n", key)
181+
}.standardOutput.asText.get().trim().toLong()
182+
171183
val gradleTestMaxParallelForks by extra(
172-
if (System.getProperty("os.name") == "Mac OS X") {
173-
// macOS reports hardware cores. This is accurate for CI, Intel (halved due to SMT) and Apple Silicon
174-
providers.exec {
175-
commandLine("sysctl", "-n", "hw.physicalcpu")
176-
}.standardOutput.asText.get().trim().toInt()
184+
if (isMacOs) {
185+
// macOS reports hardware cores.
186+
// This is accurate for CI, Intel (halved due to SMT) and Apple Silicon
187+
val physicalCpus = sysctl("hw.physicalcpu")
188+
189+
if (ciBuild) {
190+
// #21168: The `macos-14` CI runner has only 7GB RAM and OOMs (exit 134) so bound by RAM
191+
val totalRamGb = sysctl("hw.memsize") / (1024 * 1024 * 1024)
192+
val reservedMemory = 3 // 3GB (org.gradle.jvmargs) + OS
193+
val memoryBoundForks = max(1L, (totalRamGb - reservedMemory) / unitTestForkMaxHeapGb)
194+
minOf(physicalCpus, memoryBoundForks).toInt()
195+
} else {
196+
physicalCpus.toInt()
197+
}
177198
} else if (ciBuild) {
178199
// GitHub Actions run on Standard_D4ads_v5 Azure Compute Units with 4 vCPUs
179200
// They appear to be 2:1 vCPU to CPU on Linux/Windows with two vCPU cores but with performance 1:1-similar

0 commit comments

Comments
 (0)