This is an automated email from the ASF dual-hosted git repository.

LuciferYang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new 5ef3a9492f9e [SPARK-58770][CORE] Assign a name to the error condition 
`_LEGACY_ERROR_TEMP_3016-3020`
5ef3a9492f9e is described below

commit 5ef3a9492f9ef4a412ab21aa5e4ba4eb43bc0b99
Author: YangJie <[email protected]>
AuthorDate: Fri Aug 21 10:51:48 2026 +0800

    [SPARK-58770][CORE] Assign a name to the error condition 
`_LEGACY_ERROR_TEMP_3016-3020`
    
    ### What changes were proposed in this pull request?
    
    This PR converts the five `_LEGACY_ERROR_TEMP_*` conditions in 
`SparkCoreErrors` that cover RDD checkpointing, continuing the cleanup under 
[SPARK-37935](https://issues.apache.org/jira/browse/SPARK-37935). Four get 
user-facing names; the fifth is an unreachable defensive branch and becomes an 
internal error.
    
    | Legacy | Builder | Now | SQLSTATE |
    |---|---|---|---|
    | `_LEGACY_ERROR_TEMP_3016` | 
`checkpointDirectoryHasNotBeenSetInSparkContextError` | 
`CHECKPOINT_DIRECTORY_NOT_SET` | 55019 |
    | `_3017` | `invalidCheckpointDirectoryError` | 
`INVALID_CHECKPOINT_DIRECTORY` | 58030 |
    | `_3018` | `failToCreateCheckpointPathError` | 
`FAILED_CREATE_CHECKPOINT_DIRECTORY` | 58030 |
    | `_3019` | 
`checkpointRDDHasDifferentNumberOfPartitionsFromOriginalRDDError` | 
`CHECKPOINT_RDD_PARTITION_COUNT_MISMATCH` | 58030 |
    | `_3020` | `mustSpecifyCheckpointDirError` | `INTERNAL_ERROR` (entry 
deleted) | XX000 |
    
    Four top-level names rather than one umbrella: `sqlState` lives on the 
umbrella, so grouping them would force a single SQLSTATE and lose the 
55019/58030 split, and no umbrella sentence holds for all four. `_3016` fires 
before any job runs and is a user configuration mistake; the other three are 
storage failures during an action.
    
    `invalidCheckpointDirectoryError` gains an `expectedFileName` parameter, 
taken from the `checkpointFileName(i)` the caller already computes, and reports 
the directory and file name separately. The old message named only the 
offending path, which is misleading here: the check walks the sorted `part-*` 
files and compares the *i*-th name against `part-%05d(i)`, so the path it 
reports is a perfectly valid file sitting where a missing one should be. With 
`part-00001` deleted from a 4-parti [...]
    
    `failToCreateCheckpointPathError`'s parameter is renamed 
`checkpointDirPath` to `path`, matching `INVALID_BUCKET_FILE` and the other 
path-carrying conditions.
    
    ### Reachability, per condition
    
    - **`_3016`, user configuration.** `RDD.checkpoint()` is public, and 
`sc.checkpointDir` is set through `SparkContext.setCheckpointDir`, reachable 
either by calling it directly or, since 4.0.0, through `spark.checkpoint.dir`, 
which `SparkContext.scala:623` forwards to the same setter. Calling 
`checkpoint()` with neither lands here. Reachable from `Dataset.checkpoint` 
too, which calls `internalRdd.checkpoint()`, so the message deliberately avoids 
naming RDDs. The old text mentioned only [...]
    - **`_3017`, incomplete checkpoint directory.** `getPartitions` requires 
the `part-*` files to be a contiguous `part-00000..part-000NN`. It fires on a 
partially written checkpoint, a manually pruned directory, or a directory 
handed to `SparkContext.checkpointFile` (which is how streaming recovery 
rebuilds `generatedRDDs`). Driver-side: `getPartitions` runs from 
`RDD.partitions`, and `partitions_` is `transient`, so executors never compute 
it.
    - **`_3018`, storage refused the directory.** Only fires where a 
`FileSystem` reports failure by returning `false` from `mkdirs` rather than 
throwing, which is what HDFS and S3A can do; `LocalFileSystem` tends to throw 
instead. Driver-side, inside `RDD.doCheckpoint()` at the end of the first 
action.
    - **`_3019`, the write and the read-back disagree.** Not an engine 
invariant. The driver creates the directory, each executor writes its own 
`part-*` through its own `FileSystem`, and then the driver counts what its 
`FileSystem` lists, so the two sides of the comparison resolve in different 
JVMs. `setCheckpointDir` only *warns* when a cluster-mode application points at 
a local path, so a user who sets `/tmp/ckpt` on a cluster reaches this 
directly: the executors write to their own dis [...]
    - **`_3020`, unreachable.** `ReliableRDDCheckpointData`'s `cpDir` field 
throws when `sc.checkpointDir` is `None`, but its only construction site is 
`RDD.scala:1743`, two lines below the `context.checkpointDir.isEmpty` guard 
that raises `_3016`, inside the same `RDDCheckpointData.synchronized` block; 
`cpDir` is a plain `val`, evaluated there. `mustSpecifyCheckpointDirError` has 
a single call site, the `getOrElse` on that field. It is reachable only by a 
cross-thread race that also make [...]
    
    ### Why are the changes needed?
    
    The error-conditions 
[README](https://github.com/apache/spark/blob/master/common/utils/src/main/resources/error/README.md)
 disallows new `_LEGACY_ERROR_TEMP_*` entries and asks existing ones to be 
resolved. This clears five of them.
    
    Three of the five were also weak on their own terms. `_3016` predates 
`spark.checkpoint.dir` and told the user about only one of the two ways to 
configure a directory. `_3017` pointed at the wrong file, as described above. 
`_3018` said only that creating the path failed, without saying that the 
filesystem reported it through a return value, which is the detail that tells 
an operator to look at permissions rather than at Spark.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes, to error messages, with no API change.
    
    Converting any legacy condition changes the rendered string in two 
mechanical ways: `SparkThrowableHelper.formatErrorMessage` suppresses the 
`[CONDITION] ` prefix only for `_LEGACY_ERROR_`-prefixed names, and appends ` 
SQLSTATE: xxxxx` when a sqlState exists (legacy entries have none, so all four 
gain both). Beyond that:
    
    - `_3016`: `Checkpoint directory has not been set in the SparkContext` 
becomes `Cannot checkpoint because no checkpoint directory is configured. Set 
one with SparkContext.setCheckpointDir or the "spark.checkpoint.dir" 
configuration.`
    - `_3017`: `Invalid checkpoint file: <path>` becomes `Cannot read the 
checkpoint directory <path>: expected the partition file <expectedFileName> but 
found <fileName>. The partition files must be numbered contiguously from 
part-00000.` The parameter set changes from one key to three, so 
`getMessageParameters()` gains `expectedFileName` and `fileName` while `path` 
narrows from the file to its directory.
    - `_3018`: `Failed to create checkpoint path <checkpointDirPath>` becomes 
`Failed to create the checkpoint directory <path> as FileSystem.mkdirs returned 
false.` The parameter is renamed, so `getMessageParameters()` has `path` where 
it had `checkpointDirPath`.
    - `_3019`: the three-line `Checkpoint RDD has a different number of 
partitions from original RDD. Original RDD [ID: ..., num of partitions: ...]; 
Checkpoint RDD [ID: ..., num of partitions: ...].` becomes `The checkpoint of 
RDD <originalRDDId> has <newRDDLength> partition(s), but the RDD itself has 
<originalRDDLength>. The checkpoint RDD is <newRDDId>.` plus a second line 
naming the two usual causes. Same four parameters.
    - `_3020`: `Checkpoint dir must be specified.` becomes `[INTERNAL_ERROR] 
SparkContext.checkpointDir is unset when creating ReliableRDDCheckpointData. 
SQLSTATE: XX000`, and `getMessageParameters()` goes from empty to `{"message": 
...}` since `INTERNAL_ERROR`'s template is `<message>`. This does not change 
any job-failure message shape: the throw happens on the driver inside 
`RDD.checkpoint()`, before any task runs, so `DAGScheduler.abortStage`'s 
`isInternalError` filter is not involved.
    
    ### How was this patch tested?
    
    None of the five had any test coverage, and `CheckpointSuite` contained no 
`intercept` at all. Three tests are added to `CheckpointStorageSuite`, each 
asserting the condition and the SQLSTATE, and each failing against the 
pre-change code on both fields (the legacy entries carry no sqlState):
    
    - `"checkpoint() without a checkpoint directory"` builds a context with no 
checkpoint directory and asserts `CHECKPOINT_DIRECTORY_NOT_SET`.
    - `"reading a checkpoint directory with a missing partition file"` 
checkpoints a 4-partition RDD, deletes `part-00001`, then reads the directory 
back through `SparkContext.checkpointFile` and asserts 
`INVALID_CHECKPOINT_DIRECTORY` with all three message parameters, pinning that 
the reported expectation is `part-00001` and the file found is `part-00002`.
    - `"checkpoint path that cannot be created"` registers a `LocalFileSystem` 
subclass whose `mkdirs` returns `false` for the per-RDD directory and asserts 
`FAILED_CREATE_CHECKPOINT_DIRECTORY`. Two details are load-bearing: 
`LocalFileSystem` throws `FileAlreadyExistsException` when the path is occupied 
instead of returning `false`, so the failure has to be injected rather than 
staged on disk; and Hadoop caches `FileSystem` instances per scheme, so the 
test also sets `fs.file.impl.disable [...]
    
    `CHECKPOINT_RDD_PARTITION_COUNT_MISMATCH` has no triggering test. The 
comparison it guards needs an original RDD to count against, and on the 
read-back path there is none: `getPartitions`' own scaladoc notes there is "no 
way to know a priori the number of partitions to expect". Reproducing the 
write-path case means making the driver and the executors see different 
contents for the same directory, which `local` mode cannot do since both sides 
share one filesystem. Two related gaps in t [...]
    
    The SQLSTATE assertions were confirmed to be live by temporarily setting 
`CHECKPOINT_DIRECTORY_NOT_SET`'s value to `42000` and watching the test fail 
with `sqlState: expected '55019' but got '42000'` before restoring it. 
`checkError` skips the comparison when `sqlState` is `None` and 
`SparkThrowableSuite` only checks that a state is registered, so a wrong 
SQLSTATE would otherwise ship green.
    
    Ran `core/testOnly org.apache.spark.SparkThrowableSuite 
org.apache.spark.CheckpointSuite org.apache.spark.CheckpointStorageSuite` (68 
tests, all passing).
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8)
    
    Closes #58004 from LuciferYang/assign-name-legacy-3016-3020.
    
    Authored-by: YangJie <[email protected]>
    Signed-off-by: yangjie01 <[email protected]>
---
 .../src/main/resources/error/error-conditions.json | 53 ++++++++---------
 .../org/apache/spark/errors/SparkCoreErrors.scala  | 27 +++++----
 .../apache/spark/rdd/ReliableCheckpointRDD.scala   |  5 +-
 .../scala/org/apache/spark/CheckpointSuite.scala   | 69 +++++++++++++++++++++-
 4 files changed, 114 insertions(+), 40 deletions(-)

diff --git a/common/utils/src/main/resources/error/error-conditions.json 
b/common/utils/src/main/resources/error/error-conditions.json
index df1a1f3a5b8f..aa492d647f12 100644
--- a/common/utils/src/main/resources/error/error-conditions.json
+++ b/common/utils/src/main/resources/error/error-conditions.json
@@ -999,6 +999,13 @@
     },
     "sqlState" : "XX000"
   },
+  "CHECKPOINT_DIRECTORY_NOT_SET" : {
+    "message" : [
+      "Cannot checkpoint because no checkpoint directory is configured.",
+      "Set one with `SparkContext.setCheckpointDir` or the 
\"spark.checkpoint.dir\" configuration."
+    ],
+    "sqlState" : "55019"
+  },
   "CHECKPOINT_FILE_CHECKSUM_VERIFICATION_FAILED" : {
     "message" : [
       "Checksum verification failed, the file may be corrupted. File: 
<fileName>",
@@ -1014,6 +1021,13 @@
     ],
     "sqlState" : "56000"
   },
+  "CHECKPOINT_RDD_PARTITION_COUNT_MISMATCH" : {
+    "message" : [
+      "The checkpoint of RDD <originalRDDId> has <newRDDLength> partition(s), 
but the RDD itself has <originalRDDLength>. The checkpoint RDD is <newRDDId>.",
+      "This usually means the checkpoint directory is not on storage that both 
the driver and the executors can read and write, or that some checkpoint files 
did not survive until the checkpoint was read back."
+    ],
+    "sqlState" : "58030"
+  },
   "CHECK_CONSTRAINT_VIOLATION" : {
     "message" : [
       "CHECK constraint <constraintName> <expression> violated by row with 
values:",
@@ -2678,6 +2692,12 @@
     ],
     "sqlState" : "42822"
   },
+  "FAILED_CREATE_CHECKPOINT_DIRECTORY" : {
+    "message" : [
+      "Failed to create the checkpoint directory <path> as FileSystem.mkdirs 
returned false."
+    ],
+    "sqlState" : "58030"
+  },
   "FAILED_EXECUTE_UDF" : {
     "message" : [
       "User defined function (<functionName>: (<signature>) => <result>) 
failed due to: <reason>."
@@ -3774,6 +3794,12 @@
     },
     "sqlState" : "42K03"
   },
+  "INVALID_CHECKPOINT_DIRECTORY" : {
+    "message" : [
+      "Cannot read the checkpoint directory <path>: expected the partition 
file <expectedFileName> but found <fileName>. The partition files must be 
numbered contiguously from part-00000."
+    ],
+    "sqlState" : "58030"
+  },
   "INVALID_CLONE_SESSION_REQUEST" : {
     "message" : [
       "Invalid session clone request."
@@ -11594,33 +11620,6 @@
       "countByValueApprox() does not support arrays"
     ]
   },
-  "_LEGACY_ERROR_TEMP_3016" : {
-    "message" : [
-      "Checkpoint directory has not been set in the SparkContext"
-    ]
-  },
-  "_LEGACY_ERROR_TEMP_3017" : {
-    "message" : [
-      "Invalid checkpoint file: <path>"
-    ]
-  },
-  "_LEGACY_ERROR_TEMP_3018" : {
-    "message" : [
-      "Failed to create checkpoint path <checkpointDirPath>"
-    ]
-  },
-  "_LEGACY_ERROR_TEMP_3019" : {
-    "message" : [
-      "Checkpoint RDD has a different number of partitions from original RDD. 
Original",
-      "RDD [ID: <originalRDDId>, num of partitions: <originalRDDLength>];",
-      "Checkpoint RDD [ID: <newRDDId>, num of partitions: <newRDDLength>]."
-    ]
-  },
-  "_LEGACY_ERROR_TEMP_3020" : {
-    "message" : [
-      "Checkpoint dir must be specified."
-    ]
-  },
   "_LEGACY_ERROR_TEMP_3028" : {
     "message" : [
       "<errorMsg>"
diff --git a/core/src/main/scala/org/apache/spark/errors/SparkCoreErrors.scala 
b/core/src/main/scala/org/apache/spark/errors/SparkCoreErrors.scala
index cf931707c89e..442d4623c4a9 100644
--- a/core/src/main/scala/org/apache/spark/errors/SparkCoreErrors.scala
+++ b/core/src/main/scala/org/apache/spark/errors/SparkCoreErrors.scala
@@ -157,22 +157,30 @@ private[spark] object SparkCoreErrors {
 
   def checkpointDirectoryHasNotBeenSetInSparkContextError(): Throwable = {
     new SparkException(
-      errorClass = "_LEGACY_ERROR_TEMP_3016", messageParameters = Map.empty, 
cause = null
+      errorClass = "CHECKPOINT_DIRECTORY_NOT_SET",
+      messageParameters = Map.empty,
+      cause = null
     )
   }
 
-  def invalidCheckpointFileError(path: Path): Throwable = {
+  def invalidCheckpointDirectoryError(
+      partitionFilePath: Path,
+      expectedFileName: String): Throwable = {
     new SparkException(
-      errorClass = "_LEGACY_ERROR_TEMP_3017",
-      messageParameters = Map("path" -> s"$path"),
+      errorClass = "INVALID_CHECKPOINT_DIRECTORY",
+      messageParameters = Map(
+        "path" -> s"${partitionFilePath.getParent}",
+        "expectedFileName" -> expectedFileName,
+        "fileName" -> partitionFilePath.getName
+      ),
       cause = null
     )
   }
 
   def failToCreateCheckpointPathError(checkpointDirPath: Path): Throwable = {
     new SparkException(
-      errorClass = "_LEGACY_ERROR_TEMP_3018",
-      messageParameters = Map("checkpointDirPath" -> s"$checkpointDirPath"),
+      errorClass = "FAILED_CREATE_CHECKPOINT_DIRECTORY",
+      messageParameters = Map("path" -> s"$checkpointDirPath"),
       cause = null
     )
   }
@@ -183,7 +191,7 @@ private[spark] object SparkCoreErrors {
       newRDDId: Int,
       newRDDLength: Int): Throwable = {
     new SparkException(
-      errorClass = "_LEGACY_ERROR_TEMP_3019",
+      errorClass = "CHECKPOINT_RDD_PARTITION_COUNT_MISMATCH",
       messageParameters = Map(
         "originalRDDId" -> s"$originalRDDId",
         "originalRDDLength" -> s"$originalRDDLength",
@@ -200,9 +208,8 @@ private[spark] object SparkCoreErrors {
   }
 
   def mustSpecifyCheckpointDirError(): Throwable = {
-    new SparkException(
-      errorClass = "_LEGACY_ERROR_TEMP_3020", messageParameters = Map.empty, 
cause = null
-    )
+    SparkException.internalError(
+      "SparkContext.checkpointDir is unset when creating 
ReliableRDDCheckpointData.")
   }
 
   def askStandaloneSchedulerToShutDownExecutorsError(e: Exception): Throwable 
= {
diff --git 
a/core/src/main/scala/org/apache/spark/rdd/ReliableCheckpointRDD.scala 
b/core/src/main/scala/org/apache/spark/rdd/ReliableCheckpointRDD.scala
index fd42cea795d6..156a94abd628 100644
--- a/core/src/main/scala/org/apache/spark/rdd/ReliableCheckpointRDD.scala
+++ b/core/src/main/scala/org/apache/spark/rdd/ReliableCheckpointRDD.scala
@@ -78,8 +78,9 @@ private[spark] class ReliableCheckpointRDD[T: ClassTag](
       .sortBy(_.getName.stripPrefix("part-").toInt)
     // Fail fast if input files are invalid
     inputFiles.zipWithIndex.foreach { case (path, i) =>
-      if (path.getName != ReliableCheckpointRDD.checkpointFileName(i)) {
-        throw SparkCoreErrors.invalidCheckpointFileError(path)
+      val expectedFileName = ReliableCheckpointRDD.checkpointFileName(i)
+      if (path.getName != expectedFileName) {
+        throw SparkCoreErrors.invalidCheckpointDirectoryError(path, 
expectedFileName)
       }
     }
     Array.tabulate(inputFiles.length)(i => new CheckpointRDDPartition(i))
diff --git a/core/src/test/scala/org/apache/spark/CheckpointSuite.scala 
b/core/src/test/scala/org/apache/spark/CheckpointSuite.scala
index 58512a2282ac..976e9b94d23c 100644
--- a/core/src/test/scala/org/apache/spark/CheckpointSuite.scala
+++ b/core/src/test/scala/org/apache/spark/CheckpointSuite.scala
@@ -21,7 +21,7 @@ import java.io.File
 
 import scala.reflect.ClassTag
 
-import org.apache.hadoop.fs.Path
+import org.apache.hadoop.fs.{LocalFileSystem, Path}
 
 import 
org.apache.spark.internal.config.CACHE_CHECKPOINT_PREFERRED_LOCS_EXPIRE_TIME
 import org.apache.spark.internal.config.UI._
@@ -684,4 +684,71 @@ class CheckpointStorageSuite extends SparkFunSuite with 
LocalSparkContext {
       assert(flatMappedRDD.collect() === result)
     }
   }
+
+  test("checkpoint() without a checkpoint directory") {
+    sc = new SparkContext("local", "test", new SparkConf().set(UI_ENABLED.key, 
"false"))
+    checkError(
+      exception = intercept[SparkException](sc.makeRDD(1 to 4).checkpoint()),
+      condition = "CHECKPOINT_DIRECTORY_NOT_SET",
+      sqlState = Some("55019"))
+  }
+
+  test("reading a checkpoint directory with a missing partition file") {
+    withTempDir { checkpointDir =>
+      sc = new SparkContext("local", "test", new 
SparkConf().set(UI_ENABLED.key, "false"))
+      sc.setCheckpointDir(checkpointDir.toString)
+      val rdd = sc.makeRDD(1 to 20, numSlices = 4)
+      rdd.checkpoint()
+      assert(rdd.collect().toSeq === (1 to 20))
+
+      // Drop a middle partition file so the remaining names are no longer 
part-00000..part-0000N.
+      val checkpointPath = new Path(rdd.getCheckpointFile.get)
+      val fs = checkpointPath.getFileSystem(sc.hadoopConfiguration)
+      assert(fs.delete(new Path(checkpointPath, "part-00001"), false))
+
+      val recovered = sc.checkpointFile[Int](checkpointPath.toString)
+      checkError(
+        exception = intercept[SparkException](recovered.partitions),
+        condition = "INVALID_CHECKPOINT_DIRECTORY",
+        sqlState = Some("58030"),
+        parameters = Map(
+          "path" -> checkpointPath.toString,
+          "expectedFileName" -> "part-00001",
+          "fileName" -> "part-00002"))
+    }
+  }
+
+  test("checkpoint path that cannot be created") {
+    withTempDir { checkpointDir =>
+      // MkdirsFailingFilesystem refuses to create the per-RDD directory and 
reports it the way
+      // HDFS and S3A do, by returning false rather than throwing.
+      val conf = new SparkConf()
+        .set("spark.hadoop.fs.file.impl", 
classOf[MkdirsFailingFilesystem].getName)
+        .set("spark.hadoop.fs.file.impl.disable.cache", "true")
+        .set(UI_ENABLED.key, "false")
+      sc = new SparkContext("local", "test", conf)
+      sc.setCheckpointDir(checkpointDir.toString)
+      val rdd = sc.makeRDD(1 to 4)
+      val rddPath = ReliableRDDCheckpointData.checkpointPath(sc, rdd.id).get
+
+      rdd.checkpoint()
+      checkError(
+        exception = intercept[SparkException](rdd.collect()),
+        condition = "FAILED_CREATE_CHECKPOINT_DIRECTORY",
+        sqlState = Some("58030"),
+        parameters = Map("path" -> rddPath.toString))
+    }
+  }
+}
+
+/**
+ * A filesystem that refuses to create a per-RDD checkpoint directory, 
returning false from
+ * `mkdirs` instead of throwing. `LocalFileSystem` throws 
`FileAlreadyExistsException` when the
+ * path is taken, while HDFS and S3A can report the failure through the return 
value, which is
+ * what `ReliableCheckpointRDD` checks.
+ */
+class MkdirsFailingFilesystem extends LocalFileSystem {
+  override def mkdirs(f: Path): Boolean = {
+    if (f.getName.startsWith("rdd-")) false else super.mkdirs(f)
+  }
 }


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

Reply via email to