You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fluss.apache.org by yu...@apache.org on 2026/09/16 02:09:34 UTC

(fluss) branch main updated: [spark] Fix spark async write exception propagation (#4332)

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

luoyuxia pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fluss.git


The following commit(s) were added to refs/heads/main by this push:
     new 03e511a29 [spark] Fix spark async write exception propagation (#4332)
03e511a29 is described below

commit 03e511a2974c747ffd36612c1cef95ba6aaeaca8
Author: Yang Zhang <ya...@alibaba-inc.com>
AuthorDate: Wed Sep 16 10:09:29 2026 +0800

    [spark] Fix spark async write exception propagation (#4332)
---
 .../apache/fluss/spark/write/FlussDataWriter.scala |  2 +-
 .../org/apache/fluss/spark/SparkWriteTest.scala    | 73 +++++++++++++++++++++-
 2 files changed, 71 insertions(+), 4 deletions(-)

diff --git a/fluss-spark/fluss-spark-common/src/main/scala/org/apache/fluss/spark/write/FlussDataWriter.scala b/fluss-spark/fluss-spark-common/src/main/scala/org/apache/fluss/spark/write/FlussDataWriter.scala
index 94fae6133..9fd6676e3 100644
--- a/fluss-spark/fluss-spark-common/src/main/scala/org/apache/fluss/spark/write/FlussDataWriter.scala
+++ b/fluss-spark/fluss-spark-common/src/main/scala/org/apache/fluss/spark/write/FlussDataWriter.scala
@@ -55,7 +55,7 @@ abstract class FlussDataWriter[T](
   protected val flussRow = new SparkAsFlussRow(dataSchema)
 
   @volatile
-  private var asyncWriterException: Option[Throwable] = _
+  private var asyncWriterException: Option[Throwable] = None
 
   def writeRow(record: SparkAsFlussRow): CompletableFuture[T]
 
diff --git a/fluss-spark/fluss-spark-ut/src/test/scala/org/apache/fluss/spark/SparkWriteTest.scala b/fluss-spark/fluss-spark-ut/src/test/scala/org/apache/fluss/spark/SparkWriteTest.scala
index a2382c161..1198a5ada 100644
--- a/fluss-spark/fluss-spark-ut/src/test/scala/org/apache/fluss/spark/SparkWriteTest.scala
+++ b/fluss-spark/fluss-spark-ut/src/test/scala/org/apache/fluss/spark/SparkWriteTest.scala
@@ -17,15 +17,22 @@
 
 package org.apache.fluss.spark
 
-import org.apache.fluss.metadata.{Schema, TableDescriptor}
+import org.apache.fluss.client.table.writer.{AppendResult, TableWriter}
+import org.apache.fluss.config.Configuration
+import org.apache.fluss.metadata.{Schema, TableDescriptor, TablePath}
 import org.apache.fluss.row.{BinaryString, GenericRow, InternalRow}
+import org.apache.fluss.spark.row.SparkAsFlussRow
 import org.apache.fluss.spark.util.TestUtils.{createGenericRow, FLUSS_ROWTYPE}
+import org.apache.fluss.spark.write.{FlussDataWriter, FlussWriterCommitMessage}
 import org.apache.fluss.types.DataTypes
 
-import org.assertj.core.api.Assertions.assertThat
+import org.apache.spark.sql.catalyst.{InternalRow => SparkInternalRow}
+import org.apache.spark.sql.types.StructType
+import org.assertj.core.api.Assertions.{assertThat, assertThatThrownBy}
 
+import java.io.IOException
 import java.sql.Timestamp
-import java.time.Duration
+import java.util.concurrent.CompletableFuture
 
 import scala.collection.JavaConverters._
 
@@ -151,6 +158,66 @@ class SparkWriteTest extends FlussSparkTestBase {
     assertThat(flussRows2.length).isEqualTo(4)
     assertThat(flussRows2).containsAll(expectRows2.toIterable.asJava)
   }
+
+  test("asynchronous failure is propagated on the next write") {
+    val result = new CompletableFuture[AppendResult]()
+    val writer = createWriter(result)
+    val failure = new IOException("Fluss append failed")
+
+    writer.write(SparkInternalRow.empty)
+    result.completeExceptionally(failure)
+
+    assertThatThrownBy(() => writer.write(SparkInternalRow.empty))
+      .isInstanceOf(classOf[IOException])
+      .hasCause(failure)
+  }
+
+  test("asynchronous failure during flush is propagated on commit") {
+    val result = new CompletableFuture[AppendResult]()
+    val failure = new IOException("Fluss append failed")
+    val writer = createWriter(
+      result,
+      () => {
+        result.completeExceptionally(failure)
+        ()
+      })
+
+    writer.write(SparkInternalRow.empty)
+
+    assertThatThrownBy(() => writer.commit())
+      .isInstanceOf(classOf[IOException])
+      .hasCause(failure)
+  }
+
+  test("successful asynchronous write can commit") {
+    val result = new CompletableFuture[AppendResult]()
+    val writer = createWriter(
+      result,
+      () => {
+        result.complete(new AppendResult())
+        ()
+      })
+
+    writer.write(SparkInternalRow.empty)
+
+    assertThat(writer.commit()).isEqualTo(FlussWriterCommitMessage())
+    assertThat(result.isDone).isTrue
+  }
+
+  private def createWriter(
+      result: CompletableFuture[AppendResult],
+      onFlush: () => Unit = () => ()): FlussDataWriter[AppendResult] = {
+    new FlussDataWriter[AppendResult](
+      TablePath.of("test", "log_table"),
+      new StructType(),
+      new Configuration()) {
+      override val writer: TableWriter = new TableWriter {
+        override def flush(): Unit = onFlush()
+      }
+
+      override def writeRow(record: SparkAsFlussRow): CompletableFuture[AppendResult] = result
+    }
+  }
 }
 
 object SparkWriteTest {