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]

Reply via email to