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


##########
core/src/main/scala/org/apache/spark/MapOutputTracker.scala:
##########
@@ -958,6 +995,25 @@ private[spark] class MapOutputTrackerMaster(
     incrementEpoch()
   }
 
+  /**
+   * Mark a partition as having stale (redundant) push attempts and bump the 
epoch so that
+   * reducers with a cached (empty) stale set are forced to re-fetch. Without 
the epoch bump,
+   * a reducer that already fetched its merge statuses before this mark would 
keep its stale
+   * set cached and never see the new mark, causing layer-3 fallback to 
silently not fire.
+   *
+   * @param shuffleId the shuffle id.
+   * @param mapIndex the partition index (== mapIndex, not MapStatus.mapId) of 
the stale
+   *                 (redundant) attempt; this is NOT MapStatus.mapId
+   */
+  def markStalePushedPartition(shuffleId: Int, mapIndex: Int): Unit = {
+    getShuffleStatusOrError(shuffleId, "markStalePushedPartition")

Review Comment:
   `getShuffleStatusOrError` throws `ShuffleStatusNotFoundException` when the 
shuffle is absent. This is now reached from 
`TaskSetManager.detectStalePushIfShuffleTask` on the late/killed-attempt path 
(`successful(index) && killedByOtherAttempt.contains(tid)`), and the exception 
is `NonFatal`, so it propagates `handleSuccessfulTask` -> 
`TaskSchedulerImpl.handleSuccessfulTask` -> `TaskResultGetter`'s `case 
NonFatal(ex) => taskSetManager.abort(...)` -> `dagScheduler.taskSetFailed` — 
i.e. a best-effort stale-detection optimization can fail the whole task set.
   
   Before the round-4 refactor this was `shuffleStatuses.get(sid).foreach { ... 
}`, a silent no-op when the shuffle was absent. The other 
`getShuffleStatusOrError` writers (`registerMapOutput`, `unregisterMapOutput`, 
...) only run on the DAGScheduler success/registration paths where the shuffle 
is guaranteed registered; this new caller runs on an error-handling path where 
the shuffle may already have been torn down (ContextCleaner GC after the 
consuming stages finished, or a stage/job cancellation).
   
   Non-blocking, but I'd make the mark tolerant of an absent shuffle, mirroring 
the read accessors (`getShufflePushMergerLocations` -> `.getOrElse(...)`):
   ```suggestion
     def markStalePushedPartition(shuffleId: Int, mapIndex: Int): Unit = {
       shuffleStatuses.get(shuffleId).foreach { shuffleStatus =>
         shuffleStatus.markStalePushedPartition(mapIndex)
         // Bump the epoch so reducers with a cached stale set are forced to 
re-fetch.
         // A reducer that fetched its merge statuses before this mark 
otherwise keeps
         // its (empty) stale set and layer-3 fallback would not fire.
         incrementEpoch()
       }
     }
   ```
   Alternatively, keep the throwing form but guard at the call site as the old 
code did. Is there an ordering guarantee that the shuffle is always still 
registered when a late/killed attempt result arrives here? If so, a one-line 
comment saying so would be enough.



-- 
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