This is an automated email from the ASF dual-hosted git repository.
HyukjinKwon 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 850a41e06238 [SPARK-57652][SS][TEST] Deflake
snapshotStartBatchId-with-transformWithState StateDataSource test
850a41e06238 is described below
commit 850a41e0623834c9b7eb5457692f08b61b8d770a
Author: Hyukjin Kwon <[email protected]>
AuthorDate: Wed Jun 24 19:39:42 2026 +0900
[SPARK-57652][SS][TEST] Deflake
snapshotStartBatchId-with-transformWithState StateDataSource test
### What changes were proposed in this pull request?
Replace the fixed `Thread.sleep(5000)` in the `"snapshotStartBatchId with
transformWithState"` test of `StateDataSourceTransformWithStateSuite` with a
deterministic `eventually(...)` wait that polls until the RocksDB snapshot
files the `snapshotStartBatchId` reader needs (snapshot version 2 for the
partitions read) have actually been uploaded by the asynchronous maintenance
thread.
The root cause is known, but to guard against regressions the timeout
assertion now prints the actual state-directory contents, so a recurrence in
scheduled jobs is immediately diagnosable (snapshot still pending vs. cleaned
up vs. wrong path) rather than a bare failure.
### Why are the changes needed?
The snapshot upload is asynchronous (background maintenance thread), so the
fixed sleep is racy under CI load. When it is too short, the snapshot `.zip` is
not yet uploaded and the reader fails on the scheduled **Maven (Scala 2.13, JDK
21)** and **JDK 25** builds:
```
[CANNOT_LOAD_STATE_STORE.UNCATEGORIZED] An error occurred during loading
state.
Caused by: java.io.FileNotFoundException: .../state/0/1/2.zip does not exist
```
### Does this PR introduce _any_ user-facing change?
No. Test-only.
### How was this patch tested?
- **Before (failing job):** [`Build / Maven (Scala 2.13, JDK 21)` →
`sql#core - slow
tests`](https://github.com/apache/spark/actions/runs/28048347820/job/83041196522)
— `snapshotStartBatchId with transformWithState ... *** FAILED ***`
(`FileNotFoundException ... 2.zip`).
- **After (passing, run 10x to confirm the flake is gone):** [✅ 10/10
passed](https://github.com/HyukjinKwon/spark/actions/runs/28074724227/job/83116481122)
— the test was executed 10 consecutive times in one sbt session, all green.
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Opus 4.8
Closes #56721 from HyukjinKwon/SPARK-57652-tws-deflake.
Authored-by: Hyukjin Kwon <[email protected]>
Signed-off-by: Hyukjin Kwon <[email protected]>
---
.../StateDataSourceTransformWithStateSuite.scala | 32 ++++++++++++++++++++--
1 file changed, 30 insertions(+), 2 deletions(-)
diff --git
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/v2/state/StateDataSourceTransformWithStateSuite.scala
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/v2/state/StateDataSourceTransformWithStateSuite.scala
index d7f28b79acff..f861388605e0 100644
---
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/v2/state/StateDataSourceTransformWithStateSuite.scala
+++
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/v2/state/StateDataSourceTransformWithStateSuite.scala
@@ -20,6 +20,7 @@ import java.io.File
import java.time.Duration
import org.apache.hadoop.conf.Configuration
+import org.scalatest.time.SpanSugar._
import org.apache.spark.io.CompressionCodec
import org.apache.spark.sql.{Encoders, Row}
@@ -1050,20 +1051,47 @@ class StateDataSourceTransformWithStateSuite extends
StateStoreMetricsTest
.transformWithState(new AggregationStatefulProcessor(),
TimeMode.None(),
OutputMode.Append())
+
+ // Block until the async maintenance thread has written the RocksDB
snapshot `.zip` for the
+ // given state-store `version` (matching both `<v>.zip` and
checkpoint-v2 `<v>_<uid>.zip`)
+ // on each of `partitions`. Used right after `version` is committed
(while it is the current
+ // version), since maintenance only ever snapshots the current
version. On timeout it prints
+ // the actual directory contents so a recurrence in scheduled jobs is
diagnosable.
+ def waitForStateSnapshot(version: Long, partitions: Seq[Int]):
StreamAction = Execute { _ =>
+ val opStateDir = new File(tmpDir, "state/0")
+ eventually(timeout(60.seconds), interval(100.milliseconds)) {
+ partitions.foreach { partition =>
+ val partitionDir = new File(opStateDir, partition.toString)
+ val files = Option(partitionDir.listFiles())
+ .map(_.map(_.getName).sorted.toSeq).getOrElse(Seq.empty)
+ val snapshotUploaded =
files.exists(_.matches(s"$version(_.*)?\\.zip"))
+ assert(snapshotUploaded,
+ s"Snapshot (version $version) for partition $partition was not
uploaded in time. " +
+ s"Contents of $partitionDir: ${files.mkString("[", ", ",
"]")}")
+ }
+ }
+ }
+
testStream(query)(
StartStream(checkpointLocation = tmpDir.getCanonicalPath),
AddData(inputData, (1, 1L), (2, 2L), (3, 3L), (4, 4L)),
ProcessAllAvailable(),
AddData(inputData, (5, 1L), (6, 2L), (7, 3L), (8, 4L)),
ProcessAllAvailable(),
+ // State version 2 is now the current version. The
snapshotStartBatchId=1 reader below
+ // needs the version-2 snapshot, and the asynchronous maintenance
thread only creates a
+ // snapshot for the *current* version. So block here, while version
2 is still current,
+ // until that snapshot has actually been written - this
deterministically forces it to
+ // exist. (A fixed sleep at the *end* is flaky: once later batches
advance the current
+ // version, maintenance never goes back to snapshot version 2, so it
may never appear and
+ // the reader fails with FileNotFoundException on `2.zip`.)
+ waitForStateSnapshot(version = 2, partitions = Seq(1, 4)),
AddData(inputData, (9, 1L), (10, 2L), (11, 3L), (12, 4L)),
ProcessAllAvailable(),
AddData(inputData, (13, 1L), (14, 2L), (15, 3L), (16, 4L)),
ProcessAllAvailable(),
AddData(inputData, (17, 1L), (18, 2L), (19, 3L), (20, 4L)),
ProcessAllAvailable(),
- // Ensure that we get a chance to upload created snapshots
- Execute { _ => Thread.sleep(5000) },
StopStream
)
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]