Skip to content

feat: add invocation-owned executor isolation - #735

Open
zhongkechen wants to merge 157 commits into
fix/lmi-invocation-lifecyclefrom
feature/invocation-executor-isolation
Open

zhongkechen wants to merge 157 commits into
fix/lmi-invocation-lifecyclefrom
feature/invocation-executor-isolation

Conversation

@zhongkechen

@zhongkechen zhongkechen commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Issue Link, if available

Fixes #840. Stacked on #733. General shared/nested bounded-pool starvation remains in #839; hard cleanup bounds and LMI worker recovery remain in #726.

Description

Concurrent LMI invocations can exhaust a shared bounded executor with root handlers waiting for queued steps. withInvocationExecutorFactory(...) gives each invocation a fresh executor for its root, steps, child contexts and coordinators. withExecutorService(...) remains caller-owned and shared.

The runtime uses the 3.x plugin factories and preserves main's handler-owned Start/End lifecycle. It waits for admitted work and End before shutting down an invocation-owned executor. Cancellation closes admission and requests interruption; non-cooperative work can still delay shutdown beyond the cleanup budget.

The factory must return a fresh, non-null, usable executor. An identity lease prevents concurrent reuse, and failed setup cleans up a newly acquired executor without touching one owned by another invocation. Configuring both executor modes is rejected. toBuilder() and local replay preserve the resolved factories, while each invocation receives a new executor.

Cleanup retains the selected lifecycle failure or first JVM fatal, with later setup, shutdown, termination and state-query failures attached as diagnostics. Discarded completion wrappers preserve their diagnostics without rereading cause accessors or creating suppression cycles. A retained application ExecutionException keeps its identity and cause graph. Diagnostic transfer itself does not promote a suppressed fatal into the selected failure. Terminal ACK, delivery-error and implicit-close behavior retain their existing priorities.

The LMI fixed2 fixture uses per-invocation fixed(2) pools. The shared nested2 fixture remains diagnostic coverage outside the 12 gating cloud cases; this PR does not claim to solve arbitrary nested bounded-pool starvation.

Validation

Validated tree: 205034e916871f71b9e27474661b18d5bfa5f30c, including main 1c492229 and the latest #733 implementation.

  • Java 17: 3,918 tests passed, 36 conditional/cloud tests skipped. Java 25: 3,923 passed, 34 skipped.
  • All 240 focused runtime controls passed, including public setup failures, wrapper diagnostics, owned-executor cleanup and plugin/no-plugin paths.
  • Java 25 exposed an existing observer-publication timing assertion. The fixture now checks actual continuation exit before close, then waits for its observation future with a bound. Both held-close cases and all four observer-calls-close controls pass on both JVMs. Runtime ordering is unchanged; passing full-suite results were retained and the remaining Java 25 modules completed successfully.
  • Final 3.0.0 packaging and Spotless passed. Four installed OTel API cases and three local LMI controls passed against the packaged SDK JAR, with class origin verified. The unchanged harness retains its prior 203 Python checks.
  • Deployed cloud validation of this final tree remains pending. Merge fix: enforce invocation deadlines while awaiting owner cleanup #733 first, then retarget this PR to main.

Checklist

  • Shared executors remain caller-owned.
  • Owned executors close after actual work and End.
  • Factory identity, configuration-copy/replay, setup and cleanup failures are covered.
  • The partial-mitigation boundary is documented.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@zhongkechen
zhongkechen added this pull request to stack #732 September 25, 2026 17:28
@zhongkechen
zhongkechen force-pushed the feature/invocation-executor-isolation branch from 5433d65 to 703c7e0 Compare September 29, 2026 17:49
Frank Chen and others added 23 commits September 29, 2026 15:12
Pair request-local backend call/finally-exit IDs; require original peer task/wrapper occupancy at probe admission; establish a bounded child barrier before nested scheduling. Add counterexample and real-thread fixture tests, and align the fixture parent with the rebased 2.2.1 reactor. Locally reviewed and validated before publication.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@zhongkechen
zhongkechen force-pushed the feature/invocation-executor-isolation branch from 703c7e0 to 241c152 Compare September 29, 2026 22:18
@zhongkechen
zhongkechen temporarily deployed to ai-pr-review-runtime October 21, 2026 19:31 — with GitHub Actions Inactive
@github-actions

This comment has been minimized.

