[ 
https://issues.apache.org/jira/browse/SPARK-59845?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

ASF GitHub Bot updated SPARK-59845:
-----------------------------------
    Labels: pull-request-available  (was: )

> Register an accumulator with the TaskContext only after it is fully 
> deserialized
> --------------------------------------------------------------------------------
>
>                 Key: SPARK-59845
>                 URL: https://issues.apache.org/jira/browse/SPARK-59845
>             Project: Spark
>          Issue Type: Bug
>          Components: Spark Core
>    Affects Versions: 4.3.0
>            Reporter: Ziqi Liu
>            Priority: Major
>              Labels: pull-request-available
>
> {{AccumulatorV2.readObject}} registers the accumulator with the 
> {{{}TaskContext{}}}, which publishes it where other threads can reach it. 
> That runs at {{{}AccumulatorV2{}}}'s own slot during deserialization, and 
> Java serialization reads class data superclass-first, so no subclass field 
> has been assigned yet.
> A subclass holding its state in a {{val}} therefore exposes a null for that 
> window. The reader that hits it in practice is the executor heartbeater, 
> which calls {{isZero}} on every registered accumulator every 
> {{{}spark.executor.heartbeatInterval{}}}. The NPE is thrown on the 
> {{executor-heartbeater}} thread and propagates out of a 
> {{scheduleAtFixedRate}} task, so the executor stops heartbeating permanently; 
> the driver then removes it on heartbeat timeout, losing anything held only 
> there — e.g. local checkpoint blocks, which surfaces as 
> {{{}CHECKPOINT_RDD_BLOCK_ID_NOT_FOUND{}}}.
> Because a Scala {{val}} compiles to a final field, there is a second aspect: 
> the reflective write happens after publication, so per JLS 17.5.3 a reader 
> has no guarantee of ever seeing it and the JIT may fold the read.
> SPARK-20977 fixed this locally in {{CollectionAccumulator}} by making its 
> state a non-final field reached through a lazily-initializing accessor. Every 
> subclass since has had to re-derive that workaround, and not all have: 
> {{SetAccumulator}} in {{sql/execution/debug}} still dereferences a {{private 
> val _set}} from {{{}isZero{}}}, and {{EventTimeStatsAccum}} avoids the NPE 
> only incidentally, because Scala's {{==}} is null-safe — it reports the wrong 
> answer and ships a null instead.
> Fix: register in {{{}readResolve{}}}, which Java serialization invokes only 
> after every class in the hierarchy has been read. The accumulator is then 
> never observable half-initialized and no subclass needs to guard its own 
> state.
> Verified with a new test using a subclass that holds state in a plain {{val}} 
> with no null handling: it fails with the NPE on current {{master}} and passes 
> with registration moved, while the existing 
> {{{}LongAccumulator{}}}/{{{}DoubleAccumulator{}}}/{{{}CollectionAccumulator{}}}
>  tests are unaffected.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to