This is an automated email from the ASF dual-hosted git repository.
HyukjinKwon pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/master by this push:
new 5ae5aa59a82b [SPARK-57952][CORE][TEST] Retry retryOnOOM a few times to
reduce SorterSuite flaky OOM
5ae5aa59a82b is described below
commit 5ae5aa59a82b4d0a75b0412a32b0204a6ea2508f
Author: Hyukjin Kwon <[email protected]>
AuthorDate: Mon Jul 6 17:27:37 2026 +0900
[SPARK-57952][CORE][TEST] Retry retryOnOOM a few times to reduce
SorterSuite flaky OOM
### What changes were proposed in this pull request?
Make the test helper `retryOnOOM` (in `SparkFunSuite`) retry a few times
(default 3) with
a full `runGC()` between attempts, instead of running GC and retrying only
once. The sole
caller (`SorterSuite`) is updated to the new `retryOnOOM()(body)` form.
### Why are the changes needed?
The `core` module test job intermittently aborts with an OOM (observed ~1
in 5 runs, e.g.
in the branch-4.x Maven build):
```
*** RUN ABORTED ***
An exception or error caused a run to abort: Java heap space
java.lang.OutOfMemoryError: Java heap space
at
org.apache.spark.util.collection.SorterSuite.$anonfun$new$12(SorterSuite.scala:145)
at org.apache.spark.SparkFunSuite.retryOnOOM(SparkFunSuite.scala:122)
```
`SorterSuite` allocates a single ~1 GB `byte[]` (heap is `-Xmx4g`).
SPARK-57037 already
wrapped that allocation in `retryOnOOM`, which does one `System.gc()` + one
retry. But a
single GC after the first failure does not always reclaim the previous
test's large array
in time, so the lone retry allocates against a still-pressured heap and
OOMs again.
Because the failure is intermittent (the job passes ~4 of 5 runs), 4 GB is
not fundamentally
too small — this is transient heap pressure, exactly the case where
additional GC+retry
attempts help. Retrying up to 3 times (with `runGC()`, which waits for a
weak reference to
clear, between attempts) gives the JVM more chances to reclaim memory.
This is a test flakiness reduction, not a correctness change: a genuinely
too-large or
leaked allocation still fails once the attempts are exhausted, so real OOMs
are not masked.
### Does this PR introduce _any_ user-facing change?
No. Test-only change.
### How was this patch tested?
`SorterSuite` (which exercises `retryOnOOM`) on a fork via GitHub Actions;
confirmed the
signature change compiles and the suite passes.
**CI evidence**
- Failing run (apache/spark, branch-4.x `build_maven`, `core` module —
SorterSuite OOM):
https://github.com/apache/spark/actions/runs/28747189185
- Passing validation (fork, compiles `core` + runs `SorterSuite`):
https://github.com/HyukjinKwon/spark-agent6/actions/runs/28766544512
Note: the underlying OOM is intermittent (~1/5), so this reduces the flake
probability;
it is not claimed to statistically eliminate it.
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.8)
Closes #57029 from HyukjinKwon/ci-fix/agent6-oom-pr.
Authored-by: Hyukjin Kwon <[email protected]>
Signed-off-by: Hyukjin Kwon <[email protected]>
---
.../scala/org/apache/spark/SparkFunSuite.scala | 29 ++++++++++++++++------
.../apache/spark/util/collection/SorterSuite.scala | 4 +--
2 files changed, 24 insertions(+), 9 deletions(-)
diff --git a/core/src/test/scala/org/apache/spark/SparkFunSuite.scala
b/core/src/test/scala/org/apache/spark/SparkFunSuite.scala
index a0f17f8af3f3..a18b1ec5dbf9 100644
--- a/core/src/test/scala/org/apache/spark/SparkFunSuite.scala
+++ b/core/src/test/scala/org/apache/spark/SparkFunSuite.scala
@@ -113,13 +113,28 @@ abstract class SparkFunSuite
}
}
- /** Run `body`; if it throws OutOfMemoryError, force a GC and retry once. */
- protected def retryOnOOM[T](body: => T): T = {
- try body
- catch {
- case _: OutOfMemoryError =>
- runGC()
- body
+ /**
+ * Run `body`; if it throws OutOfMemoryError, force a GC and retry, up to
`maxAttempts`
+ * total attempts (default 3). A single GC+retry is not always enough in
memory-constrained
+ * CI: a large array retained by a previous test may not be reclaimed by the
first
+ * System.gc(), so the retried allocation OOMs again. Retrying a few times
(with a full
+ * runGC() that waits for a weak reference to clear between attempts) gives
the JVM
+ * additional chances to reclaim memory. This does not mask genuine leaks:
an unbounded
+ * allocation still fails once the attempts are exhausted.
+ */
+ protected def retryOnOOM[T](maxAttempts: Int = 3)(body: => T): T = {
+ var attempt = 1
+ while (true) {
+ try {
+ return body
+ } catch {
+ case oom: OutOfMemoryError =>
+ if (attempt >= maxAttempts) throw oom
+ runGC()
+ attempt += 1
+ }
}
+ // Unreachable: the loop either returns a value or rethrows the OOM.
+ throw new IllegalStateException("retryOnOOM exited its retry loop
unexpectedly")
}
}
diff --git
a/core/src/test/scala/org/apache/spark/util/collection/SorterSuite.scala
b/core/src/test/scala/org/apache/spark/util/collection/SorterSuite.scala
index 2767769924bc..f654501ab436 100644
--- a/core/src/test/scala/org/apache/spark/util/collection/SorterSuite.scala
+++ b/core/src/test/scala/org/apache/spark/util/collection/SorterSuite.scala
@@ -141,8 +141,8 @@ class SorterSuite extends SparkFunSuite {
val arrayToSortSize = 1091482190
// Memory held by the previous test (e.g. the ~256 MB int array in
"SPARK-5984
// TimSort bug") may not be reclaimed before this >1 GB allocation,
causing flaky
- // OOM in CI. Force a GC and retry once on OOM.
- val arrayToSort = retryOnOOM(new Array[Byte](arrayToSortSize))
+ // OOM in CI. Force a GC and retry a few times on OOM.
+ val arrayToSort = retryOnOOM()(new Array[Byte](arrayToSortSize))
var sum: Int = -1
for (i <- runLengths) {
sum += i
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]