[api][runtime][java][python] Add composable durable futures - #1127
Conversation
|
@pltbkd @da-daken @joeyutong Would you mind taking a look at this PR at your convenience? |
da-daken
left a comment
There was a problem hiding this comment.
LGTM! Just a non-blocking comment—take a look when you get a chance.
| * the callable to a thread pool, and resumes when complete. On JDK < 21, this falls back to | ||
| * synchronous execution. | ||
| * <p>Creating the returned handle does not execute the callable or reserve durable state. Use | ||
| * {@link #await(DurableFuture)} to execute a single call, or {@link #gather(List)} to compose |
There was a problem hiding this comment.
Would it be better to add a description about parallelism?
|
Hi @wenjin272, thanks for the PR — overall LGTM. I have a suggestion about the API. Do you think it's better to put
btw, |
Generated-by: Codex (GPT-5) Co-authored-by: Codex <codex@openai.com>
4f11f50 to
4ccd023
Compare
Reuse locally completed durable outcomes and batch only unresolved calls while preserving result order. Simplify Java future validation and cover success, failure, and mixed-resolution cases. Generated-by: Codex (GPT-5) Co-authored-by: Codex <codex@openai.com>
Keep interrupted or cancelled durable resolutions out of the completed-outcome cache so gather cannot convert control flow into a business failure. Cover initial, pending, and reconciled resolution paths. Generated-by: Codex (GPT-5) Co-authored-by: Codex <codex@openai.com>
Linked issue: #1122
Purpose of change
This makes asynchronous durable calls composable in Java and Python without requiring users to construct a separate batch-call descriptor.
Runtime flow
durableExecuteAsync/durable_execute_asynccreates a cold, runtime-ownedDurableFuture; creation neither reserves a durable slot nor starts the callable.ctx.await(...)or Pythonawaitdelegates to the existing recovery state machine, which replays a terminal result, reconciles or re-executes a pending call, or reserves and executes a new call.ctx.gather(...)plans the complete ordered batch, reserves all required pending slots, and only then submits uncached calls.Key decisions
DurableFutureis an opaque, context-owned handle rather than ajava.util.concurrent.Future. Java waits go throughctx.await()so JDK 21 execution can yield through the runtime continuation instead of blocking withFuture.get().gatheraccepts only unique, unresolved single-call handles created by the same context. This prevents ambiguous slot ownership and reuse.Behavioral Semantics
awaitorgatherBehavioral contracts:
Outcome.failurevalues.InterruptedExceptionremains a cancellation signal in Java: it is propagated, the interrupt flag is restored, and interrupted/unstarted slots remain pending.asynciofutures.Tests
testDurableExecuteAsyncCreatesDeferredReusableHandle;test_flink_runner_context_async_future_is_deferred_and_reusabletestGatherReservesBatchBeforeExecutionAndCompletesChildren;test_flink_runner_context_gather_reserves_before_executionToolCallActiontests, including parallel business and infrastructure failuresVerified:
./tools/lint.sh --check../tools/build.sh --javaon JDK 17: all 35 modules built successfully with tests skipped.Not verified: external-service E2E execution, live Flink checkpoint recovery, and execution of the JDK 21 continuation path (compile-only in this environment).
Implementation invariants and evidence
API
This is a breaking pre-0.4 Java API change:
durableExecuteAsyncnow returnsDurableFuture<T>, and immediate callers migrate toctx.await(ctx.durableExecuteAsync(...)). Java addsawaitandgather, and removesdurableExecuteAllAsync.Python direct-await usage remains source-compatible. Python adds public
DurableFutureandctx.gather(...), removes publicDurableCall/AsyncExecutionResult, and removesdurable_execute_all_asyncin favor of composing single-call handles.Documentation
doc-neededdoc-not-neededdoc-includedWas this patch authored or co-authored using generative AI tooling?
Generated-by: Codex (GPT-5)