This is an automated email from the ASF dual-hosted git repository.
wenjin272 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/flink-agents.git
The following commit(s) were added to refs/heads/main by this push:
new 24c992353 [runtime][python] Propagate durable call result persistence
failures (#1211)
24c992353 is described below
commit 24c992353e1fe59520db7f8c3ac9b89616915256
Author: Sangkyoon Nam <[email protected]>
AuthorDate: Fri Oct 9 12:53:52 2026 +0900
[runtime][python] Propagate durable call result persistence failures (#1211)
Generated-by: Claude Code 2.1.292 (Claude Opus 5.5)
---
.../flink_agents/runtime/flink_runner_context.py | 15 ++--
.../test_flink_runner_context_reconcilable.py | 87 ++++++++++++++++++++++
2 files changed, 95 insertions(+), 7 deletions(-)
diff --git a/python/flink_agents/runtime/flink_runner_context.py
b/python/flink_agents/runtime/flink_runner_context.py
index 048ba04bc..140d5b29c 100644
--- a/python/flink_agents/runtime/flink_runner_context.py
+++ b/python/flink_agents/runtime/flink_runner_context.py
@@ -969,14 +969,15 @@ class FlinkRunnerContext(RunnerContext,
ExecutionReporter):
result_payload, exception_payload = self._serialize_call_payloads(
result, exception
)
-
- self._j_runner_context.recordCallCompletion(
- function_id, result_payload, exception_payload
- )
except Exception as e:
- # If Java method doesn't exist, silently ignore
- if "recordCallCompletion" not in str(e):
- logger.warning("Failed to record call completion: %s", e)
+ logger.warning("Failed to record call completion: %s", e)
+ return
+
+ # A failed write must reach the caller: returning normally would let
the
+ # action proceed although recovery could execute the call again.
+ self._j_runner_context.recordCallCompletion(
+ function_id, result_payload, exception_payload
+ )
@staticmethod
def _serialize_call_payloads(
diff --git
a/python/flink_agents/runtime/tests/test_flink_runner_context_reconcilable.py
b/python/flink_agents/runtime/tests/test_flink_runner_context_reconcilable.py
index 7c272e5eb..308639a26 100644
---
a/python/flink_agents/runtime/tests/test_flink_runner_context_reconcilable.py
+++
b/python/flink_agents/runtime/tests/test_flink_runner_context_reconcilable.py
@@ -1353,3 +1353,90 @@ def
test_flink_runner_context_gather_respects_max_parallelism() -> None:
"SUCCEEDED",
]
assert j_runner_context.current_call_index == 4
+
+
+class _FailingPersistJavaRunnerContext(_FakeJavaRunnerContext):
+ """Fails every call-result write the way ``DurableExecutionManager``
does."""
+
+ def recordCallCompletion(
+ self,
+ function_id: str,
+ result_payload: bytes | None,
+ exception_payload: bytes | None,
+ ) -> None:
+ self.operations.append("record")
+ msg = "java.lang.RuntimeException: Failed to persist ActionState"
+ raise RuntimeError(msg)
+
+
+def _durable_call(ctx: FlinkRunnerContext, asynchronous: bool, func: Any) ->
Any:
+ if asynchronous:
+ return _run_async(ctx.durable_execute_async(func))
+ return ctx.durable_execute(func)
+
+
[email protected]("asynchronous", [False, True])
+def test_call_result_persistence_failure_reaches_the_caller(
+ asynchronous: bool,
+) -> None:
+ calls = []
+
+ def call() -> str:
+ calls.append(1)
+ return "receipt-1"
+
+ store = _FailingPersistJavaRunnerContext()
+ ctx = _create_runner_context(store)
+ try:
+ with pytest.raises(RuntimeError, match="Failed to persist
ActionState"):
+ _durable_call(ctx, asynchronous, call)
+ finally:
+ _close_runner_context(ctx)
+
+ assert len(calls) == 1
+ assert store.operations.count("record") == 1
+ assert store.call_results == []
+
+
[email protected]("asynchronous", [False, True])
+def test_result_that_cannot_be_pickled_is_returned_unrecorded(
+ asynchronous: bool,
+) -> None:
+ lock = threading.Lock()
+
+ def call() -> Any:
+ return lock
+
+ store = _FakeJavaRunnerContext()
+ ctx = _create_runner_context(store)
+ try:
+ result = _durable_call(ctx, asynchronous, call)
+ finally:
+ _close_runner_context(ctx)
+
+ assert result is lock
+ assert "record" not in store.operations
+ assert store.call_results == []
+
+
[email protected]("asynchronous", [False, True])
+def test_persistence_failure_replaces_the_call_exception(asynchronous: bool)
-> None:
+ calls = []
+
+ def call() -> str:
+ calls.append(1)
+ msg = "call failed"
+ raise ValueError(msg)
+
+ store = _FailingPersistJavaRunnerContext()
+ ctx = _create_runner_context(store)
+ try:
+ with pytest.raises(RuntimeError, match="Failed to persist
ActionState") as info:
+ _durable_call(ctx, asynchronous, call)
+ finally:
+ _close_runner_context(ctx)
+
+ assert info.value.__cause__ is None
+ assert len(calls) == 1
+ assert store.operations.count("record") == 1
+ assert store.call_results == []