This is an automated email from the ASF dual-hosted git repository.
jason810496 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 46f3badb3df [v3-4-test] Warn about oversized task state values from
language SDK tasks (#74945) (#75299)
46f3badb3df is described below
commit 46f3badb3df97b20dcb2f6acdf5ad96c3baf3c48
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Oct 22 09:05:07 2026 +0800
[v3-4-test] Warn about oversized task state values from language SDK tasks
(#74945) (#75299)
* Warn about oversized task state values from language SDK tasks
* Write the oversized task state warning to the task log
(cherry picked from commit eccdab540cf52d11c34c9706a40cfcdd5790ab04)
Co-authored-by: ChickenBenny <[email protected]>
---
go-sdk/pkg/execution/client.go | 4 --
.../main/kotlin/org/apache/airflow/sdk/Client.kt | 2 -
.../src/airflow/sdk/coordinators/_subprocess.py | 18 +++++++++
.../tests/task_sdk/coordinators/test_subprocess.py | 44 ++++++++++++++++++++++
ts-sdk/src/coordinator/client.ts | 3 --
5 files changed, 62 insertions(+), 9 deletions(-)
diff --git a/go-sdk/pkg/execution/client.go b/go-sdk/pkg/execution/client.go
index 8fda6b0864b..f8263a8236d 100644
--- a/go-sdk/pkg/execution/client.go
+++ b/go-sdk/pkg/execution/client.go
@@ -477,10 +477,6 @@ func (s taskStateStore) Set(
return fmt.Errorf("cannot set task state key %q: %w", key, err)
}
- // TODO: warn when the serialized value exceeds the deployment's
- // [state_store] max_value_storage_bytes, matching Python's
- // airflow.sdk.execution_time.context task store setter.
-
_, err = s.client.comm.Communicate(ctx, genmodels.SetTaskStateStore{
TIID: s.client.tiID,
Key: key,
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 fa3355fc961..d8cb35734fe 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
@@ -297,8 +297,6 @@ class TaskStateStore internal constructor(
)
else -> now.plus(retention)
}
- // TODO: warn when the serialized value exceeds [state_store]
max_value_storage_bytes once the
- // coordinator passes it to the JVM, as the Python accessor does.
impl.setTaskStateStore(tiId = details.ti.id, key = key, value = value,
expiresAt = expiresAt)
}
diff --git a/task-sdk/src/airflow/sdk/coordinators/_subprocess.py
b/task-sdk/src/airflow/sdk/coordinators/_subprocess.py
index 424b10be916..0bd84e147be 100644
--- a/task-sdk/src/airflow/sdk/coordinators/_subprocess.py
+++ b/task-sdk/src/airflow/sdk/coordinators/_subprocess.py
@@ -30,6 +30,7 @@ from __future__ import annotations
import contextlib
import ipaddress
import itertools
+import json
import os
import selectors
import signal
@@ -66,6 +67,8 @@ if TYPE_CHECKING:
from airflow.dag_processing.bundles.base import BaseDagBundle # noqa:
SDK002
from airflow.sdk.api.client import Client
from airflow.sdk.api.datamodels._generated import TaskInstance,
TIRunContext
+ from airflow.sdk.execution_time.comms import SetTaskStateStore
+ from airflow.sdk.execution_time.supervisor import RequestResult
Tracked = TypeVar("Tracked", socket.socket, subprocess.Popen)
@@ -445,6 +448,21 @@ class _PopenActivitySubprocess(ActivitySubprocess):
self._close_unused_sockets(self._comm_server, self._logs_server)
return code
+ def _handle_set_task_state_store(
+ self, msg: SetTaskStateStore, log: FilteringBoundLogger, req_id: int
+ ) -> RequestResult:
+ # A Python task warns in its own setter; a language SDK runtime cannot
compute the size
+ # json.dumps gives, so the supervisor warns for it in the task log.
+ if (limit := conf.getint("state_store", "max_value_storage_bytes")) >
0:
+ if (size := len(json.dumps(msg.value))) > limit:
+ self.process_log.warning(
+ "Task store value exceeds configured
max_value_storage_bytes",
+ key=msg.key,
+ size=size,
+ max_value_storage_bytes=limit,
+ )
+ return super()._handle_set_task_state_store(msg, log, req_id)
+
def _initialize_pinned_bundle(target: BundleInfo, logger:
FilteringBoundLogger) -> BaseDagBundle:
"""
diff --git a/task-sdk/tests/task_sdk/coordinators/test_subprocess.py
b/task-sdk/tests/task_sdk/coordinators/test_subprocess.py
index 2b7615ea42c..63eafc6d1ab 100644
--- a/task-sdk/tests/task_sdk/coordinators/test_subprocess.py
+++ b/task-sdk/tests/task_sdk/coordinators/test_subprocess.py
@@ -55,6 +55,7 @@ from airflow.sdk.coordinators._subprocess import (
_start_server,
log,
)
+from airflow.sdk.execution_time.comms import SetTaskStateStore
from airflow.sdk.execution_time.coordinator import BaseCoordinator,
TaskLaunchError
from airflow.sdk.execution_time.supervisor import ActivitySubprocess
from airflow.sdk.importers import DagSourceCode, reset_importer_registry
@@ -938,6 +939,49 @@ class TestPopenActivitySubprocessStart:
assert mock_register.mock_calls == [call(ANY, ANY, ANY, ANY, data=ANY)]
+class TestPopenActivitySubprocessSetTaskStateStore:
+ @pytest.mark.parametrize(
+ ("limit", "value", "warns"),
+ [
+ # json.dumps gives '{"a": "\\u00e9"}', 15 bytes; Go's
encoding/json gives 11.
+ pytest.param("14", {"a": "é"}, True, id="over-limit"),
+ pytest.param("15", {"a": "é"}, False, id="at-limit"),
+ pytest.param("0", {"a": "é"}, False, id="limit-disabled"),
+ ],
+ )
+ def test_warns_when_value_exceeds_max_value_storage_bytes(self, limit,
value, warns):
+ client = MagicMock(spec=Client)
+ process_log = MagicMock()
+ proc = _PopenActivitySubprocess(
+ process_log=process_log,
+ id=uuid7(),
+ pid=12345,
+ stdin=MagicMock(),
+ process=MagicMock(),
+ client=client,
+ comm_server=MagicMock(spec=socket.socket),
+ logs_server=MagicMock(spec=socket.socket),
+ )
+ msg = SetTaskStateStore(ti_id=proc.id, key="k", value=value,
expires_at=None)
+
+ with (
+ conf_vars({("state_store", "max_value_storage_bytes"): limit}),
+ patch.object(_PopenActivitySubprocess, "send_msg", autospec=True),
+ ):
+ proc._handle_request(msg, log=MagicMock(), req_id=1)
+
+ client.task_state_store.set.assert_called_once_with(proc.id, "k",
value, expires_at=None)
+ if warns:
+ process_log.warning.assert_called_once_with(
+ "Task store value exceeds configured max_value_storage_bytes",
+ key="k",
+ size=15,
+ max_value_storage_bytes=14,
+ )
+ else:
+ process_log.warning.assert_not_called()
+
+
# This script connects back to the supervisor the way a language SDK runtime
does, then waits for
# StartupDetails. It exits with code 0 as soon as the supervisor sends
anything or closes the
# connection. It exits with code 3 if 10 seconds pass and neither has happened.
diff --git a/ts-sdk/src/coordinator/client.ts b/ts-sdk/src/coordinator/client.ts
index 00ba4efe56e..025dd29d68c 100644
--- a/ts-sdk/src/coordinator/client.ts
+++ b/ts-sdk/src/coordinator/client.ts
@@ -272,9 +272,6 @@ export function createCoordinatorClient(
) {
throw new TypeError("task state store value must not be null, NaN, or
Infinity");
}
- // TODO: warn when the serialized value exceeds the deployment's
- // [state_store] max_value_storage_bytes, as Python's task store setter
- // does, once the coordinator passes that setting to the runtime.
const msg: SetTaskStateStore = {
type: "SetTaskStateStore",
ti_id: tiId,