diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index e38fe4149..682eb8a28 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -57,6 +57,12 @@ jobs: role-to-assume: "${{ secrets.ACTIONS_INTEGRATION_ROLE_NAME }}" role-session-name: java-language-sdk-test aws-region: ${{ env.AWS_REGION }} + - name: Setup Java ${{ matrix.java }} + uses: actions/setup-java@v5 + with: + distribution: corretto + java-version: ${{ matrix.java }} + cache: maven - name: Build locally run: mvn -B -q -Dmaven.test.skip=true install --file pom.xml - name: sam build @@ -70,14 +76,6 @@ jobs: --resolve-image-repos --resolve-s3 --capabilities CAPABILITY_IAM --parameter-overrides \ 'ParameterKey=Architecture,ParameterValue=x86_64 ParameterKey=JavaVersion,ParameterValue=java${{ matrix.java }}' working-directory: ./examples - - name: Setup Java ${{ matrix.java }} - uses: actions/setup-java@v5 - with: - distribution: corretto - java-version: ${{ matrix.java }} - cache: maven - - name: Build locally - run: mvn -B -q -Dmaven.test.skip=true install --file pom.xml - name: Cloud Based Integration Tests run: mvn clean test -B -Dtest.cloud.enabled=true -Dtest=CloudBasedIntegrationTest -Dtest.function.name.suffix='-java${{ matrix.java }}-runtime' working-directory: ./examples diff --git a/examples/pom.xml b/examples/pom.xml index 9a5b0f9b6..f4b8ce755 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -140,4 +140,38 @@ + + + + exclude-virtual-threads + + [,21) + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + **/vt/ManyAsyncStepsVirtualThreadPoolExample.java + + + **/vt/ManyAsyncStepsVirtualThreadPoolExampleTest.java + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/vt/ManyAsyncStepsVirtualThreadPoolExampleTest.java + + + + + + + diff --git a/examples/src/main/java/software/amazon/lambda/durable/examples/child/ManyAsyncChildContextExample.java b/examples/src/main/java/software/amazon/lambda/durable/examples/child/ManyAsyncChildContextExample.java index 797d5ce4d..e753127fd 100644 --- a/examples/src/main/java/software/amazon/lambda/durable/examples/child/ManyAsyncChildContextExample.java +++ b/examples/src/main/java/software/amazon/lambda/durable/examples/child/ManyAsyncChildContextExample.java @@ -9,6 +9,8 @@ import software.amazon.lambda.durable.DurableContext; import software.amazon.lambda.durable.DurableFuture; import software.amazon.lambda.durable.DurableHandler; +import software.amazon.lambda.durable.examples.types.ManyAsyncStepsInput; +import software.amazon.lambda.durable.examples.types.ManyAsyncStepsOutput; /** * Performance test example demonstrating concurrent async child contexts. @@ -21,15 +23,10 @@ *
  • All results are collected using {@link DurableFuture#allOf} * */ -public class ManyAsyncChildContextExample - extends DurableHandler { - - public record Input(int multiplier, int steps) {} - - public record Output(long result, long executionTimeMs, long replayTimeMs) {} +public class ManyAsyncChildContextExample extends DurableHandler { @Override - public Output handleRequest(Input input, DurableContext context) { + public ManyAsyncStepsOutput handleRequest(ManyAsyncStepsInput input, DurableContext context) { var startTime = System.nanoTime(); var multiplier = input.multiplier(); var steps = input.steps(); @@ -65,7 +62,7 @@ public Output handleRequest(Input input, DurableContext context) { var replayTimeMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); - return new Output(totalSum, executionTimeMs, replayTimeMs); + return new ManyAsyncStepsOutput(totalSum, executionTimeMs, replayTimeMs); } @Override diff --git a/examples/src/main/java/software/amazon/lambda/durable/examples/step/ManyAsyncStepsExample.java b/examples/src/main/java/software/amazon/lambda/durable/examples/step/ManyAsyncStepsExample.java index bad4fde75..2afae9ac4 100644 --- a/examples/src/main/java/software/amazon/lambda/durable/examples/step/ManyAsyncStepsExample.java +++ b/examples/src/main/java/software/amazon/lambda/durable/examples/step/ManyAsyncStepsExample.java @@ -9,6 +9,8 @@ import software.amazon.lambda.durable.DurableContext; import software.amazon.lambda.durable.DurableFuture; import software.amazon.lambda.durable.DurableHandler; +import software.amazon.lambda.durable.examples.types.ManyAsyncStepsInput; +import software.amazon.lambda.durable.examples.types.ManyAsyncStepsOutput; /** * Performance test example demonstrating concurrent async steps. @@ -21,14 +23,10 @@ *
  • All results are collected using {@link DurableFuture#allOf} * */ -public class ManyAsyncStepsExample extends DurableHandler { - - public record Input(int multiplier, int steps) {} - - public record Output(long result, long executionTimeMs, long replayTimeMs) {} +public class ManyAsyncStepsExample extends DurableHandler { @Override - public Output handleRequest(Input input, DurableContext context) { + public ManyAsyncStepsOutput handleRequest(ManyAsyncStepsInput input, DurableContext context) { var startTime = System.nanoTime(); var multiplier = input.multiplier(); var steps = input.steps(); @@ -60,7 +58,7 @@ public Output handleRequest(Input input, DurableContext context) { var replayTimeMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); - return new Output(totalSum, executionTimeMs, replayTimeMs); + return new ManyAsyncStepsOutput(totalSum, executionTimeMs, replayTimeMs); } @Override diff --git a/examples/src/main/java/software/amazon/lambda/durable/examples/types/ManyAsyncStepsInput.java b/examples/src/main/java/software/amazon/lambda/durable/examples/types/ManyAsyncStepsInput.java new file mode 100644 index 000000000..de3a21a11 --- /dev/null +++ b/examples/src/main/java/software/amazon/lambda/durable/examples/types/ManyAsyncStepsInput.java @@ -0,0 +1,5 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.lambda.durable.examples.types; + +public record ManyAsyncStepsInput(int multiplier, int steps) {} diff --git a/examples/src/main/java/software/amazon/lambda/durable/examples/types/ManyAsyncStepsOutput.java b/examples/src/main/java/software/amazon/lambda/durable/examples/types/ManyAsyncStepsOutput.java new file mode 100644 index 000000000..eb66a87d7 --- /dev/null +++ b/examples/src/main/java/software/amazon/lambda/durable/examples/types/ManyAsyncStepsOutput.java @@ -0,0 +1,5 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.lambda.durable.examples.types; + +public record ManyAsyncStepsOutput(long result, long executionTimeMs, long replayTimeMs) {} diff --git a/examples/src/main/java/software/amazon/lambda/durable/examples/vt/ManyAsyncStepsVirtualThreadPoolExample.java b/examples/src/main/java/software/amazon/lambda/durable/examples/vt/ManyAsyncStepsVirtualThreadPoolExample.java new file mode 100644 index 000000000..bc8364e1a --- /dev/null +++ b/examples/src/main/java/software/amazon/lambda/durable/examples/vt/ManyAsyncStepsVirtualThreadPoolExample.java @@ -0,0 +1,74 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.lambda.durable.examples.vt; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import software.amazon.lambda.durable.DurableConfig; +import software.amazon.lambda.durable.DurableContext; +import software.amazon.lambda.durable.DurableFuture; +import software.amazon.lambda.durable.DurableHandler; +import software.amazon.lambda.durable.examples.types.ManyAsyncStepsInput; +import software.amazon.lambda.durable.examples.types.ManyAsyncStepsOutput; + +/** + * Performance test example demonstrating concurrent async steps. + * + *

    This example tests the SDK's ability to handle many concurrent operations: + * + *

      + *
    • Creates async steps in a loop + *
    • Each step performs a simple computation + *
    • All results are collected using {@link DurableFuture#allOf} + *
    + */ +public class ManyAsyncStepsVirtualThreadPoolExample extends DurableHandler { + + @Override + public ManyAsyncStepsOutput handleRequest(ManyAsyncStepsInput input, DurableContext context) { + var startTime = System.nanoTime(); + var multiplier = input.multiplier(); + var steps = input.steps(); + var logger = context.getLogger(); + + logger.info("Starting {} async steps with multiplier {}", steps, multiplier); + + // Create async steps + var futures = new ArrayList>(steps); + for (var i = 0; i < steps; i++) { + var index = i; + var future = context.stepAsync("compute-" + i, Integer.class, stepCtx -> index * multiplier); + futures.add(future); + } + + logger.info("All {} async steps created, collecting results", steps); + + // Collect all results using allOf + var results = DurableFuture.allOf(futures); + var totalSum = results.stream().mapToInt(Integer::intValue).sum(); + + // checkpoint the executionTime so that we can have the same value when replay + var executionTimeMs = context.step( + "execution-time", Long.class, stepCtx -> TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)); + logger.info("Completed {} steps, total sum: {}, execution time: {}ms", steps, totalSum, executionTimeMs); + + // Wait 2 seconds to test replay + context.wait("post-compute-wait", Duration.ofSeconds(2)); + + var replayTimeMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); + + return new ManyAsyncStepsOutput(totalSum, executionTimeMs, replayTimeMs); + } + + @Override + protected DurableConfig createConfiguration() { + // Add a small checkpoint delay to help batch the checkpoint requests and reduce the overall latencies + // when the function has many concurrent operations + return DurableConfig.builder() + .withCheckpointDelay(Duration.ofMillis(10)) + .withExecutorService(Executors.newVirtualThreadPerTaskExecutor()) + .build(); + } +} diff --git a/examples/src/test/java/software/amazon/lambda/durable/examples/CloudBasedIntegrationTest.java b/examples/src/test/java/software/amazon/lambda/durable/examples/CloudBasedIntegrationTest.java index 6f809513e..dfaf27f89 100644 --- a/examples/src/test/java/software/amazon/lambda/durable/examples/CloudBasedIntegrationTest.java +++ b/examples/src/test/java/software/amazon/lambda/durable/examples/CloudBasedIntegrationTest.java @@ -12,7 +12,9 @@ import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; @@ -22,11 +24,11 @@ import software.amazon.awssdk.services.lambda.model.OperationStatus; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.lambda.durable.TypeToken; -import software.amazon.lambda.durable.examples.child.ManyAsyncChildContextExample; import software.amazon.lambda.durable.examples.general.GenericTypesExample; -import software.amazon.lambda.durable.examples.step.ManyAsyncStepsExample; import software.amazon.lambda.durable.examples.types.ApprovalRequest; import software.amazon.lambda.durable.examples.types.GreetingRequest; +import software.amazon.lambda.durable.examples.types.ManyAsyncStepsInput; +import software.amazon.lambda.durable.examples.types.ManyAsyncStepsOutput; import software.amazon.lambda.durable.examples.wait.ConcurrentWaitForConditionExample; import software.amazon.lambda.durable.model.ExecutionStatus; import software.amazon.lambda.durable.serde.JacksonSerDes; @@ -536,10 +538,10 @@ void testManyAsyncStepsExample(int steps, long maxExecutionTime, long maxReplayT for (var i = 0; i < PERFORMANCE_TEST_REPEAT; i++) { var runner = CloudDurableTestRunner.create( arn("many-async-steps-example"), - ManyAsyncStepsExample.Input.class, - ManyAsyncStepsExample.Output.class, + ManyAsyncStepsInput.class, + ManyAsyncStepsOutput.class, lambdaClient); - var result = runner.run(new ManyAsyncStepsExample.Input(2, steps)); + var result = runner.run(new ManyAsyncStepsInput(2, steps)); assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus()); @@ -565,6 +567,44 @@ void testManyAsyncStepsExample(int steps, long maxExecutionTime, long maxReplayT assertTrue(minimalExecutionTimeMs < maxExecutionTime); } + @EnabledForJreRange(min = JRE.JAVA_21) + @ParameterizedTest + @CsvSource({"100, 1000, 20", "500, 2000, 30", "1000, 3000, 50"}) + void testManyAsyncStepsVirtualThreadExample(int steps, long maxExecutionTime, long maxReplayTime) { + long minimalExecutionTimeMs = Long.MAX_VALUE; + long minimalReplayTimeMs = Long.MAX_VALUE; + for (var i = 0; i < PERFORMANCE_TEST_REPEAT; i++) { + var runner = CloudDurableTestRunner.create( + arn("many-async-steps-virtual-thread-pool-example"), + ManyAsyncStepsInput.class, + ManyAsyncStepsOutput.class, + lambdaClient); + var result = runner.run(new ManyAsyncStepsInput(2, steps)); + + assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus()); + + var finalResult = result.getResult(); + System.out.printf("ManyAsyncStepsVirtualThreadPoolExample result (%d steps): %s\n", steps, finalResult); + assertNotNull(finalResult); + assertEquals((long) steps * (steps - 1), finalResult.result()); // Sum of 0..steps * 2 + + // Verify some operations are tracked + assertNotNull(runner.getOperation("compute-0")); + assertNotNull(runner.getOperation("compute-" + (steps - 1))); + + if (finalResult.executionTimeMs() < minimalExecutionTimeMs) { + minimalExecutionTimeMs = finalResult.executionTimeMs(); + } + + if (finalResult.replayTimeMs() < minimalReplayTimeMs) { + minimalReplayTimeMs = finalResult.replayTimeMs(); + } + } + + assertTrue(minimalReplayTimeMs < maxReplayTime); + assertTrue(minimalExecutionTimeMs < maxExecutionTime); + } + @ParameterizedTest // OOM if it creates 1000 child contexts @CsvSource({"100, 1500, 10", "500, 3000, 20"}) @@ -574,10 +614,10 @@ void testManyAsyncChildContextExample(int steps, long maxExecutionTime, long max for (var i = 0; i < PERFORMANCE_TEST_REPEAT; i++) { var runner = CloudDurableTestRunner.create( arn("many-async-child-context-example"), - ManyAsyncChildContextExample.Input.class, - ManyAsyncChildContextExample.Output.class, + ManyAsyncStepsInput.class, + ManyAsyncStepsOutput.class, lambdaClient); - var result = runner.run(new ManyAsyncChildContextExample.Input(2, steps)); + var result = runner.run(new ManyAsyncStepsInput(2, steps)); assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus()); diff --git a/examples/src/test/java/software/amazon/lambda/durable/examples/child/ManyAsyncChildContextExampleTest.java b/examples/src/test/java/software/amazon/lambda/durable/examples/child/ManyAsyncChildContextExampleTest.java index 94e5ff097..24bf18f21 100644 --- a/examples/src/test/java/software/amazon/lambda/durable/examples/child/ManyAsyncChildContextExampleTest.java +++ b/examples/src/test/java/software/amazon/lambda/durable/examples/child/ManyAsyncChildContextExampleTest.java @@ -6,7 +6,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; -import software.amazon.lambda.durable.examples.step.ManyAsyncStepsExample; +import software.amazon.lambda.durable.examples.types.ManyAsyncStepsInput; +import software.amazon.lambda.durable.examples.types.ManyAsyncStepsOutput; import software.amazon.lambda.durable.model.ExecutionStatus; import software.amazon.lambda.durable.testing.LocalDurableTestRunner; @@ -15,14 +16,14 @@ class ManyAsyncChildContextExampleTest { @Test void testManyAsyncSteps() { var handler = new ManyAsyncChildContextExample(); - var runner = LocalDurableTestRunner.create(ManyAsyncChildContextExample.Input.class, handler); + var runner = LocalDurableTestRunner.create(ManyAsyncStepsInput.class, handler); - var input = new ManyAsyncChildContextExample.Input(2, 500); + var input = new ManyAsyncStepsInput(2, 500); var result = runner.runUntilComplete(input); assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus()); - var output = result.getResult(ManyAsyncStepsExample.Output.class); + var output = result.getResult(ManyAsyncStepsOutput.class); assertNotNull(output); // Sum of 0..499 * 2 = 499 * 500 / 2 * 2 = 249500 @@ -32,25 +33,23 @@ void testManyAsyncSteps() { @Test void testManyAsyncStepsWithDefaultMultiplier() { var handler = new ManyAsyncChildContextExample(); - var runner = LocalDurableTestRunner.create(ManyAsyncChildContextExample.Input.class, handler); + var runner = LocalDurableTestRunner.create(ManyAsyncStepsInput.class, handler); - var input = new ManyAsyncChildContextExample.Input(1, 500); + var input = new ManyAsyncStepsInput(1, 500); var result = runner.runUntilComplete(input); assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus()); // Sum of 0..499 = 499 * 500 / 2 = 124750 - assertEquals( - 124750, - result.getResult(ManyAsyncChildContextExample.Output.class).result()); + assertEquals(124750, result.getResult(ManyAsyncStepsOutput.class).result()); } @Test void testOperationsAreTracked() { var handler = new ManyAsyncChildContextExample(); - var runner = LocalDurableTestRunner.create(ManyAsyncChildContextExample.Input.class, handler); + var runner = LocalDurableTestRunner.create(ManyAsyncStepsInput.class, handler); - var result = runner.runUntilComplete(new ManyAsyncChildContextExample.Input(1, 500)); + var result = runner.runUntilComplete(new ManyAsyncStepsInput(1, 500)); assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus()); diff --git a/examples/src/test/java/software/amazon/lambda/durable/examples/step/ManyAsyncStepsExampleTest.java b/examples/src/test/java/software/amazon/lambda/durable/examples/step/ManyAsyncStepsExampleTest.java index 287d5bd96..5d8102ecf 100644 --- a/examples/src/test/java/software/amazon/lambda/durable/examples/step/ManyAsyncStepsExampleTest.java +++ b/examples/src/test/java/software/amazon/lambda/durable/examples/step/ManyAsyncStepsExampleTest.java @@ -5,6 +5,8 @@ import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; +import software.amazon.lambda.durable.examples.types.ManyAsyncStepsInput; +import software.amazon.lambda.durable.examples.types.ManyAsyncStepsOutput; import software.amazon.lambda.durable.model.ExecutionStatus; import software.amazon.lambda.durable.testing.LocalDurableTestRunner; @@ -13,42 +15,40 @@ class ManyAsyncStepsExampleTest { @Test void testManyAsyncSteps() { var handler = new ManyAsyncStepsExample(); - var runner = LocalDurableTestRunner.create(ManyAsyncStepsExample.Input.class, handler); + var runner = LocalDurableTestRunner.create(ManyAsyncStepsInput.class, handler); - var input = new ManyAsyncStepsExample.Input(2, 500); + var input = new ManyAsyncStepsInput(2, 500); var result = runner.runUntilComplete(input); assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus()); - var output = result.getResult(ManyAsyncStepsExample.Output.class); + var output = result.getResult(ManyAsyncStepsOutput.class); assertNotNull(output); // Sum of 0..499 * 2 = 499 * 500 / 2 * 2 = 249500 - assertEquals( - 249500, result.getResult(ManyAsyncStepsExample.Output.class).result()); + assertEquals(249500, result.getResult(ManyAsyncStepsOutput.class).result()); } @Test void testManyAsyncStepsWithDefaultMultiplier() { var handler = new ManyAsyncStepsExample(); - var runner = LocalDurableTestRunner.create(ManyAsyncStepsExample.Input.class, handler); + var runner = LocalDurableTestRunner.create(ManyAsyncStepsInput.class, handler); - var input = new ManyAsyncStepsExample.Input(1, 500); + var input = new ManyAsyncStepsInput(1, 500); var result = runner.runUntilComplete(input); assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus()); // Sum of 0..499 = 499 * 500 / 2 = 124750 - assertEquals( - 124750, result.getResult(ManyAsyncStepsExample.Output.class).result()); + assertEquals(124750, result.getResult(ManyAsyncStepsOutput.class).result()); } @Test void testOperationsAreTracked() { var handler = new ManyAsyncStepsExample(); - var runner = LocalDurableTestRunner.create(ManyAsyncStepsExample.Input.class, handler); + var runner = LocalDurableTestRunner.create(ManyAsyncStepsInput.class, handler); - var result = runner.runUntilComplete(new ManyAsyncStepsExample.Input(1, 500)); + var result = runner.runUntilComplete(new ManyAsyncStepsInput(1, 500)); assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus()); diff --git a/examples/src/test/java/software/amazon/lambda/durable/examples/vt/ManyAsyncStepsVirtualThreadPoolExampleTest.java b/examples/src/test/java/software/amazon/lambda/durable/examples/vt/ManyAsyncStepsVirtualThreadPoolExampleTest.java new file mode 100644 index 000000000..dd15e437f --- /dev/null +++ b/examples/src/test/java/software/amazon/lambda/durable/examples/vt/ManyAsyncStepsVirtualThreadPoolExampleTest.java @@ -0,0 +1,64 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.lambda.durable.examples.vt; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; +import software.amazon.lambda.durable.examples.types.ManyAsyncStepsInput; +import software.amazon.lambda.durable.examples.types.ManyAsyncStepsOutput; +import software.amazon.lambda.durable.model.ExecutionStatus; +import software.amazon.lambda.durable.testing.LocalDurableTestRunner; + +@EnabledForJreRange(min = JRE.JAVA_21) +class ManyAsyncStepsVirtualThreadPoolExampleTest { + + @Test + void testManyAsyncSteps() { + var handler = new ManyAsyncStepsVirtualThreadPoolExample(); + var runner = LocalDurableTestRunner.create(ManyAsyncStepsInput.class, handler); + + var input = new ManyAsyncStepsInput(2, 500); + var result = runner.runUntilComplete(input); + + assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus()); + + var output = result.getResult(ManyAsyncStepsOutput.class); + assertNotNull(output); + + // Sum of 0..499 * 2 = 499 * 500 / 2 * 2 = 249500 + assertEquals(249500, result.getResult(ManyAsyncStepsOutput.class).result()); + } + + @Test + void testManyAsyncStepsWithDefaultMultiplier() { + var handler = new ManyAsyncStepsVirtualThreadPoolExample(); + var runner = LocalDurableTestRunner.create(ManyAsyncStepsInput.class, handler); + + var input = new ManyAsyncStepsInput(1, 500); + var result = runner.runUntilComplete(input); + + assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus()); + + // Sum of 0..499 = 499 * 500 / 2 = 124750 + assertEquals(124750, result.getResult(ManyAsyncStepsOutput.class).result()); + } + + @Test + void testOperationsAreTracked() { + var handler = new ManyAsyncStepsVirtualThreadPoolExample(); + var runner = LocalDurableTestRunner.create(ManyAsyncStepsInput.class, handler); + + var result = runner.runUntilComplete(new ManyAsyncStepsInput(1, 500)); + + assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus()); + + // Verify some operations are tracked + assertNotNull(result.getOperation("compute-0")); + assertNotNull(result.getOperation("compute-499")); + assertNotNull(result.getOperation("compute-250")); + } +} diff --git a/examples/template.yaml b/examples/template.yaml index 36adfc5a5..4f02619f3 100644 --- a/examples/template.yaml +++ b/examples/template.yaml @@ -15,6 +15,12 @@ Parameters: Default: 'java17' Description: Java runtime version +Conditions: + IsJava21OrLater: + !Or + - !Equals [!Ref JavaVersion, 'java21'] + - !Equals [!Ref JavaVersion, 'java25'] + Globals: Function: Timeout: 900 @@ -392,6 +398,24 @@ Resources: - lambda:GetDurableExecutionState Resource: !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:concurrent-wait-for-condition-example-${JavaVersion}-runtime" + ManyAsyncStepsVirtualThreadPoolExampleFunction: + Type: AWS::Serverless::Function + Condition: IsJava21OrLater + Properties: + FunctionName: !Join + - '-' + - - 'many-async-steps-virtual-thread-pool-example' + - !Ref JavaVersion + - runtime + Handler: "software.amazon.lambda.durable.examples.vt.ManyAsyncStepsVirtualThreadPoolExample" + Policies: + - Statement: + - Effect: Allow + Action: + - lambda:CheckpointDurableExecutions + - lambda:GetDurableExecutionState + Resource: !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:many-async-steps-virtual-thread-pool-example-${JavaVersion}-runtime" + Outputs: NoopExampleFunction: Description: Noop Example Function ARN @@ -480,3 +504,8 @@ Outputs: ConcurrentWaitForConditionExampleFunction: Description: Concurrent Wait For Condition Example Function ARN Value: !GetAtt ConcurrentWaitForConditionExampleFunction.Arn + + ManyAsyncStepsVirtualThreadPoolExampleFunction: + Condition: IsJava21OrLater + Description: Many Async Steps Virtual Thread Pool Example Function ARN + Value: !GetAtt ManyAsyncStepsVirtualThreadPoolExampleFunction.Arn