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

vatsrahul1001 pushed a commit to branch v3-4-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-4-test by this push:
     new 8dab6b9f0de [v3-4-test] Java SDK: Reject byte arrays instead of 
letting the task hang (#74218) (#74831)
8dab6b9f0de is described below

commit 8dab6b9f0de815d1ca46ba40afae990cdfa6549e
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sun Oct 18 13:13:04 2026 +0530

    [v3-4-test] Java SDK: Reject byte arrays instead of letting the task hang 
(#74218) (#74831)
    
    (cherry picked from commit 665b94999c38426f794cc95d8644028189882ead)
    
    Signed-off-by: PoAn Yang <[email protected]>
    Co-authored-by: PoAn Yang <[email protected]>
---
 .../main/kotlin/org/apache/airflow/sdk/Client.kt   |  5 +++-
 .../org/apache/airflow/sdk/execution/MsgPack.kt    | 13 +++++----
 .../org/apache/airflow/sdk/execution/CommTest.kt   | 34 ++++++++++++++++++++++
 3 files changed, 45 insertions(+), 7 deletions(-)

diff --git a/java-sdk/sdk/src/main/kotlin/org/apache/airflow/sdk/Client.kt 
b/java-sdk/sdk/src/main/kotlin/org/apache/airflow/sdk/Client.kt
index c394e270436..fa3355fc961 100644
--- a/java-sdk/sdk/src/main/kotlin/org/apache/airflow/sdk/Client.kt
+++ b/java-sdk/sdk/src/main/kotlin/org/apache/airflow/sdk/Client.kt
@@ -179,8 +179,11 @@ class Client internal constructor(
    * Pushes an XCom value for downstream tasks to read.
    *
    * @param key XCom key; defaults to [XCOM_RETURN_KEY].
-   * @param value Value to push. Must be JSON-serializable.
+   * @param value Value to push. Must be JSON-serializable. The SDK rejects 
byte
+   *    arrays, so encode binary data as a string first, for example with 
Base64.
    * @throws ApiError if the API call fails.
+   * @throws IllegalArgumentException if [value] is or contains a type that the
+   *    SDK cannot send, such as a byte array.
    */
   @JvmOverloads fun setXCom(
     key: String = XCOM_RETURN_KEY,
diff --git 
a/java-sdk/sdk/src/main/kotlin/org/apache/airflow/sdk/execution/MsgPack.kt 
b/java-sdk/sdk/src/main/kotlin/org/apache/airflow/sdk/execution/MsgPack.kt
index 48a9c259ba5..31764e28732 100644
--- a/java-sdk/sdk/src/main/kotlin/org/apache/airflow/sdk/execution/MsgPack.kt
+++ b/java-sdk/sdk/src/main/kotlin/org/apache/airflow/sdk/execution/MsgPack.kt
@@ -41,11 +41,6 @@ import java.math.BigInteger
 import java.time.OffsetDateTime
 import java.time.ZoneOffset
 
-private fun MessagePacker.packByteArray(data: ByteArray) {
-  packBinaryHeader(data.size)
-  data.forEach { packByte(it) }
-}
-
 private fun MessagePacker.packMap(data: Map<*, *>) {
   packMapHeader(data.size)
   data.forEach { (k, v) ->
@@ -71,7 +66,13 @@ fun MessagePacker.packAny(data: Any?) {
     is BigInteger -> packBigInteger(data)
     is Float -> packFloat(data)
     is Double -> packDouble(data)
-    is ByteArray -> packByteArray(data)
+    // The supervisor rejects bytes in values such as XCom values. It does not 
reply
+    // to a request it cannot decode, so the task that sent the request would 
wait forever.
+    is ByteArray ->
+      throw IllegalArgumentException(
+        "Airflow does not accept byte arrays because they are not JSON values. 
" +
+          "Encode the bytes as a string first, for example with 
java.util.Base64.",
+      )
     is String -> packString(data)
     is Map<*, *> -> packMap(data)
     is Collection<*> -> packCollection(data)
diff --git 
a/java-sdk/sdk/src/test/kotlin/org/apache/airflow/sdk/execution/CommTest.kt 
b/java-sdk/sdk/src/test/kotlin/org/apache/airflow/sdk/execution/CommTest.kt
index 20fba213948..b6c12c36a57 100644
--- a/java-sdk/sdk/src/test/kotlin/org/apache/airflow/sdk/execution/CommTest.kt
+++ b/java-sdk/sdk/src/test/kotlin/org/apache/airflow/sdk/execution/CommTest.kt
@@ -20,6 +20,7 @@
 package org.apache.airflow.sdk.execution
 
 import io.ktor.utils.io.ByteChannel
+import io.ktor.utils.io.exhausted
 import io.ktor.utils.io.readByteArray
 import io.ktor.utils.io.writeByteArray
 import kotlinx.coroutines.async
@@ -679,6 +680,39 @@ class CommsTest {
     comm.close()
   }
 
+  @Test
+  @DisplayName("setXCom rejects a byte array nested in the value without 
writing anything to the supervisor")
+  @Timeout(value = 30, unit = TimeUnit.SECONDS)
+  fun setXComRejectsByteArrayBeforeWriting() {
+    val toClient = ByteChannel(autoFlush = true)
+    val fromClient = ByteChannel(autoFlush = true)
+    val comm = CoordinatorComm(toClient, fromClient)
+    val details =
+      StartupDetails().also {
+        it.ti =
+          TaskInstance().also { ti ->
+            ti.dagId = "d"
+            ti.taskId = "t"
+            ti.runId = "r"
+          }
+      }
+    val client = PublicClient(details, CoordinatorClient(comm))
+    // Answer the first request in advance. If setXCom sent the byte array 
anyway,
+    // this reply would let setXCom return. Then assertThrows would fail at 
once
+    // instead of the test waiting for its 30-second timeout.
+    runBlocking { toClient.writeFrame(emptyResponseFrame(0)) }
+
+    val error =
+      Assertions.assertThrows(IllegalArgumentException::class.java) {
+        client.setXCom(value = mapOf("blob" to byteArrayOf(1)))
+      }
+
+    Assertions.assertTrue("Base64" in error.message.orEmpty(), "the error 
should suggest an encoding: $error")
+    runBlocking { fromClient.flushAndClose() }
+    Assertions.assertTrue(runBlocking { fromClient.exhausted() }, "nothing 
should be written to the supervisor")
+    comm.close()
+  }
+
   @Test
   @DisplayName("Should fail a pending call when the coordinator socket closes")
   @Timeout(value = 30, unit = TimeUnit.SECONDS)

Reply via email to