This is an automated email from the ASF dual-hosted git repository.
cloud-fan pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/branch-4.x by this push:
new ac67095dcd9b [SPARK-60570][CORE] Add AccumulatorContext.getOrNull to
avoid an Option per driver-side accumulator lookup
ac67095dcd9b is described below
commit ac67095dcd9be8250865a2f67966f4a45ab8bd06
Author: Shujing Yang <[email protected]>
AuthorDate: Tue Oct 27 16:29:38 2026 +0000
[SPARK-60570][CORE] Add AccumulatorContext.getOrNull to avoid an Option per
driver-side accumulator lookup
### What changes were proposed in this pull request?
This PR adds `AccumulatorContext.getOrNull`. It does the same lookup as
`AccumulatorContext.get`, but returns `null` instead of `None` when the
accumulator is not registered or has been garbage collected. `get` now
delegates to `getOrNull` and wraps the result in `Option`.
`DAGScheduler.updateAccumulators` now calls `getOrNull` and null-checks the
result.
### Why are the changes needed?
`DAGScheduler.updateAccumulators` runs on the driver for every finished
task, and it looks up every accumulator update the task carries. A task can
carry hundreds of updates when SQL metrics are involved.
`AccumulatorContext.get` returns an `Option`, so each lookup allocates a `Some`
that is dropped right away. `getOrNull` removes that allocation from the
per-task path.
### Does this PR introduce _any_ user-facing change?
No. `get` returns the same results as before, including `None` for a
garbage-collected accumulator. The garbage-collected warning is still logged
inside the lookup. A missing accumulator in `updateAccumulators` still throws
`accessNonExistentAccumulatorError`. `AccumulatorContext` is `private[spark]`,
so no public API changes.
### How was this patch tested?
No new test, because the changed methods return, log, and throw exactly as
before. Existing suites cover the changed paths and pass locally:
```
build/sbt "core/testOnly org.apache.spark.AccumulatorSuite
org.apache.spark.util.AccumulatorV2Suite
org.apache.spark.scheduler.DAGSchedulerSuite"
```
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Claude Opus 5.5)
This pull request and its description were written by Isaac.
Closes #59801 from shujingyang-db/accum-get-or-null.
Authored-by: Shujing Yang <[email protected]>
Signed-off-by: Wenchen Fan <[email protected]>
(cherry picked from commit 293dcf9af9e3acd20779d622b535657a52aae9c3)
Signed-off-by: Wenchen Fan <[email protected]>
---
.../scala/org/apache/spark/scheduler/DAGScheduler.scala | 8 +++++---
.../main/scala/org/apache/spark/util/AccumulatorV2.scala | 13 +++++++++++--
2 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/core/src/main/scala/org/apache/spark/scheduler/DAGScheduler.scala
b/core/src/main/scala/org/apache/spark/scheduler/DAGScheduler.scala
index 45a9948a069b..d4e32f93d64c 100644
--- a/core/src/main/scala/org/apache/spark/scheduler/DAGScheduler.scala
+++ b/core/src/main/scala/org/apache/spark/scheduler/DAGScheduler.scala
@@ -3517,10 +3517,12 @@ private[spark] class DAGScheduler(
val id = updates.id
try {
// Find the corresponding accumulator on the driver and update it
- val acc: AccumulatorV2[Any, Any] = AccumulatorContext.get(id) match {
- case Some(accum) => accum.asInstanceOf[AccumulatorV2[Any, Any]]
- case None =>
+ val acc: AccumulatorV2[Any, Any] = {
+ val accum = AccumulatorContext.getOrNull(id)
+ if (accum == null) {
throw SparkCoreErrors.accessNonExistentAccumulatorError(id)
+ }
+ accum.asInstanceOf[AccumulatorV2[Any, Any]]
}
acc.merge(updates.asInstanceOf[AccumulatorV2[Any, Any]])
if (acc.isInstanceOf[LastAttemptAccumulator[_, _, _]]) {
diff --git a/core/src/main/scala/org/apache/spark/util/AccumulatorV2.scala
b/core/src/main/scala/org/apache/spark/util/AccumulatorV2.scala
index 20b2baef0f23..688445033a44 100644
--- a/core/src/main/scala/org/apache/spark/util/AccumulatorV2.scala
+++ b/core/src/main/scala/org/apache/spark/util/AccumulatorV2.scala
@@ -287,9 +287,18 @@ private[spark] object AccumulatorContext extends Logging {
* Returns the [[AccumulatorV2]] registered with the given ID, if any.
*/
def get(id: Long): Option[AccumulatorV2[_, _]] = {
+ Option(getOrNull(id))
+ }
+
+ /**
+ * Same as [[get]] but returns null instead of None when the accumulator is
not registered or has
+ * been garbage collected. Used on the driver's per-task accumulator update
path to avoid
+ * allocating an Option for every lookup.
+ */
+ def getOrNull(id: Long): AccumulatorV2[_, _] = {
val ref = originals.get(id)
if (ref eq null) {
- None
+ null
} else {
// Since we are storing weak references, warn when the underlying data
is not valid.
val acc = ref.get
@@ -297,7 +306,7 @@ private[spark] object AccumulatorContext extends Logging {
logWarning(log"Attempted to access garbage collected accumulator " +
log"${MDC(ACCUMULATOR_ID, id)}")
}
- Option(acc)
+ acc
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]