LuciferYang opened a new pull request, #57905:
URL: https://github.com/apache/spark/pull/57905

   ### What changes were proposed in this pull request?
   
   Make the local-disk shuffle recovery scan in 
`KubernetesLocalDiskShuffleExecutorComponents.recoverDiskStore` tolerate 
directories it cannot list, instead of aborting the whole recovery.
   
   All five listings now go through a helper that logs and skips when 
`File.listFiles` returns null. A local directory too shallow to have a volume 
root two levels up is skipped with a warning that names the config to change. 
The dead `if (files != null)` guard on an `Array.flatMap` result is removed. 
`SPARK-57530` took the same approach for `SparkFileUtils.recursiveList`.
   
   ### Why are the changes needed?
   
   The scan is a strict `Array.map`/`flatMap` chain with no null handling on 
any of its five `File.listFiles` calls, and the two-level `getParent` walk is 
unguarded. One bad entry throws, `initializeExecutor` swallows the exception 
through `Utils.tryLogNonFatalError`, and the executor goes on to serve requests 
with nothing recovered. The only trace is an NPE stack trace that does not 
mention shuffle recovery, and the job recomputes every map output on the reused 
PVC.
   
   Two cases reach it.
   
   A configured local directory with fewer than three path components. The scan 
walks two levels up from each entry, so for `/data` the first `getParent` 
yields `/` and the second yields null, and `new File((null: String))` throws 
`NullPointerException` from `java.io.File.<init>`. `/data` is the mount path in 
the code block immediately above the recovery instructions in the Local Storage 
section of `running-on-kubernetes.md`, so this is reachable by following the 
docs. Because `map` is strict, one such entry also discards every correctly 
nested directory configured alongside it. An empty string behaves the same way, 
and the existing `filter(_ != null)` does not stop it because 
`String.split(",")` yields `""` rather than null.
   
   A directory in the walk whose `listFiles()` returns null: removed while the 
walk runs, or present but not readable by the executor uid.
   
   The only null check in the method, `if (files != null)` applied to the 
result of `Array.flatMap`, can never fire, so the walk reads as null-safe while 
none of the five listings are guarded.
   
   `SPARK-40459` already established that a single file should not block the 
rest of the recovery. The collection phase in front of that loop was still 
all-or-nothing.
   
   ### Does this PR introduce _any_ user-facing change?
   
   No API or configuration change. Recovery that previously aborted on either 
condition now proceeds and recovers the remaining files, and each skip logs a 
warning. 
`spark.shuffle.sort.io.plugin.class=org.apache.spark.shuffle.KubernetesLocalDiskShuffleDataIO`
 plus `spark.kubernetes.driver.reusePersistentVolumeClaim=true` are still 
required for any of this code to run.
   
   ### How was this patch tested?
   
   Two tests added to `KubernetesLocalDiskShuffleDataIOSuite`, one per 
condition, both driving `recoverDiskStore` directly.
   
   Each test stubs `BlockManager.TempFileBasedBlockStoreUpdater` to throw a 
sentinel `IllegalStateException` and asserts that message, so passing requires 
the scan to have walked down to a recoverable shuffle file. A test that merely 
completed without throwing would pass on an empty scan and prove nothing.
   
   Both were confirmed to fail against the unfixed tree before the fix was 
written, with the NPE arriving from the expected place in each case:
   
   ```
   [info] - SPARK-58693: a local dir with fewer than three path components is 
skipped *** FAILED *** (571 milliseconds)
   [info]   Expected exception java.lang.IllegalStateException to be thrown, 
but java.lang.NullPointerException was thrown 
(KubernetesLocalDiskShuffleDataIOSuite.scala:280)
   [info]   Cause: java.lang.NullPointerException:
   [info]   at java.base/java.io.File.<init>(File.java:278)
   [info] - SPARK-58693: an unlistable directory in the scan does not abort 
recovery *** FAILED *** (8 milliseconds)
   [info]   Expected exception java.lang.IllegalStateException to be thrown, 
but java.lang.NullPointerException was thrown 
(KubernetesLocalDiskShuffleDataIOSuite.scala:298)
   [info]   Cause: java.lang.NullPointerException: Cannot invoke 
"scala.collection.IterableOnce.knownSize()" because "xs" is null
   [info]   at 
scala.collection.mutable.ArrayBuilder.addAll(ArrayBuilder.scala:69)
   [info]   at 
scala.collection.mutable.ArrayBuilder.addAll(ArrayBuilder.scala:24)
   ```
   
   The unlistable-directory test uses `setReadable(false, false)` and, 
following `FsHistoryProviderSuite`, `assume`s the bit actually cleared so it 
self-skips for root. The scala `kubernetes` module runs in the `build` job, 
which has no `container:` and therefore does not run as root, so it does 
execute in CI. The other test needs no permission manipulation and runs 
everywhere.
   
   `build/sbt -Pkubernetes 'kubernetes/testOnly 
org.apache.spark.shuffle.KubernetesLocalDiskShuffleDataIOSuite'`:
   
   ```
   [info] - recompute is not blocked by the recovery (12 seconds, 384 
milliseconds)
   [info] - Partial recompute shuffle data (11 seconds, 118 milliseconds)
   [info] - A new rdd and full recovery of old data (8 seconds, 816 
milliseconds)
   [info] - Multi stages (8 seconds, 557 milliseconds)
   [info] - SPARK-44501: Ignore checksum files (571 milliseconds)
   [info] - SPARK-44534: Handle only shuffle files (7 milliseconds)
   [info] - SPARK-58693: a local dir with fewer than three path components is 
skipped (6 milliseconds)
   [info] - SPARK-58693: an unlistable directory in the scan does not abort 
recovery (4 milliseconds)
   [info] Run completed in 42 seconds, 491 milliseconds.
   [info] Total number of tests run: 8
   [info] Suites: completed 1, aborted 0
   [info] Tests: succeeded 8, failed 0, canceled 0, ignored 0, pending 0
   ```
   
   `kubernetes/scalastyle` and `kubernetes/Test/scalastyle` report 0 errors.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Opus 5)
   


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