yunfengzhou-hub opened a new pull request, #1237: URL: https://github.com/apache/flink-agents/pull/1237
## Summary - With `num-async-threads=1` (and under enough nested or concurrent waits at any size), a parent action awaiting an internal sub-agent held the only pool worker via a blocking `responseFuture.get()`, while the child's own durable async call needed a worker from the same finite pool to make progress. The result was a self-deadlock — neither side could advance. - The fix lets the parent's wait yield its continuation instead of occupying a pool worker. A new runtime-internal marker interface, `ContinuationNativeCallable<T> extends DurableCallable<T>`, identifies callables whose body suspends by yielding the caller's continuation and must therefore run inline on the mailbox thread rather than being dispatched to an async-pool worker. `JavaRunnerContextImpl.executionBoundary()` routes marked callables through `callable::call`; all others continue to go through `resolveAsync`. `ContinuationActionExecutor.awaitFuture()` bridges the target `CompletableFuture` into a `Future<?>` registered as `pendingFuture` and yields via `Continuation.yield`, so `executeAction`'s existing `hasPendingAsync` gate drives resumption through the operator's mail loop. Zero pool workers are held during the wait, so deadlock is impossible at any pool size, nesting depth, or concurrency level. Durable state still records the real `SubagentResult` because the callable returns it directly. - `InternalSubagentSetup.prepare()` now returns a `ContinuationNativeCallable` and awaits the child via `JavaRunnerContextImpl.awaitCompletableFuture`. The public blocking `RunnerContextImpl.awaitSubagentCall` entry point used by the Python cross-language path is preserved unchanged. `DeferredSubagentFuture` is untouched; the combined `awaitAll` path still routes through `DeferredSubagentFuture.execute()` and therefore through the marker. ## Test plan - [x] Three new `@Timeout(30)` tests at `num-async-threads=1` cover the issue's full stated scope: single call, nested calls, combined concurrent calls (`InternalSubagentDeadlockTest`). All three time out without the fix and complete promptly with it (mutation verified). - [x] Full runtime suite passes on JDK 21: 1243 tests, 0 failures, 0 errors, 27 skipped. - [x] Full runtime suite passes on JDK 11: 1239 tests, 0 failures, 0 errors, 19 skipped (multi-release compile of the JDK11 fallback variant). - [x] Spotless check passes under JDK 11 (where google-java-format actually runs). - [x] Existing `InternalSubagentCallTest` (6/6) and `InternalSubagentRecoveryTest` (14/14) pass, including the child-failure path that exercises `joinCompletableFuture`'s exception unwrapping. ## Compatibility impact - No public API change. `ContinuationNativeCallable` lives in `runtime`, not `api`; users cannot and need not implement it. - The Python cross-language path continues to use the blocking `RunnerContextImpl.awaitSubagentCall` entry point off-mailbox on its own async worker; behavior unchanged. - Recovery semantics unchanged: `durableExecuteCompletionOnly` is untouched, and the callable returns the real `SubagentResult`, so replayed results match what was persisted before this fix. Generated-by: Qoder (Claude Sonnet 4.5) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