@zhongkechen
zhongkechen temporarily deployed to ai-pr-review-runtime October 21, 2026 20:01 — with GitHub Actions Inactive
@github-actions

This comment has been minimized.

@zhongkechen
zhongkechen temporarily deployed to ai-pr-review-runtime October 21, 2026 20:59 — with GitHub Actions Inactive
Comment on lines +1933 to +1936
cleanupInterrupted.set(true);
executorService.shutdownNow();
return combineFailures(
priorFailure, lifecycleFailure("Interrupted while shutting down invocation executor", interrupted));

This comment was marked as outdated.

Comment on lines +1743 to +1745
shutdownFailure = normalizeOwnedExecutorFailure(
inspectOwnedExecutorFailure(shutdownFailure), isFatal(failure) ? (Error) failure : null);
failure = combineFailures(failure, shutdownFailure);

This comment was marked as outdated.

@github-actions

This comment has been minimized.

Resolve the stacked-branch conflicts against fix/lmi-invocation-lifecycle
at 318d78b while retaining invocation-owned executor cleanup, failure
diagnostics, and the executor-isolation lifecycle tests.

Validation:
- Java 17 clean verify and Spotless: 3,985 passed, 33 conditional/cloud skips.
- Java 25 focused lifecycle tests: 397 passed.
- LMI Python fixture tests: 203 passed.
- Packaged OpenTelemetry API compatibility: 4 cases passed.
@zhongkechen
zhongkechen temporarily deployed to ai-pr-review-runtime October 27, 2026 17:42 — with GitHub Actions Inactive
Comment on lines +1743 to +1744
shutdownFailure = normalizeOwnedExecutorFailure(
inspectOwnedExecutorFailure(shutdownFailure), isFatal(failure) ? (Error) failure : null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex AI review · Finding arf_v1_bzoq6c7q7wr72nt5c4p3wdy7xx

[P2] Preserve nonfatal wrapper diagnostics during cleanup

If the final isShutdown() query throws CompletionException(cause) with a suppressed diagnostic after successful shutdown, this call selects the nonfatal cause without transferring the wrapper's diagnostics. The wrapper is discarded before DurableExecutor can normalize it, so those failures become unreachable. Preserve diagnostics from every discarded completion wrapper while retaining application ExecutionException identity and cause graphs. Add a nonfatal state-query wrapper regression.

To suppress repeats, reply /ai verdict deferred, /ai verdict false-positive, or /ai verdict out-of-scope on the first nonblank line. Add rationale on later lines. A new /ai verdict accepted allows future repeats again.

Comment on lines +1932 to +1936
} catch (InterruptedException interrupted) {
cleanupInterrupted.set(true);
executorService.shutdownNow();
return combineFailures(
priorFailure, lifecycleFailure("Interrupted while shutting down invocation executor", interrupted));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex AI review · Finding arf_v1_vgz5islhufc7okvs3qyjuzz6cb

[P2] Capture interruption before attempting forced shutdown

When awaitTermination() throws InterruptedException and shutdownNow() subsequently throws, this catch exits before creating the lifecycle failure containing the interruption. Although the interrupt flag is restored, the original exception is absent from the caller's diagnostic graph. Capture the interruption failure before invoking shutdownNow(), then aggregate any fallback failure while preserving fatal precedence. Extend the existing INTERRUPTED_FALLBACK regression to assert that both exceptions remain reachable.

To suppress repeats, reply /ai verdict deferred, /ai verdict false-positive, or /ai verdict out-of-scope on the first nonblank line. Add rationale on later lines. A new /ai verdict accepted allows future repeats again.

@github-actions

Copy link
Copy Markdown
Contributor

Codex AI review

Two P2 cleanup findings remain:

  • sdk/src/main/java/software/amazon/lambda/durable/execution/ExecutionManager.java:1743 — lost nonfatal wrapper diagnostics.
  • sdk/src/main/java/software/amazon/lambda/durable/execution/ExecutionManager.java:1934 — lost interruption when forced shutdown fails.

Static inspection only; tests were not executed.

Reviewed commit cd59f6542248bbdc4a98c85d12e5eee64b1ecb7e. Workflow run

This branch was previously deployed

1 inactive deployment
ai-pr-review-runtime — cd59f654 Deployed Oct 27, 2026 by zhongkechen via ai-pr-review / Codex review / Generate Codex review #2737
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Add invocation-owned executor isolation

1 participant