From 8a025879f517601163d58b165a8306b5fce4b31b Mon Sep 17 00:00:00 2001 From: Frank Chen <65260095+zhongkechen@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:27:34 -0700 Subject: [PATCH 1/2] fix(otel): preserve operation start status --- .../durable/otel/InvocationOtelPlugin.java | 8 +++---- .../otel/InvocationOtelPluginTest.java | 18 +++++++++++++++ .../lambda/durable/plugin/OperationInfo.java | 23 ++++++++++++++++++- .../durable/plugin/PluginInfoConverter.java | 3 +++ .../plugin/PluginInfoConverterTest.java | 10 ++++++-- 5 files changed, 55 insertions(+), 7 deletions(-) diff --git a/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/InvocationOtelPlugin.java b/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/InvocationOtelPlugin.java index 178c0bccc..07dd19e66 100644 --- a/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/InvocationOtelPlugin.java +++ b/otel-plugin/src/main/java/software/amazon/lambda/durable/otel/InvocationOtelPlugin.java @@ -285,9 +285,8 @@ public void onInvocationEnd(InvocationEndInfo info) { if (invocationSpan == null) return; - // End still-open operation spans without stamping a status — no terminal - // durable.operation.status means still running (STARTED). A later invocation's - // onOperationEnd emits a continuation span with the real terminal status. + // End still-open operation spans with the STARTED status set in onOperationStart. + // A later invocation's onOperationEnd emits a continuation span with the real terminal status. for (var entry : operationSpans.entrySet()) { entry.getValue().end(); } @@ -377,7 +376,8 @@ public void onOperationStart(OperationInfo info) { .setParent(parentContext) .setAttribute(DURABLE_EXECUTION_ARN, durableExecutionArn) .setAttribute(DURABLE_OPERATION_ID, info.id()) - .setAttribute(DURABLE_OPERATION_TYPE, info.type()); + .setAttribute(DURABLE_OPERATION_TYPE, info.type()) + .setAttribute(DURABLE_OPERATION_STATUS, info.status() != null ? info.status() : "STARTED"); if (info.isReplay()) { // Operation was already started in a prior invocation — use a random span ID diff --git a/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/InvocationOtelPluginTest.java b/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/InvocationOtelPluginTest.java index 1d9c5eb9d..2d1c57fa3 100644 --- a/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/InvocationOtelPluginTest.java +++ b/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/InvocationOtelPluginTest.java @@ -489,6 +489,8 @@ void operationEnd_withSuccess_setsOkOnOperationSpan() { .findFirst() .orElseThrow(); assertEquals(StatusCode.OK, operationSpan.getStatus().getStatusCode()); + assertEquals( + "SUCCEEDED", operationSpan.getAttributes().get(AttributeKey.stringKey("durable.operation.status"))); } @Test @@ -642,6 +644,22 @@ void operationNotCompleted_spanEndedAtInvocationEnd() { .findFirst() .orElseThrow(); assertEquals("my-wait", operationSpan.getName()); + assertEquals("STARTED", operationSpan.getAttributes().get(AttributeKey.stringKey("durable.operation.status"))); + } + + @Test + void operationStart_withStatus_preservesStatus() { + plugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", false, Instant.now())); + + plugin.onOperationStart( + new OperationInfo("op-1", "my-step", "STEP", "Step", null, Instant.now(), null, "PENDING", true)); + plugin.onInvocationEnd(new InvocationEndInfo("req-1", "arn:exec1", false, InvocationStatus.PENDING, null)); + + var operationSpan = spanExporter.getFinishedSpanItems().stream() + .filter(s -> "my-step".equals(s.getName())) + .findFirst() + .orElseThrow(); + assertEquals("PENDING", operationSpan.getAttributes().get(AttributeKey.stringKey("durable.operation.status"))); } @Test diff --git a/sdk/src/main/java/software/amazon/lambda/durable/plugin/OperationInfo.java b/sdk/src/main/java/software/amazon/lambda/durable/plugin/OperationInfo.java index 766a13272..046b4caa8 100644 --- a/sdk/src/main/java/software/amazon/lambda/durable/plugin/OperationInfo.java +++ b/sdk/src/main/java/software/amazon/lambda/durable/plugin/OperationInfo.java @@ -17,6 +17,7 @@ * @param startTimestamp when the operation started — on first execution this is a local {@code Instant.now()} which may * slightly differ from the timestamp recorded by the backend; on replay it comes from the backend checkpoint * @param endTimestamp when the operation ended (null if still running) + * @param status current operation status (may be null before the first checkpoint) * @param isReplay true if this operation already exists in the execution state from a prior invocation. Plugins can use * this to avoid generating duplicate span IDs. * @deprecated This is a preview API that is experimental and may be changed or removed in future releases. @@ -30,4 +31,24 @@ public record OperationInfo( String parentId, Instant startTimestamp, Instant endTimestamp, - boolean isReplay) {} + String status, + boolean isReplay) { + + /** + * Creates operation information without a status. + * + * @deprecated This is a preview API that is experimental and may be changed or removed in future releases. + */ + @Deprecated + public OperationInfo( + String id, + String name, + String type, + String subType, + String parentId, + Instant startTimestamp, + Instant endTimestamp, + boolean isReplay) { + this(id, name, type, subType, parentId, startTimestamp, endTimestamp, null, isReplay); + } +} diff --git a/sdk/src/main/java/software/amazon/lambda/durable/plugin/PluginInfoConverter.java b/sdk/src/main/java/software/amazon/lambda/durable/plugin/PluginInfoConverter.java index 9e8f79afe..58911f7be 100644 --- a/sdk/src/main/java/software/amazon/lambda/durable/plugin/PluginInfoConverter.java +++ b/sdk/src/main/java/software/amazon/lambda/durable/plugin/PluginInfoConverter.java @@ -36,6 +36,9 @@ public static OperationInfo toOperationInfo(Operation operation, OperationIdenti parentId, operation != null ? operation.startTimestamp() : Instant.now(), operation != null ? operation.endTimestamp() : null, + operation != null && operation.status() != null + ? operation.status().toString() + : null, operation != null); } diff --git a/sdk/src/test/java/software/amazon/lambda/durable/plugin/PluginInfoConverterTest.java b/sdk/src/test/java/software/amazon/lambda/durable/plugin/PluginInfoConverterTest.java index 8d00e8400..dbba0870f 100644 --- a/sdk/src/test/java/software/amazon/lambda/durable/plugin/PluginInfoConverterTest.java +++ b/sdk/src/test/java/software/amazon/lambda/durable/plugin/PluginInfoConverterTest.java @@ -7,6 +7,7 @@ import java.time.Instant; import org.junit.jupiter.api.Test; import software.amazon.awssdk.services.lambda.model.Operation; +import software.amazon.awssdk.services.lambda.model.OperationStatus; import software.amazon.lambda.durable.model.OperationIdentifier; import software.amazon.lambda.durable.model.OperationSubType; @@ -31,8 +32,11 @@ class PluginInfoConverterTest { @Test void toOperationInfo_withIdentifier_mapsAllFields() { - var operation = - Operation.builder().startTimestamp(START).endTimestamp(END).build(); + var operation = Operation.builder() + .startTimestamp(START) + .endTimestamp(END) + .status(OperationStatus.STARTED) + .build(); var info = PluginInfoConverter.toOperationInfo(operation, WAIT_FOR_CONDITION_IDENTIFIER, PARENT_ID); @@ -43,6 +47,7 @@ void toOperationInfo_withIdentifier_mapsAllFields() { assertEquals(PARENT_ID, info.parentId()); assertEquals(START, info.startTimestamp()); assertEquals(END, info.endTimestamp()); + assertEquals("STARTED", info.status()); } @Test @@ -58,6 +63,7 @@ void toOperationInfo_withIdentifier_nullOperation_usesCurrentTime() { assertNotNull(info.startTimestamp()); assertFalse(info.startTimestamp().isBefore(before)); assertNull(info.endTimestamp()); + assertNull(info.status()); } // ─── toOperationEndInfo ────────────────────────────────────────────── From 9832ed4923f6096c8b76c4c4b01906a9d0ce1e8e Mon Sep 17 00:00:00 2001 From: Frank Chen <65260095+zhongkechen@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:33:27 -0700 Subject: [PATCH 2/2] refactor(plugin): remove legacy operation info constructor --- .../durable/otel/ExecutionOtelPluginTest.java | 37 +++++++----- .../otel/InvocationOtelPluginTest.java | 60 +++++++++++-------- .../lambda/durable/plugin/OperationInfo.java | 21 +------ .../durable/plugin/PluginRunnerTest.java | 2 +- 4 files changed, 59 insertions(+), 61 deletions(-) diff --git a/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/ExecutionOtelPluginTest.java b/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/ExecutionOtelPluginTest.java index ca03c55f5..6936065c2 100644 --- a/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/ExecutionOtelPluginTest.java +++ b/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/ExecutionOtelPluginTest.java @@ -73,7 +73,7 @@ void defaultConstructor_usesGlobalSdkTracerProviderDirectly() { var defaultPlugin = new ExecutionOtelPlugin(); defaultPlugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); defaultPlugin.onOperationStart( - new OperationInfo("op-1", "step", "STEP", "Step", null, Instant.now(), null, false)); + new OperationInfo("op-1", "step", "STEP", "Step", null, Instant.now(), null, null, false)); defaultPlugin.onOperationEnd(new OperationEndInfo( "op-1", "step", "STEP", "Step", null, Instant.now(), Instant.now(), "SUCCEEDED", null, false, null)); defaultPlugin.onInvocationEnd( @@ -227,7 +227,8 @@ void retryingInvocation_invocationSpanUnset_workflowNotExported() { @Test void operationSpan_carriesAttemptNumberAtEnd() { plugin.onInvocationStart(new InvocationInfo("req-1", ARN, true, Instant.now())); - plugin.onOperationStart(new OperationInfo("op-1", "flaky", "STEP", "Step", null, Instant.now(), null, false)); + plugin.onOperationStart( + new OperationInfo("op-1", "flaky", "STEP", "Step", null, Instant.now(), null, null, false)); plugin.onOperationEnd(new OperationEndInfo( "op-1", "flaky", "STEP", "Step", null, Instant.now(), Instant.now(), "SUCCEEDED", 3, false, null)); plugin.onInvocationEnd(new InvocationEndInfo("req-1", ARN, true, InvocationStatus.SUCCEEDED, null)); @@ -263,7 +264,7 @@ void operationSpan_startsAtOperationStartTimestamp() { var opStart = Instant.parse("2026-02-01T10:00:00Z"); var opEnd = Instant.parse("2026-02-01T10:00:03Z"); plugin.onInvocationStart(new InvocationInfo("req-1", ARN, true, Instant.now())); - plugin.onOperationStart(new OperationInfo("op-1", "step-a", "STEP", "Step", null, opStart, null, false)); + plugin.onOperationStart(new OperationInfo("op-1", "step-a", "STEP", "Step", null, opStart, null, null, false)); plugin.onOperationEnd(new OperationEndInfo( "op-1", "step-a", "STEP", "Step", null, opStart, opEnd, "SUCCEEDED", null, false, null)); plugin.onInvocationEnd(new InvocationEndInfo("req-1", ARN, true, InvocationStatus.SUCCEEDED, null)); @@ -278,7 +279,8 @@ void operationSpan_startsAtOperationStartTimestamp() { @Test void operationSpan_parentedToWorkflow_linkedToInvocation() { plugin.onInvocationStart(new InvocationInfo("req-1", ARN, true, Instant.now())); - plugin.onOperationStart(new OperationInfo("op-1", "step-a", "STEP", "Step", null, Instant.now(), null, false)); + plugin.onOperationStart( + new OperationInfo("op-1", "step-a", "STEP", "Step", null, Instant.now(), null, null, false)); plugin.onOperationEnd(new OperationEndInfo( "op-1", "step-a", "STEP", "Step", null, Instant.now(), Instant.now(), "SUCCEEDED", null, false, null)); plugin.onInvocationEnd(new InvocationEndInfo("req-1", ARN, true, InvocationStatus.SUCCEEDED, null)); @@ -301,7 +303,8 @@ void operationSpan_parentedToWorkflow_linkedToInvocation() { @Test void attemptSpan_childOfOperation_linkedToInvocation() { plugin.onInvocationStart(new InvocationInfo("req-1", ARN, true, Instant.now())); - plugin.onOperationStart(new OperationInfo("op-1", "compute", "STEP", "Step", null, Instant.now(), null, false)); + plugin.onOperationStart( + new OperationInfo("op-1", "compute", "STEP", "Step", null, Instant.now(), null, null, false)); plugin.onUserFunctionStart( new UserFunctionStartInfo("op-1", "compute", "STEP", "Step", null, Instant.now(), false, 1)); plugin.onUserFunctionEnd(new UserFunctionEndInfo( @@ -333,9 +336,9 @@ void attemptSpan_childOfOperation_linkedToInvocation() { void childOperation_parentedToParentOperationSpan() { plugin.onInvocationStart(new InvocationInfo("req-1", ARN, true, Instant.now())); plugin.onOperationStart(new OperationInfo( - "op-parent", "my-context", "CONTEXT", "RunInChildContext", null, Instant.now(), null, false)); - plugin.onOperationStart( - new OperationInfo("op-child", "inner-step", "STEP", "Step", "op-parent", Instant.now(), null, false)); + "op-parent", "my-context", "CONTEXT", "RunInChildContext", null, Instant.now(), null, null, false)); + plugin.onOperationStart(new OperationInfo( + "op-child", "inner-step", "STEP", "Step", "op-parent", Instant.now(), null, null, false)); plugin.onOperationEnd(new OperationEndInfo( "op-child", "inner-step", @@ -418,7 +421,8 @@ void userFunctionSuccess_setsOkOnAttemptSpan() { @Test void operationSuccess_setsOkOnOperationSpan() { plugin.onInvocationStart(new InvocationInfo("req-1", ARN, true, Instant.now())); - plugin.onOperationStart(new OperationInfo("op-1", "step-ok", "STEP", "Step", null, Instant.now(), null, false)); + plugin.onOperationStart( + new OperationInfo("op-1", "step-ok", "STEP", "Step", null, Instant.now(), null, null, false)); plugin.onOperationEnd(new OperationEndInfo( "op-1", "step-ok", "STEP", "Step", null, Instant.now(), Instant.now(), "SUCCEEDED", null, false, null)); plugin.onInvocationEnd(new InvocationEndInfo("req-1", ARN, true, InvocationStatus.SUCCEEDED, null)); @@ -434,7 +438,7 @@ void operationEnd_withNonSuccessStatusAndNoError_leavesOperationSpanUnset() { // stamped OK — the span status stays UNSET. plugin.onInvocationStart(new InvocationInfo("req-1", ARN, true, Instant.now())); plugin.onOperationStart( - new OperationInfo("op-cancel", "step-cancel", "STEP", "Step", null, Instant.now(), null, false)); + new OperationInfo("op-cancel", "step-cancel", "STEP", "Step", null, Instant.now(), null, null, false)); plugin.onOperationEnd(new OperationEndInfo( "op-cancel", "step-cancel", @@ -482,7 +486,7 @@ void operationEnd_withNullStatusAndNoError_setsOkOnOperationSpan() { // null status and null error. This is genuine success and must be stamped OK. plugin.onInvocationStart(new InvocationInfo("req-1", ARN, true, Instant.now())); plugin.onOperationStart( - new OperationInfo("op-ctx", "my-ctx", "CONTEXT", null, null, Instant.now(), null, false)); + new OperationInfo("op-ctx", "my-ctx", "CONTEXT", null, null, Instant.now(), null, null, false)); plugin.onOperationEnd(new OperationEndInfo( "op-ctx", "my-ctx", "CONTEXT", null, null, Instant.now(), Instant.now(), null, null, false, null)); plugin.onInvocationEnd(new InvocationEndInfo("req-1", ARN, true, InvocationStatus.SUCCEEDED, null)); @@ -494,7 +498,8 @@ void operationEnd_withNullStatusAndNoError_setsOkOnOperationSpan() { @Test void operationNotCompleted_notEndedAtInvocationEnd() { plugin.onInvocationStart(new InvocationInfo("req-1", ARN, true, Instant.now())); - plugin.onOperationStart(new OperationInfo("op-1", "my-wait", "WAIT", "Wait", null, Instant.now(), null, false)); + plugin.onOperationStart( + new OperationInfo("op-1", "my-wait", "WAIT", "Wait", null, Instant.now(), null, null, false)); plugin.onInvocationEnd(new InvocationEndInfo("req-1", ARN, true, InvocationStatus.PENDING, null)); var spans = spanExporter.getFinishedSpanItems(); @@ -511,7 +516,8 @@ void operationNotCompleted_notEndedAtInvocationEnd() { void operationOpenedThenCompletedNextInvocation_exportedOnceOnOperationEnd() { // Invocation 1: operation opens but does not complete. plugin.onInvocationStart(new InvocationInfo("req-1", ARN, true, Instant.now())); - plugin.onOperationStart(new OperationInfo("op-1", "my-wait", "WAIT", "Wait", null, Instant.now(), null, false)); + plugin.onOperationStart( + new OperationInfo("op-1", "my-wait", "WAIT", "Wait", null, Instant.now(), null, null, false)); plugin.onInvocationEnd(new InvocationEndInfo("req-1", ARN, true, InvocationStatus.PENDING, null)); assertTrue( spanExporter.getFinishedSpanItems().stream() @@ -541,7 +547,8 @@ void operationOpenedThenCompletedNextInvocation_exportedOnceOnOperationEnd() { @Test void allSpansShareTraceId_acrossInvocations() { plugin.onInvocationStart(new InvocationInfo("req-1", ARN, true, Instant.now())); - plugin.onOperationStart(new OperationInfo("op-1", "step-1", "STEP", "Step", null, Instant.now(), null, false)); + plugin.onOperationStart( + new OperationInfo("op-1", "step-1", "STEP", "Step", null, Instant.now(), null, null, false)); plugin.onOperationEnd(new OperationEndInfo( "op-1", "step-1", "STEP", "Step", null, Instant.now(), Instant.now(), "SUCCEEDED", null, false, null)); plugin.onInvocationEnd(new InvocationEndInfo("req-1", ARN, true, InvocationStatus.PENDING, null)); @@ -641,7 +648,7 @@ void xrayExtraction_allSpansShareExtractedTraceId() { "Workflow"); xrayPlugin.onInvocationStart(new InvocationInfo("req-1", ARN, true, Instant.now())); xrayPlugin.onOperationStart( - new OperationInfo("op-1", "step-a", "STEP", "Step", null, Instant.now(), null, false)); + new OperationInfo("op-1", "step-a", "STEP", "Step", null, Instant.now(), null, null, false)); xrayPlugin.onOperationEnd(new OperationEndInfo( "op-1", "step-a", "STEP", "Step", null, Instant.now(), Instant.now(), "SUCCEEDED", null, false, null)); xrayPlugin.onInvocationEnd(new InvocationEndInfo("req-1", ARN, true, InvocationStatus.SUCCEEDED, null)); diff --git a/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/InvocationOtelPluginTest.java b/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/InvocationOtelPluginTest.java index 2d1c57fa3..018273260 100644 --- a/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/InvocationOtelPluginTest.java +++ b/otel-plugin/src/test/java/software/amazon/lambda/durable/otel/InvocationOtelPluginTest.java @@ -96,7 +96,7 @@ void defaultConstructor_usesGlobalSdkTracerProviderDirectly() { var defaultPlugin = new InvocationOtelPlugin(); defaultPlugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); defaultPlugin.onOperationStart( - new OperationInfo("op-1", "step", "STEP", "Step", null, Instant.now(), null, false)); + new OperationInfo("op-1", "step", "STEP", "Step", null, Instant.now(), null, null, false)); defaultPlugin.onOperationEnd(new OperationEndInfo( "op-1", "step", "STEP", "Step", null, Instant.now(), Instant.now(), "SUCCEEDED", null, false, null)); defaultPlugin.onInvocationEnd( @@ -134,7 +134,7 @@ public ContextPropagators getPropagators() { var defaultPlugin = new InvocationOtelPlugin(); defaultPlugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); defaultPlugin.onOperationStart( - new OperationInfo("op-1", "step", "STEP", "Step", null, Instant.now(), null, false)); + new OperationInfo("op-1", "step", "STEP", "Step", null, Instant.now(), null, null, false)); defaultPlugin.onOperationEnd(new OperationEndInfo( "op-1", "step", "STEP", "Step", null, Instant.now(), Instant.now(), "SUCCEEDED", null, false, null)); defaultPlugin.onInvocationEnd( @@ -248,7 +248,7 @@ void invocationSpan_hasInternalKind() { void operationSpanName_usesOperationName_withoutPrefix() { plugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); plugin.onOperationStart( - new OperationInfo("op-1", "create-greeting", "STEP", "Step", null, Instant.now(), null, false)); + new OperationInfo("op-1", "create-greeting", "STEP", "Step", null, Instant.now(), null, null, false)); plugin.onOperationEnd(new OperationEndInfo( "op-1", "create-greeting", @@ -296,7 +296,8 @@ void attemptSpanName_usesOperationNameWithAttemptNumber() { void operationEnd_withAttempt_stampsAttemptNumberOnOperationSpan() { plugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); - plugin.onOperationStart(new OperationInfo("op-1", "flaky", "STEP", "Step", null, Instant.now(), null, false)); + plugin.onOperationStart( + new OperationInfo("op-1", "flaky", "STEP", "Step", null, Instant.now(), null, null, false)); plugin.onOperationEnd(new OperationEndInfo( "op-1", "flaky", "STEP", "Step", null, Instant.now(), Instant.now(), "SUCCEEDED", 3, false, null)); plugin.onInvocationEnd(new InvocationEndInfo("req-1", "arn:exec1", true, InvocationStatus.SUCCEEDED, null)); @@ -386,7 +387,8 @@ void operationStart_createsSpan_operationEnd_endsIt() { var end = Instant.parse("2026-06-01T10:00:05Z"); // Operation span created at start - plugin.onOperationStart(new OperationInfo("op-hash-1", "my-step", "STEP", "Step", null, start, null, false)); + plugin.onOperationStart( + new OperationInfo("op-hash-1", "my-step", "STEP", "Step", null, start, null, null, false)); // Operation span ended at completion plugin.onOperationEnd(new OperationEndInfo( @@ -478,7 +480,8 @@ void userFunctionEnd_withSuccess_setsOkOnAttemptSpan() { void operationEnd_withSuccess_setsOkOnOperationSpan() { plugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); - plugin.onOperationStart(new OperationInfo("op-1", "step-ok", "STEP", "Step", null, Instant.now(), null, false)); + plugin.onOperationStart( + new OperationInfo("op-1", "step-ok", "STEP", "Step", null, Instant.now(), null, null, false)); plugin.onOperationEnd(new OperationEndInfo( "op-1", "step-ok", "STEP", "Step", null, Instant.now(), Instant.now(), "SUCCEEDED", null, false, null)); @@ -501,7 +504,7 @@ void operationEnd_withNonSuccessStatusAndNoError_leavesOperationSpanUnset() { plugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); plugin.onOperationStart( - new OperationInfo("op-cancel", "step-cancel", "STEP", "Step", null, Instant.now(), null, false)); + new OperationInfo("op-cancel", "step-cancel", "STEP", "Step", null, Instant.now(), null, null, false)); plugin.onOperationEnd(new OperationEndInfo( "op-cancel", "step-cancel", @@ -559,7 +562,7 @@ void operationEnd_withNullStatusAndNoError_setsOkOnOperationSpan() { plugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); plugin.onOperationStart( - new OperationInfo("op-ctx", "my-ctx", "CONTEXT", null, null, Instant.now(), null, false)); + new OperationInfo("op-ctx", "my-ctx", "CONTEXT", null, null, Instant.now(), null, null, false)); plugin.onOperationEnd(new OperationEndInfo( "op-ctx", "my-ctx", "CONTEXT", null, null, Instant.now(), Instant.now(), null, null, false, null)); @@ -578,7 +581,8 @@ void fullLifecycle_producesCorrectSpanHierarchy() { plugin.onInvocationStart(new InvocationInfo("req-1", arn, true, Instant.now())); // Step 1: operation starts, user function runs, operation completes - plugin.onOperationStart(new OperationInfo("op-1", "step-a", "STEP", "Step", null, Instant.now(), null, false)); + plugin.onOperationStart( + new OperationInfo("op-1", "step-a", "STEP", "Step", null, Instant.now(), null, null, false)); plugin.onUserFunctionStart( new UserFunctionStartInfo("op-1", "step-a", "STEP", "Step", null, Instant.now(), false, 1)); plugin.onUserFunctionEnd(new UserFunctionEndInfo( @@ -587,7 +591,8 @@ void fullLifecycle_producesCorrectSpanHierarchy() { "op-1", "step-a", "STEP", "Step", null, Instant.now(), Instant.now(), "SUCCEEDED", null, false, null)); // Step 2: operation starts, user function runs, operation completes - plugin.onOperationStart(new OperationInfo("op-2", "step-b", "STEP", "Step", null, Instant.now(), null, false)); + plugin.onOperationStart( + new OperationInfo("op-2", "step-b", "STEP", "Step", null, Instant.now(), null, null, false)); plugin.onUserFunctionStart( new UserFunctionStartInfo("op-2", "step-b", "STEP", "Step", null, Instant.now(), false, 1)); plugin.onUserFunctionEnd(new UserFunctionEndInfo( @@ -630,7 +635,8 @@ void operationNotCompleted_spanEndedAtInvocationEnd() { plugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); // Operation starts but never completes (e.g., wait operation, invocation suspends) - plugin.onOperationStart(new OperationInfo("op-1", "my-wait", "WAIT", "Wait", null, Instant.now(), null, false)); + plugin.onOperationStart( + new OperationInfo("op-1", "my-wait", "WAIT", "Wait", null, Instant.now(), null, null, false)); // Invocation ends without onOperationEnd being called plugin.onInvocationEnd(new InvocationEndInfo("req-1", "arn:exec1", true, InvocationStatus.PENDING, null)); @@ -719,7 +725,7 @@ void xrayExtraction_allSpansShareExtractedTraceId() { xrayPlugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); xrayPlugin.onOperationStart( - new OperationInfo("op-1", "step-a", "STEP", "Step", null, Instant.now(), null, false)); + new OperationInfo("op-1", "step-a", "STEP", "Step", null, Instant.now(), null, null, false)); xrayPlugin.onUserFunctionStart( new UserFunctionStartInfo("op-1", "step-a", "STEP", "Step", null, Instant.now(), false, 1)); xrayPlugin.onUserFunctionEnd(new UserFunctionEndInfo( @@ -805,7 +811,7 @@ void xrayExtraction_multipleInvocations_sameTraceId_unifiedTrace() { // First invocation xrayPlugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); xrayPlugin.onOperationStart( - new OperationInfo("op-1", "step-1", "STEP", "Step", null, Instant.now(), null, false)); + new OperationInfo("op-1", "step-1", "STEP", "Step", null, Instant.now(), null, null, false)); xrayPlugin.onOperationEnd(new OperationEndInfo( "op-1", "step-1", "STEP", "Step", null, Instant.now(), Instant.now(), "SUCCEEDED", null, false, null)); xrayPlugin.onInvocationEnd(new InvocationEndInfo("req-1", "arn:exec1", true, InvocationStatus.PENDING, null)); @@ -813,7 +819,7 @@ void xrayExtraction_multipleInvocations_sameTraceId_unifiedTrace() { // Second invocation (same execution, same X-Ray Root from backend) xrayPlugin.onInvocationStart(new InvocationInfo("req-2", "arn:exec1", false, Instant.now())); xrayPlugin.onOperationStart( - new OperationInfo("op-2", "step-2", "STEP", "Step", null, Instant.now(), null, false)); + new OperationInfo("op-2", "step-2", "STEP", "Step", null, Instant.now(), null, null, false)); xrayPlugin.onOperationEnd(new OperationEndInfo( "op-2", "step-2", "STEP", "Step", null, Instant.now(), Instant.now(), "SUCCEEDED", null, false, null)); xrayPlugin.onInvocationEnd( @@ -976,7 +982,7 @@ void contextOperation_doesNotCreateAttemptSpan() { // Create operation span first so the CONTEXT user function has a parent plugin.onOperationStart(new OperationInfo( - "op-1", "child-ctx", "CONTEXT", "RunInChildContext", null, Instant.now(), null, false)); + "op-1", "child-ctx", "CONTEXT", "RunInChildContext", null, Instant.now(), null, null, false)); plugin.onUserFunctionStart(new UserFunctionStartInfo( "op-1", "child-ctx", "CONTEXT", "RunInChildContext", null, Instant.now(), false, null)); @@ -1032,11 +1038,11 @@ void childOperation_parentedToParentOperationSpan() { // Parent context operation plugin.onOperationStart(new OperationInfo( - "op-parent", "my-context", "CONTEXT", "RunInChildContext", null, Instant.now(), null, false)); + "op-parent", "my-context", "CONTEXT", "RunInChildContext", null, Instant.now(), null, null, false)); // Child operation with parentId pointing to parent - plugin.onOperationStart( - new OperationInfo("op-child", "inner-step", "STEP", "Step", "op-parent", Instant.now(), null, false)); + plugin.onOperationStart(new OperationInfo( + "op-child", "inner-step", "STEP", "Step", "op-parent", Instant.now(), null, null, false)); plugin.onOperationEnd(new OperationEndInfo( "op-child", "inner-step", @@ -1090,14 +1096,16 @@ void multiInvocation_stepWaitStep_producesCorrectSpans() { // Invocation 1: step completes, wait starts plugin.onInvocationStart(new InvocationInfo("req-1", arn, true, Instant.now())); - plugin.onOperationStart(new OperationInfo("op-1", "step-A", "STEP", "Step", null, Instant.now(), null, false)); + plugin.onOperationStart( + new OperationInfo("op-1", "step-A", "STEP", "Step", null, Instant.now(), null, null, false)); plugin.onUserFunctionStart( new UserFunctionStartInfo("op-1", "step-A", "STEP", "Step", null, Instant.now(), false, 1)); plugin.onUserFunctionEnd(new UserFunctionEndInfo( "op-1", "step-A", "STEP", "Step", null, Instant.now(), Instant.now(), false, 1, true, null)); plugin.onOperationEnd(new OperationEndInfo( "op-1", "step-A", "STEP", "Step", null, Instant.now(), Instant.now(), "SUCCEEDED", null, false, null)); - plugin.onOperationStart(new OperationInfo("op-2", "pause", "WAIT", "Wait", null, Instant.now(), null, false)); + plugin.onOperationStart( + new OperationInfo("op-2", "pause", "WAIT", "Wait", null, Instant.now(), null, null, false)); plugin.onInvocationEnd(new InvocationEndInfo("req-1", arn, true, InvocationStatus.PENDING, null)); // Invocation 1 should have: step op + step attempt + wait (PENDING) + invocation = 4 @@ -1110,7 +1118,8 @@ void multiInvocation_stepWaitStep_producesCorrectSpans() { plugin.onInvocationStart(new InvocationInfo("req-2", arn, false, Instant.now())); plugin.onOperationEnd(new OperationEndInfo( "op-2", "pause", "WAIT", "Wait", null, Instant.now(), Instant.now(), "SUCCEEDED", null, false, null)); - plugin.onOperationStart(new OperationInfo("op-3", "step-B", "STEP", "Step", null, Instant.now(), null, false)); + plugin.onOperationStart( + new OperationInfo("op-3", "step-B", "STEP", "Step", null, Instant.now(), null, null, false)); plugin.onUserFunctionStart( new UserFunctionStartInfo("op-3", "step-B", "STEP", "Step", null, Instant.now(), false, 1)); plugin.onUserFunctionEnd(new UserFunctionEndInfo( @@ -1144,7 +1153,7 @@ void crossInvocation_stepRetry_attemptsParentedToRespectiveInvocations() { // Invocation 1: step starts, attempt 1 fails, invocation suspended during retry poll plugin.onInvocationStart(new InvocationInfo("req-1", arn, true, Instant.now())); plugin.onOperationStart( - new OperationInfo("op-1", "process-payment", "STEP", "Step", null, Instant.now(), null, false)); + new OperationInfo("op-1", "process-payment", "STEP", "Step", null, Instant.now(), null, null, false)); plugin.onUserFunctionStart( new UserFunctionStartInfo("op-1", "process-payment", "STEP", "Step", null, Instant.now(), false, 1)); plugin.onUserFunctionEnd(new UserFunctionEndInfo( @@ -1187,7 +1196,7 @@ void crossInvocation_stepRetry_attemptsParentedToRespectiveInvocations() { plugin.onInvocationStart(new InvocationInfo("req-2", arn, false, Instant.now())); // isReplay=true: this operation already exists in the execution state plugin.onOperationStart( - new OperationInfo("op-1", "process-payment", "STEP", "Step", null, Instant.now(), null, true)); + new OperationInfo("op-1", "process-payment", "STEP", "Step", null, Instant.now(), null, null, true)); plugin.onUserFunctionStart( new UserFunctionStartInfo("op-1", "process-payment", "STEP", "Step", null, Instant.now(), false, 2)); plugin.onUserFunctionEnd(new UserFunctionEndInfo( @@ -1273,7 +1282,8 @@ void workflowSpan_notExportedOnNonTerminal() { @Test void operationAndAttemptSpans_linkToWorkflowSpan() { plugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec-wf", true, Instant.now())); - plugin.onOperationStart(new OperationInfo("op-1", "step-a", "STEP", "Step", null, Instant.now(), null, false)); + plugin.onOperationStart( + new OperationInfo("op-1", "step-a", "STEP", "Step", null, Instant.now(), null, null, false)); plugin.onUserFunctionStart( new UserFunctionStartInfo("op-1", "step-a", "STEP", "Step", null, Instant.now(), false, 1)); plugin.onUserFunctionEnd(new UserFunctionEndInfo( @@ -1303,7 +1313,7 @@ void operationLinksToWorkflow_withXRayContext() { false); xrayPlugin.onInvocationStart(new InvocationInfo("req-1", "arn:exec1", true, Instant.now())); xrayPlugin.onOperationStart( - new OperationInfo("op-1", "step-a", "STEP", "Step", null, Instant.now(), null, false)); + new OperationInfo("op-1", "step-a", "STEP", "Step", null, Instant.now(), null, null, false)); xrayPlugin.onOperationEnd(new OperationEndInfo( "op-1", "step-a", "STEP", "Step", null, Instant.now(), Instant.now(), "SUCCEEDED", null, false, null)); xrayPlugin.onInvocationEnd(new InvocationEndInfo("req-1", "arn:exec1", true, InvocationStatus.SUCCEEDED, null)); diff --git a/sdk/src/main/java/software/amazon/lambda/durable/plugin/OperationInfo.java b/sdk/src/main/java/software/amazon/lambda/durable/plugin/OperationInfo.java index 046b4caa8..24744c868 100644 --- a/sdk/src/main/java/software/amazon/lambda/durable/plugin/OperationInfo.java +++ b/sdk/src/main/java/software/amazon/lambda/durable/plugin/OperationInfo.java @@ -32,23 +32,4 @@ public record OperationInfo( Instant startTimestamp, Instant endTimestamp, String status, - boolean isReplay) { - - /** - * Creates operation information without a status. - * - * @deprecated This is a preview API that is experimental and may be changed or removed in future releases. - */ - @Deprecated - public OperationInfo( - String id, - String name, - String type, - String subType, - String parentId, - Instant startTimestamp, - Instant endTimestamp, - boolean isReplay) { - this(id, name, type, subType, parentId, startTimestamp, endTimestamp, null, isReplay); - } -} + boolean isReplay) {} diff --git a/sdk/src/test/java/software/amazon/lambda/durable/plugin/PluginRunnerTest.java b/sdk/src/test/java/software/amazon/lambda/durable/plugin/PluginRunnerTest.java index 941d18c1e..d34b5a8ca 100644 --- a/sdk/src/test/java/software/amazon/lambda/durable/plugin/PluginRunnerTest.java +++ b/sdk/src/test/java/software/amazon/lambda/durable/plugin/PluginRunnerTest.java @@ -146,7 +146,7 @@ private static InvocationEndInfo invocationEndInfo() { } private static OperationInfo operationInfo() { - return new OperationInfo("op-1", "test-step", "STEP", null, null, Instant.now(), null, false); + return new OperationInfo("op-1", "test-step", "STEP", null, null, Instant.now(), null, null, false); } private static OperationEndInfo operationEndInfo() {