feat: add custom extension operations API - #607
Conversation
03121c7 to
a73d2d0
Compare
This comment has been minimized.
This comment has been minimized.
| /** Returns the operation type derived from the sub-type. */ | ||
| public OperationType operationType() { | ||
| return subType.getOperationType(); | ||
| public record OperationIdentifier(String operationId, String name, OperationType operationType, String subType) { |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
| validateSubType(subType); | ||
| Objects.requireNonNull(function, "function cannot be null"); | ||
| Objects.requireNonNull(config, "config cannot be null"); | ||
| claim(); |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
| */ | ||
| public class ErrorHandlingExample extends DurableHandler<Object, String> { | ||
|
|
||
| private static final Logger logger = LoggerFactory.getLogger(ErrorHandlingExample.class); |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
This comment has been minimized.
This comment has been minimized.
| // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| package software.amazon.lambda.durable.operation; | ||
| package software.amazon.lambda.durable.primitive; |
There was a problem hiding this comment.
Codex AI review
[P1] Retain compatibility classes at the old public FQCNs. This move, together with removal of the other public operation engine classes, makes existing clients fail to compile or link (NoClassDefFoundError) when upgrading to 2.1.1. Keep deprecated forwarding classes under software.amazon.lambda.durable.operation, or defer their removal to a major release.
| /** Returns the operation type derived from the sub-type. */ | ||
| public OperationType operationType() { | ||
| return subType.getOperationType(); | ||
| public record OperationIdentifier(String operationId, String name, OperationType operationType, String subType) { |
There was a problem hiding this comment.
Codex AI review
[P1] Preserve the existing constructor and accessor descriptors. The public record previously accepted (String, String, OperationSubType) and exposed OperationSubType subType(). Replacing both breaks source and binary compatibility. Retain the three-argument constructor and enum accessor, and expose custom subtype strings through a separately named accessor such as subTypeValue().
| Completion awaitCompletion(ExpectedCompletionStatus expectedCompletionStatus) { | ||
| while (true) { |
There was a problem hiding this comment.
Codex AI review
[P2] Restore unrecoverable handling for coordinator faults. The former concurrency engine converted unexpected exceptions, including failures from custom shouldComplete callbacks, into IllegalDurableOperationException and terminated execution. Exceptions escaping this loop are now caught by ChildContextPrimitive, checkpointed as a recoverable failed map/parallel context, and can be caught by user code. Wrap this loop with the previous policy: rethrow suspension/unrecoverable signals and convert other failures to IllegalDurableOperationException; add an integration test for a throwing completion callback.
| var localOperationId = String.valueOf(operationCounter.incrementAndGet()); | ||
| if (!allocatedLocalIds.add(localOperationId)) { | ||
| throw new IllegalArgumentException("Local operation ID is already in use: " + localOperationId); |
There was a problem hiding this comment.
Codex AI review
[P2] Skip custom-claimed numeric IDs instead of failing. After reserving custom local ID "2", the counter is 1, so the next sequential reservation calculates "2" and throws. This contradicts the new documented contract that generated IDs skip claimed values and makes valid mixed custom/sequential workflows fail before creating an operation. Increment in a loop until allocatedLocalIds.add(...) succeeds, and update the collision test accordingly.
| var virtualEmptyMap = itemList.isEmpty() && !context.getDurableConfig().shouldCheckpointEmptyMap(); | ||
| var parentConfig = parentContextConfig(mapConfig.serDes(), virtualEmptyMap).toBuilder() |
There was a problem hiding this comment.
Codex AI review
[P2] Preserve the replay-to-execution transition for non-checkpointed empty maps. Making this parent virtual bypasses BasePrimitive.execute()'s missing-operation lookup and setExecutionMode() call. On replay, the enclosing context therefore remains replaying after reaching the empty map, so isReplaying() is incorrect and subsequent durable logs can be suppressed. Keep the map uncheckpointed while explicitly performing the transition, and cover this with a replay/logging test.
Codex AI reviewFound five actionable issues affecting compatibility, replay semantics, and execution lifecycle. Tests were not run per review constraints. Reviewed commit |
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
Issue Link, if available
Closes #571
Description
ExtensionContextandExtensionOperationSPI for composing SDK-owned durable primitives with stable reservations, custom subtypes, stateful step replay, and configurable child contexts.Durable*Operationfacades and move backend engines into theprimitivepackage.DurableContext,ParallelDurableFuture, and compatibility config APIs.Demo/Screenshots
Not applicable. This change adds Java SDK APIs and internal architecture; no visual interface is changed.
Checklist
Testing
Unit Tests
Yes. Added and updated unit coverage for extension reservations, stateful steps, child-context replay, operation facades/config conversion, primitive implementations, deterministic IDs, plugin metadata, and compatibility APIs.
Full SDK result: 1,202 tests passed.
Integration Tests
Yes. Added integration coverage for extension operations, static operation facades, extension concurrency, plugins, replay, callbacks, retries, map, and parallel behavior.
Full integration result: 413 tests passed.
Examples
No new example was required. Existing examples remain source-compatible and passed their test suite: 120 tests passed, with 34 cloud-only tests skipped by default.
Verification command:
The OpenTelemetry module also passed 163 tests. Spotless and diff checks pass.