diff --git a/sdk-integration-tests/src/test/java/software/amazon/lambda/durable/ExceptionIntegrationTest.java b/sdk-integration-tests/src/test/java/software/amazon/lambda/durable/ExceptionIntegrationTest.java index 5445a4f3d..0e0e6f4c3 100644 --- a/sdk-integration-tests/src/test/java/software/amazon/lambda/durable/ExceptionIntegrationTest.java +++ b/sdk-integration-tests/src/test/java/software/amazon/lambda/durable/ExceptionIntegrationTest.java @@ -166,7 +166,8 @@ void testStepInterruptedExceptionForAtMostOnceAfterCheckpointLoss() { return "result"; }, StepConfig.builder() - .semantics(StepSemantics.AT_MOST_ONCE_PER_RETRY) + .semanticsPerRetry(StepSemantics.AT_MOST_ONCE_PER_RETRY) + .retryStrategy(RetryStrategies.Presets.NO_RETRY) .build()); }); @@ -199,7 +200,8 @@ void testStepInterruptedExceptionCanBeCaughtForRecovery() { return "payment-success"; }, StepConfig.builder() - .semantics(StepSemantics.AT_MOST_ONCE_PER_RETRY) + .semanticsPerRetry(StepSemantics.AT_MOST_ONCE_PER_RETRY) + .retryStrategy(RetryStrategies.Presets.NO_RETRY) .build()); } catch (StepInterruptedException e) { // Recovery: check external status and return verified result diff --git a/sdk-integration-tests/src/test/java/software/amazon/lambda/durable/StepSemanticsIntegrationTest.java b/sdk-integration-tests/src/test/java/software/amazon/lambda/durable/StepSemanticsIntegrationTest.java index 53a4eaf83..12d9f5a50 100644 --- a/sdk-integration-tests/src/test/java/software/amazon/lambda/durable/StepSemanticsIntegrationTest.java +++ b/sdk-integration-tests/src/test/java/software/amazon/lambda/durable/StepSemanticsIntegrationTest.java @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 package software.amazon.lambda.durable; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; @@ -18,29 +18,6 @@ class StepSemanticsIntegrationTest { void testAtLeastOnceCompletesSuccessfully() { var executionCount = new AtomicInteger(0); - var runner = LocalDurableTestRunner.create( - String.class, - (input, ctx) -> ctx.step( - "my-step", - String.class, - stepCtx -> { - executionCount.incrementAndGet(); - return "result"; - }, - StepConfig.builder() - .semantics(StepSemantics.AT_LEAST_ONCE_PER_RETRY) - .build())); - - var result = runner.run("test-input"); - - assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus()); - assertEquals(1, executionCount.get()); - } - - @Test - void testSemanticsPerRetry_atLeastOnceCompletesSuccessfully() { - var executionCount = new AtomicInteger(0); - var runner = LocalDurableTestRunner.create( String.class, (input, ctx) -> ctx.step( @@ -64,29 +41,6 @@ void testSemanticsPerRetry_atLeastOnceCompletesSuccessfully() { void testAtMostOnceCompletesSuccessfully() { var executionCount = new AtomicInteger(0); - var runner = LocalDurableTestRunner.create( - String.class, - (input, ctx) -> ctx.step( - "my-step", - String.class, - stepCtx -> { - executionCount.incrementAndGet(); - return "result"; - }, - StepConfig.builder() - .semantics(StepSemantics.AT_MOST_ONCE_PER_RETRY) - .build())); - - var result = runner.run("test-input"); - - assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus()); - assertEquals(1, executionCount.get()); - } - - @Test - void testSemanticsPerRetry_atMostOnceCompletesSuccessfully() { - var executionCount = new AtomicInteger(0); - var runner = LocalDurableTestRunner.create( String.class, (input, ctx) -> ctx.step( @@ -110,30 +64,6 @@ void testSemanticsPerRetry_atMostOnceCompletesSuccessfully() { void testAtMostOnceNoRetryFailsImmediately() { var executionCount = new AtomicInteger(0); - var runner = LocalDurableTestRunner.create( - String.class, - (input, ctx) -> ctx.step( - "my-step", - String.class, - stepCtx -> { - executionCount.incrementAndGet(); - throw new RuntimeException("Always fails"); - }, - StepConfig.builder() - .semantics(StepSemantics.AT_MOST_ONCE_PER_RETRY) - .retryStrategy(RetryStrategies.Presets.NO_RETRY) - .build())); - - var result = runner.run("test-input"); - - assertEquals(ExecutionStatus.FAILED, result.getStatus()); - assertEquals(1, executionCount.get()); - } - - @Test - void testSemanticsPerRetry_atMostOnceNoRetryFailsImmediately() { - var executionCount = new AtomicInteger(0); - var runner = LocalDurableTestRunner.create( String.class, (input, ctx) -> ctx.step( @@ -155,7 +85,7 @@ void testSemanticsPerRetry_atMostOnceNoRetryFailsImmediately() { } @Test - void testDefaultSemanticsIsAtLeastOnce() { + void testDefaultSemanticsPerRetryIsAtLeastOnce() { var executionCount = new AtomicInteger(0); var runner = LocalDurableTestRunner.create( @@ -175,46 +105,18 @@ void testDefaultSemanticsIsAtLeastOnce() { void testAtLeastOnceReExecutesAfterCheckpointLoss() { var executionCount = new AtomicInteger(0); - var runner = LocalDurableTestRunner.create(String.class, (input, context) -> { - return context.step( - "step", - String.class, - stepCtx -> { - var count = executionCount.incrementAndGet(); - return "Executed " + count + " times"; - }, - StepConfig.builder() - .semantics(StepSemantics.AT_LEAST_ONCE_PER_RETRY) - .build()); - }); - - runner.run("test"); - assertEquals(1, executionCount.get()); - - runner.simulateFireAndForgetCheckpointLoss("step"); - - var result = runner.run("test"); - - assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus()); - assertEquals(2, executionCount.get()); - } - - @Test - void testSemanticsPerRetry_atLeastOnceReExecutesAfterCheckpointLoss() { - var executionCount = new AtomicInteger(0); - - var runner = LocalDurableTestRunner.create(String.class, (input, context) -> { - return context.step( - "step", - String.class, - stepCtx -> { - var count = executionCount.incrementAndGet(); - return "Executed " + count + " times"; - }, - StepConfig.builder() - .semanticsPerRetry(StepSemantics.AT_LEAST_ONCE_PER_RETRY) - .build()); - }); + var runner = LocalDurableTestRunner.create( + String.class, + (input, context) -> context.step( + "step", + String.class, + stepCtx -> { + var count = executionCount.incrementAndGet(); + return "Executed " + count + " times"; + }, + StepConfig.builder() + .semanticsPerRetry(StepSemantics.AT_LEAST_ONCE_PER_RETRY) + .build())); runner.run("test"); assertEquals(1, executionCount.get()); @@ -231,45 +133,18 @@ void testSemanticsPerRetry_atLeastOnceReExecutesAfterCheckpointLoss() { void testAtLeastOnceReExecutesAfterCheckpointFailure() { var executionCount = new AtomicInteger(0); - var runner = LocalDurableTestRunner.create(String.class, (input, context) -> { - return context.step( - "step", - String.class, - stepCtx -> { - var count = executionCount.incrementAndGet(); - return "Executed " + count + " times"; - }, - StepConfig.builder() - .semantics(StepSemantics.AT_LEAST_ONCE_PER_RETRY) - .build()); - }); - - runner.run("test"); - assertEquals(1, executionCount.get()); - - runner.resetCheckpointToStarted("step"); - var result = runner.runUntilComplete("test"); - - assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus()); - assertEquals(2, executionCount.get()); - } - - @Test - void testSemanticsPerRetry_atLeastOnceReExecutesAfterCheckpointFailure() { - var executionCount = new AtomicInteger(0); - - var runner = LocalDurableTestRunner.create(String.class, (input, context) -> { - return context.step( - "step", - String.class, - stepCtx -> { - var count = executionCount.incrementAndGet(); - return "Executed " + count + " times"; - }, - StepConfig.builder() - .semanticsPerRetry(StepSemantics.AT_LEAST_ONCE_PER_RETRY) - .build()); - }); + var runner = LocalDurableTestRunner.create( + String.class, + (input, context) -> context.step( + "step", + String.class, + stepCtx -> { + var count = executionCount.incrementAndGet(); + return "Executed " + count + " times"; + }, + StepConfig.builder() + .semanticsPerRetry(StepSemantics.AT_LEAST_ONCE_PER_RETRY) + .build())); runner.run("test"); assertEquals(1, executionCount.get()); @@ -281,53 +156,22 @@ void testSemanticsPerRetry_atLeastOnceReExecutesAfterCheckpointFailure() { assertEquals(2, executionCount.get()); } - // This behavior is incorrect (the step should retry after interruption), but is kept for backward - // compatibility. The deprecated StepConfig.semantics() method preserves this behavior. - // Use StepConfig.semanticsPerRetry() for the corrected behavior (see below test). @Test - void testAtMostOnceThrowsExceptionAfterCheckpointFailure_deprecatedBackwardCompatibility() { + void testAtMostOnceRetriesAfterInterruption() { var executionCount = new AtomicInteger(0); - var runner = LocalDurableTestRunner.create(String.class, (input, context) -> { - return context.step( - "step", - String.class, - stepCtx -> { - executionCount.incrementAndGet(); - return "Should not re-execute"; - }, - StepConfig.builder() - .semantics(StepSemantics.AT_MOST_ONCE_PER_RETRY) - .build()); - }); - - runner.run("test"); - assertEquals(1, executionCount.get()); - - runner.resetCheckpointToStarted("step"); - - var result = runner.run("test"); - - assertEquals(ExecutionStatus.FAILED, result.getStatus()); - assertEquals(1, executionCount.get()); - } - - @Test - void testSemanticsPerRetry_atMostOnceRetriesAfterInterruption() { - var executionCount = new AtomicInteger(0); - - var runner = LocalDurableTestRunner.create(String.class, (input, context) -> { - return context.step( - "step", - String.class, - stepCtx -> { - executionCount.incrementAndGet(); - return "result"; - }, - StepConfig.builder() - .semanticsPerRetry(StepSemantics.AT_MOST_ONCE_PER_RETRY) - .build()); - }); + var runner = LocalDurableTestRunner.create( + String.class, + (input, context) -> context.step( + "step", + String.class, + stepCtx -> { + executionCount.incrementAndGet(); + return "result"; + }, + StepConfig.builder() + .semanticsPerRetry(StepSemantics.AT_MOST_ONCE_PER_RETRY) + .build())); runner.run("test"); assertEquals(1, executionCount.get()); diff --git a/sdk/src/main/java/software/amazon/lambda/durable/config/StepConfig.java b/sdk/src/main/java/software/amazon/lambda/durable/config/StepConfig.java index 6b4a49481..92a90a643 100644 --- a/sdk/src/main/java/software/amazon/lambda/durable/config/StepConfig.java +++ b/sdk/src/main/java/software/amazon/lambda/durable/config/StepConfig.java @@ -14,13 +14,11 @@ */ public class StepConfig { private final RetryStrategy retryStrategy; - private final StepSemantics semantics; private final StepSemantics semanticsPerRetry; private final SerDes serDes; private StepConfig(Builder builder) { this.retryStrategy = builder.retryStrategy; - this.semantics = builder.semantics; this.semanticsPerRetry = builder.semanticsPerRetry; this.serDes = builder.serDes; } @@ -30,20 +28,9 @@ public RetryStrategy retryStrategy() { return retryStrategy != null ? retryStrategy : RetryStrategies.Presets.DEFAULT; } - /** - * Returns the delivery semantics for this step, defaults to AT_LEAST_ONCE_PER_RETRY if not specified. - * - * @deprecated Use {@link #semanticsPerRetry()} instead. This method has incorrect behavior where - * AT_MOST_ONCE_PER_RETRY does not retry after step interruption. - */ - @Deprecated(forRemoval = true) - public StepSemantics semantics() { - return semantics != null ? semantics : StepSemantics.AT_LEAST_ONCE_PER_RETRY; - } - - /** Returns the delivery semantics per retry for this step, or null if not specified. */ + /** Returns the delivery semantics per retry for this step. */ public StepSemantics semanticsPerRetry() { - return semanticsPerRetry; + return semanticsPerRetry != null ? semanticsPerRetry : StepSemantics.AT_LEAST_ONCE_PER_RETRY; } /** Returns the custom serializer for this step, or null if not specified (uses default SerDes). */ @@ -52,7 +39,7 @@ public SerDes serDes() { } public Builder toBuilder() { - return new Builder(retryStrategy, semantics, semanticsPerRetry, serDes); + return new Builder(retryStrategy, semanticsPerRetry, serDes); } /** @@ -61,20 +48,17 @@ public Builder toBuilder() { * @return a new Builder instance */ public static Builder builder() { - return new Builder(null, null, null, null); + return new Builder(null, null, null); } /** Builder for creating StepConfig instances. */ public static class Builder { private RetryStrategy retryStrategy; - private StepSemantics semantics; private StepSemantics semanticsPerRetry; private SerDes serDes; - public Builder( - RetryStrategy retryStrategy, StepSemantics semantics, StepSemantics semanticsPerRetry, SerDes serDes) { + public Builder(RetryStrategy retryStrategy, StepSemantics semanticsPerRetry, SerDes serDes) { this.retryStrategy = retryStrategy; - this.semantics = semantics; this.semanticsPerRetry = semanticsPerRetry; this.serDes = serDes; } @@ -90,25 +74,9 @@ public Builder retryStrategy(RetryStrategy retryStrategy) { return this; } - /** - * Sets the delivery semantics for the step. - * - * @param semantics the delivery semantics to use, defaults to AT_LEAST_ONCE_PER_RETRY if not specified - * @return this builder for method chaining - * @deprecated Use {@link #semanticsPerRetry(StepSemantics)} instead. This method has incorrect behavior where - * AT_MOST_ONCE_PER_RETRY does not retry after step interruption. - */ - @Deprecated(forRemoval = true) - public Builder semantics(StepSemantics semantics) { - this.semantics = semantics; - return this; - } - /** * Sets the delivery semantics per retry for the step. * - *

If set, this takes precedence over {@link #semantics(StepSemantics)}. - * * @param semanticsPerRetry the delivery semantics to use * @return this builder for method chaining */ diff --git a/sdk/src/main/java/software/amazon/lambda/durable/config/StepSemantics.java b/sdk/src/main/java/software/amazon/lambda/durable/config/StepSemantics.java index 183991d0f..46f3da523 100644 --- a/sdk/src/main/java/software/amazon/lambda/durable/config/StepSemantics.java +++ b/sdk/src/main/java/software/amazon/lambda/durable/config/StepSemantics.java @@ -17,12 +17,6 @@ public enum StepSemantics { /** * At-most-once delivery per retry attempt. START checkpoint is awaited before user code runs. If interrupted, * throws {@link software.amazon.lambda.durable.exception.StepInterruptedException}. - * - *

When used with {@link StepConfig.Builder#semantics(StepSemantics)}: interrupted steps are NOT retried - * (incorrect behavior, kept for backward compatibility). - * - *

When used with {@link StepConfig.Builder#semanticsPerRetry(StepSemantics)}: interrupted steps are retried - * according to the retry strategy (correct behavior). */ AT_MOST_ONCE_PER_RETRY } diff --git a/sdk/src/main/java/software/amazon/lambda/durable/operation/StepOperation.java b/sdk/src/main/java/software/amazon/lambda/durable/operation/StepOperation.java index ef43c2acf..6018a0ccf 100644 --- a/sdk/src/main/java/software/amazon/lambda/durable/operation/StepOperation.java +++ b/sdk/src/main/java/software/amazon/lambda/durable/operation/StepOperation.java @@ -170,11 +170,9 @@ private void handleStepFailure(Throwable exception, int attempt) { errorObject = serializeException(exception); } - var isRetryable = !(exception instanceof StepInterruptedException) - || config.semanticsPerRetry() == StepSemantics.AT_MOST_ONCE_PER_RETRY; var retryDecision = config.retryStrategy().makeRetryDecision(exception, attempt); - if (isRetryable && retryDecision.shouldRetry()) { + if (retryDecision.shouldRetry()) { // Send RETRY var retryDelayInSeconds = Math.toIntExact(retryDecision.delay().toSeconds()); var retryUpdate = OperationUpdate.builder() @@ -224,9 +222,7 @@ public T get() { } } - @SuppressWarnings("deprecation") private boolean isAtMostOnce() { - var semantics = config.semanticsPerRetry() != null ? config.semanticsPerRetry() : config.semantics(); - return semantics == StepSemantics.AT_MOST_ONCE_PER_RETRY; + return config.semanticsPerRetry() == StepSemantics.AT_MOST_ONCE_PER_RETRY; } } diff --git a/sdk/src/test/java/software/amazon/lambda/durable/config/StepConfigTest.java b/sdk/src/test/java/software/amazon/lambda/durable/config/StepConfigTest.java index a65b6bea9..909788261 100644 --- a/sdk/src/test/java/software/amazon/lambda/durable/config/StepConfigTest.java +++ b/sdk/src/test/java/software/amazon/lambda/durable/config/StepConfigTest.java @@ -33,22 +33,6 @@ void testBuilderChaining() { var strategy = RetryStrategies.Presets.DEFAULT; var customSerDes = new JacksonSerDes(); - var config = StepConfig.builder() - .retryStrategy(strategy) - .semantics(StepSemantics.AT_MOST_ONCE_PER_RETRY) - .serDes(customSerDes) - .build(); - - assertEquals(strategy, config.retryStrategy()); - assertEquals(StepSemantics.AT_MOST_ONCE_PER_RETRY, config.semantics()); - assertEquals(customSerDes, config.serDes()); - } - - @Test - void testBuilderChainingWithSemanticsPerRetry() { - var strategy = RetryStrategies.Presets.DEFAULT; - var customSerDes = new JacksonSerDes(); - var config = StepConfig.builder() .retryStrategy(strategy) .semanticsPerRetry(StepSemantics.AT_MOST_ONCE_PER_RETRY) @@ -68,17 +52,10 @@ void testBuilderWithNullRetryStrategy() { } @Test - void testSemanticsDefaultsToAtLeastOnce() { - var config = StepConfig.builder().build(); - - assertEquals(StepSemantics.AT_LEAST_ONCE_PER_RETRY, config.semantics()); - } - - @Test - void testSemanticsPerRetryDefaultsToNull() { + void testSemanticsPerRetryDefaultsToAtLeastOnce() { var config = StepConfig.builder().build(); - assertNull(config.semanticsPerRetry()); + assertEquals(StepSemantics.AT_LEAST_ONCE_PER_RETRY, config.semanticsPerRetry()); } @Test @@ -121,12 +98,12 @@ void testBuilderWithAllOptions() { var config = StepConfig.builder() .retryStrategy(strategy) - .semantics(StepSemantics.AT_MOST_ONCE_PER_RETRY) + .semanticsPerRetry(StepSemantics.AT_MOST_ONCE_PER_RETRY) .serDes(customSerDes) .build(); assertEquals(strategy, config.retryStrategy()); - assertEquals(StepSemantics.AT_MOST_ONCE_PER_RETRY, config.semantics()); + assertEquals(StepSemantics.AT_MOST_ONCE_PER_RETRY, config.semanticsPerRetry()); assertEquals(customSerDes, config.serDes()); } } diff --git a/sdk/src/test/java/software/amazon/lambda/durable/config/WaitForCallbackConfigTest.java b/sdk/src/test/java/software/amazon/lambda/durable/config/WaitForCallbackConfigTest.java index 6f12f5cd8..146d07a57 100644 --- a/sdk/src/test/java/software/amazon/lambda/durable/config/WaitForCallbackConfigTest.java +++ b/sdk/src/test/java/software/amazon/lambda/durable/config/WaitForCallbackConfigTest.java @@ -49,7 +49,7 @@ void testBuilderWithCustomCallbackConfig() { void testBuilderWithBothConfigs() { var stepConfig = StepConfig.builder() .retryStrategy(RetryStrategies.Presets.DEFAULT) - .semantics(StepSemantics.AT_MOST_ONCE_PER_RETRY) + .semanticsPerRetry(StepSemantics.AT_MOST_ONCE_PER_RETRY) .build(); var callbackConfig = CallbackConfig.builder() .timeout(Duration.ofMinutes(5)) @@ -146,7 +146,7 @@ void testBuilderWithAllOptions() { var customSerDes = new JacksonSerDes(); var stepConfig = StepConfig.builder() .retryStrategy(RetryStrategies.Presets.DEFAULT) - .semantics(StepSemantics.AT_MOST_ONCE_PER_RETRY) + .semanticsPerRetry(StepSemantics.AT_MOST_ONCE_PER_RETRY) .serDes(customSerDes) .build(); var callbackConfig = CallbackConfig.builder() @@ -163,7 +163,7 @@ void testBuilderWithAllOptions() { assertEquals(stepConfig, config.stepConfig()); assertEquals(callbackConfig, config.callbackConfig()); assertEquals(RetryStrategies.Presets.DEFAULT, config.stepConfig().retryStrategy()); - assertEquals(StepSemantics.AT_MOST_ONCE_PER_RETRY, config.stepConfig().semantics()); + assertEquals(StepSemantics.AT_MOST_ONCE_PER_RETRY, config.stepConfig().semanticsPerRetry()); assertEquals(Duration.ofMinutes(10), config.callbackConfig().timeout()); assertEquals(Duration.ofMinutes(2), config.callbackConfig().heartbeatTimeout()); } @@ -174,7 +174,7 @@ void testStepConfigDefaultsWhenNull() { assertNotNull(config.stepConfig()); assertEquals(RetryStrategies.Presets.DEFAULT, config.stepConfig().retryStrategy()); - assertEquals(StepSemantics.AT_LEAST_ONCE_PER_RETRY, config.stepConfig().semantics()); + assertEquals(StepSemantics.AT_LEAST_ONCE_PER_RETRY, config.stepConfig().semanticsPerRetry()); assertNull(config.stepConfig().serDes()); }