You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by as...@apache.org on 2025/07/12 00:09:23 UTC

(spark) branch master updated: [SPARK-52740][SS] Fix NPE in HDFSBackedStateStoreProvider accessing StateStoreConf.sqlConf when checkpoint format version is >=2

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

ashrigondekar 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 d91a5da06920 [SPARK-52740][SS] Fix NPE in HDFSBackedStateStoreProvider accessing StateStoreConf.sqlConf when checkpoint format version is >=2
d91a5da06920 is described below

commit d91a5da0692008fe332d02d6b875ee359757b5d6
Author: Livia Zhu <li...@databricks.com>
AuthorDate: Fri Jul 11 17:08:55 2025 -0700

    [SPARK-52740][SS] Fix NPE in HDFSBackedStateStoreProvider accessing StateStoreConf.sqlConf when checkpoint format version is >=2
    
    ### What changes were proposed in this pull request?
    
    This change stores checkpoint format version as a field StateStoreConf directly so we do not need to access the transient sqlConf field.
    
    ### Why are the changes needed?
    
    Since StateStoreConf's sqlConf field is transient, it is set to null when the conf is serialized and deserialized. This causes a NPE in HDFSBackedStateStoreProvider when checkpoint format version >= 2 because it accesses the sqlConf field for the assert error message.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No
    
    ### How was this patch tested?
    
    New unit test
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Closes #51431 from liviazhu/liviazhu-db/hdfs-npe.
    
    Authored-by: Livia Zhu <li...@databricks.com>
    Signed-off-by: Anish Shrigondekar <an...@databricks.com>
---
 .../state/HDFSBackedStateStoreProvider.scala       |  2 +-
 .../execution/streaming/state/StateStoreConf.scala |  7 +++-
 .../streaming/state/StateStoreSuite.scala          | 39 +++++++++++++++++++++-
 3 files changed, 45 insertions(+), 3 deletions(-)

diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/HDFSBackedStateStoreProvider.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/HDFSBackedStateStoreProvider.scala
index 25a1ca249c22..0b59b038cbc1 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/HDFSBackedStateStoreProvider.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/HDFSBackedStateStoreProvider.scala
@@ -351,7 +351,7 @@ private[sql] class HDFSBackedStateStoreProvider extends StateStoreProvider with
     assert(
       !storeConf.enableStateStoreCheckpointIds,
       "HDFS State Store Provider doesn't support checkpointFormatVersion >= 2 " +
-        s"checkpointFormatVersion ${storeConf.sqlConf.stateStoreCheckpointFormatVersion}")
+        s"checkpointFormatVersion ${storeConf.stateStoreCheckpointFormatVersion}")
 
     this.stateStoreId_ = stateStoreId
     this.keySchema = keySchema
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/StateStoreConf.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/StateStoreConf.scala
index 9a994200baeb..cbf8227ac08c 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/StateStoreConf.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/StateStoreConf.scala
@@ -22,7 +22,9 @@ import org.apache.spark.sql.internal.SQLConf
 
 /** A class that contains configuration parameters for [[StateStore]]s. */
 class StateStoreConf(
-    @transient private[state] val sqlConf: SQLConf,
+    // Should be private because it could be null under serialization (due to
+    // the transient annotation)
+    @transient private val sqlConf: SQLConf,
     val extraOptions: Map[String, String] = Map.empty)
   extends Serializable {
 
@@ -106,6 +108,9 @@ class StateStoreConf(
   /** Whether to unload the store on task completion. */
   val unloadOnCommit = sqlConf.stateStoreUnloadOnCommit
 
+  /** The version of the state store checkpoint format. */
+  val stateStoreCheckpointFormatVersion: Int = sqlConf.stateStoreCheckpointFormatVersion
+
   /**
    * Additional configurations related to state store. This will capture all configs in
    * SQLConf that start with `spark.sql.streaming.stateStore.`
diff --git a/sql/core/src/test/scala/org/apache/spark/sql/execution/streaming/state/StateStoreSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/execution/streaming/state/StateStoreSuite.scala
index aa4a50b853a4..dae52e8b5120 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/execution/streaming/state/StateStoreSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/execution/streaming/state/StateStoreSuite.scala
@@ -17,7 +17,7 @@
 
 package org.apache.spark.sql.execution.streaming.state
 
-import java.io.{File, IOException}
+import java.io.{ByteArrayInputStream, ByteArrayOutputStream, File, IOException, ObjectInputStream, ObjectOutputStream}
 import java.net.URI
 import java.util
 import java.util.UUID
@@ -986,6 +986,43 @@ class StateStoreSuite extends StateStoreSuiteBase[HDFSBackedStateStoreProvider]
     }
   }
 
+  test("SPARK-52740: HDFSBackedStateStoreProvider checkpoint format version 2 with " +
+    "deserialized sqlConf throws error correctly") {
+    val sqlConf = getDefaultSQLConf(SQLConf.STATE_STORE_MIN_DELTAS_FOR_SNAPSHOT.defaultValue.get,
+      SQLConf.MAX_BATCHES_TO_RETAIN_IN_MEMORY.defaultValue.get)
+    sqlConf.setConf(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION, 2)
+    val originalConf = new StateStoreConf(sqlConf)
+
+    // Serialize
+    val baos = new ByteArrayOutputStream()
+    val oos = new ObjectOutputStream(baos)
+    oos.writeObject(originalConf)
+    oos.close()
+
+    // Deserialize
+    val bais = new ByteArrayInputStream(baos.toByteArray)
+    val ois = new ObjectInputStream(bais)
+    val deserializedConf = ois.readObject().asInstanceOf[StateStoreConf]
+    ois.close()
+
+    val provider = new HDFSBackedStateStoreProvider()
+    val hadoopConf = new Configuration()
+    hadoopConf.set(StreamExecution.RUN_ID_KEY, UUID.randomUUID().toString)
+
+    val e = intercept[AssertionError] {
+      provider.init(
+        StateStoreId(newDir(), Random.nextInt(), 0),
+        keySchema,
+        valueSchema,
+        NoPrefixKeyStateEncoderSpec(keySchema),
+        useColumnFamilies = false,
+        deserializedConf,
+        hadoopConf)
+    }
+    assert(e.getMessage.contains(
+      "HDFS State Store Provider doesn't support checkpointFormatVersion >= 2"))
+  }
+
   override def newStoreProvider(): HDFSBackedStateStoreProvider = {
     newStoreProvider(opId = Random.nextInt(), partition = 0)
   }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@spark.apache.org
For additional commands, e-mail: commits-help@spark.apache.org