cloud-fan commented on code in PR #56559:
URL: https://github.com/apache/spark/pull/56559#discussion_r3470052283


##########
core/src/main/scala/org/apache/spark/storage/PushBasedFetchHelper.scala:
##########
@@ -17,23 +17,20 @@
 
 package org.apache.spark.storage
 
-import java.util.concurrent.TimeUnit
-
-import scala.collection
-import scala.collection.mutable
-import scala.collection.mutable.ArrayBuffer
-import scala.util.{Failure, Success}
-
-import org.roaringbitmap.RoaringBitmap
-
 import org.apache.spark.MapOutputTracker
 import org.apache.spark.MapOutputTracker.SHUFFLE_PUSH_MAP_ID
-import org.apache.spark.internal.Logging
 import org.apache.spark.internal.LogKeys._
+import org.apache.spark.internal.Logging
 import org.apache.spark.network.shuffle.{BlockStoreClient, MergedBlockMeta, 
MergedBlocksMetaListener}
 import org.apache.spark.shuffle.ShuffleReadMetricsReporter
 import org.apache.spark.storage.BlockManagerId.SHUFFLE_MERGER_IDENTIFIER
 import org.apache.spark.storage.ShuffleBlockFetcherIterator._
+import org.roaringbitmap.RoaringBitmap
+
+import java.util.concurrent.TimeUnit
+import scala.collection.mutable
+import scala.collection.mutable.ArrayBuffer
+import scala.util.{Failure, Success}

Review Comment:
   These imports are now ordered spark → 3rdParty → java → scala, but 
scalastyle's `ImportOrderChecker` enforces `java, scala, 3rdParty, spark` at 
error level (`scalastyle-config.xml:578`), so the lint CI gate will fail. Looks 
like an IDE "optimize imports" with non-Spark settings; restore the grouping:
   ```suggestion
   import java.util.concurrent.TimeUnit
   
   import scala.collection.mutable
   import scala.collection.mutable.ArrayBuffer
   import scala.util.{Failure, Success}
   
   import org.roaringbitmap.RoaringBitmap
   
   import org.apache.spark.MapOutputTracker
   import org.apache.spark.MapOutputTracker.SHUFFLE_PUSH_MAP_ID
   import org.apache.spark.internal.LogKeys._
   import org.apache.spark.internal.Logging
   import org.apache.spark.network.shuffle.{BlockStoreClient, MergedBlockMeta, 
MergedBlocksMetaListener}
   import org.apache.spark.shuffle.ShuffleReadMetricsReporter
   import org.apache.spark.storage.BlockManagerId.SHUFFLE_MERGER_IDENTIFIER
   import org.apache.spark.storage.ShuffleBlockFetcherIterator._
   ```



##########
core/src/main/scala/org/apache/spark/MapOutputTracker.scala:
##########
@@ -105,6 +105,30 @@ private class ShuffleStatus(
    */
   private[spark] val checksumMismatchIndices: Set[Int] = Set()
 
+  /**
+   * Set of stale pushed partition indexes for this shuffle. Each entry is a 
partitionId (which
+   * equals mapIndex, not MapStatus.mapId). When task retry or speculation 
causes multiple
+   * attempts for the same map output to push, the merger may include data 
from a stale attempt.
+   * We record the stale partition indexes here so the reduce side can check 
chunkBitmaps and
+   * fallback if stale data is present in a merged block.
+   */
+  private[this] val staleMapIndexes = new java.util.HashSet[Int]()
+
+  /**
+   * Mark a partition as having stale (redundant) push attempts. Called from 
TaskSetManager when it
+   * detects that multiple task attempts for the same map output pushed data 
to the merger.
+   * @param mapIndex the partition index (== mapIndex) of the stale 
(redundant) attempt;
+   *                   this is NOT MapStatus.mapId
+   */
+  def markStalePushedPartition(mapIndex: Int): Unit = withWriteLock {
+    staleMapIndexes.add(mapIndex)
+  }
+
+  /**
+   * Get all stale pushed partition indexes for this shuffle. Returns empty 
set if none exist.
+   */
+  def getStaleMapIndexes: Set[Int] = staleMapIndexes.asScala

Review Comment:
   `.asScala` returns a live, mutable view backed by the non-thread-safe 
`staleMapIndexes` HashSet, and this read takes no `withReadLock` — yet 
`markStalePushedPartition` mutates the same set under `withWriteLock`. When the 
reduce side uses the master tracker, `checkStaleMapIdInMergedBlock` iterates 
this view (`isEmpty` / `exists`) while the driver may be marking a new stale 
partition, which is a data race and can throw `ConcurrentModificationException` 
mid-iteration. Both siblings already handle this correctly — the worker-side 
accessor returns a defensive copy (line 1359) and the serialization path takes 
`withReadLock` (line 466). Mirror them here:
   ```suggestion
     def getStaleMapIndexes: Set[Int] = withReadLock {
       new java.util.HashSet[Int](staleMapIndexes).asScala
     }
   ```



##########
core/src/main/scala/org/apache/spark/MapOutputTracker.scala:
##########
@@ -1644,6 +1717,48 @@ private[spark] object MapOutputTracker extends Logging {
     }
   }
 
+  /**
+   * Serialize a set of stale pushed partition indexes into a compact byte 
array.
+   * Uses DataOutputStream for a simple, efficient binary format:
+   * [int: count][int: mapId1][int: mapId2]...

Review Comment:
   These elements are mapIndexes (partitionIds), not `MapStatus.mapId` — the 
exact term this PR renamed everywhere to avoid. Match the diagram to the rest 
of the doc:
   ```suggestion
      * [int: count][int: mapIndex1][int: mapIndex2]...
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to