88import java .util .ArrayList ;
99import java .util .List ;
1010import java .util .concurrent .atomic .AtomicInteger ;
11+ import java .util .concurrent .atomic .AtomicReference ;
1112import org .junit .jupiter .params .ParameterizedTest ;
1213import org .junit .jupiter .params .provider .CsvSource ;
1314import software .amazon .lambda .durable .config .CompletionConfig ;
1617import software .amazon .lambda .durable .config .WaitForConditionConfig ;
1718import software .amazon .lambda .durable .model .ConcurrencyCompletionStatus ;
1819import software .amazon .lambda .durable .model .ExecutionStatus ;
20+ import software .amazon .lambda .durable .model .ParallelResult ;
1921import software .amazon .lambda .durable .model .WaitForConditionResult ;
2022import software .amazon .lambda .durable .retry .WaitStrategies ;
2123import software .amazon .lambda .durable .testing .LocalDurableTestRunner ;
@@ -29,7 +31,7 @@ void testSimpleParallel(NestingType nestingType, int events) {
2931 var runner = LocalDurableTestRunner .create (String .class , (input , context ) -> {
3032 var config = ParallelConfig .builder ().nestingType (nestingType ).build ();
3133 var futures = new ArrayList <DurableFuture <String >>();
32- var parallel = context .parallel ("process-items" , config );
34+ ParallelDurableFuture parallel = context .parallel ("process-items" , config );
3335
3436 try (parallel ) {
3537 for (var item : List .of ("a" , "b" , "c" )) {
@@ -58,7 +60,7 @@ void testParallelWithStepsInsideBranches(NestingType nestingType, int events) {
5860 var runner = LocalDurableTestRunner .create (String .class , (input , context ) -> {
5961 var config = ParallelConfig .builder ().nestingType (nestingType ).build ();
6062 var futures = new ArrayList <DurableFuture <String >>();
61- var parallel = context .parallel ("parallel-with-steps" , config );
63+ ParallelDurableFuture parallel = context .parallel ("parallel-with-steps" , config );
6264
6365 try (parallel ) {
6466 for (var item : List .of ("hello" , "world" )) {
@@ -118,7 +120,7 @@ void testParallelPartialFailure_failedBranchDoesNotPreventOthers(NestingType nes
118120 void testParallelAllBranchesFail (NestingType nestingType , int events ) {
119121 var runner = LocalDurableTestRunner .create (String .class , (input , context ) -> {
120122 var config = ParallelConfig .builder ().nestingType (nestingType ).build ();
121- var parallel = context .parallel ("all-fail" , config );
123+ ParallelDurableFuture parallel = context .parallel ("all-fail" , config );
122124
123125 try (parallel ) {
124126 parallel .branch ("branch-x" , String .class , ctx -> {
@@ -155,7 +157,7 @@ void testParallelWithMaxConcurrency1_sequentialExecution(NestingType nestingType
155157 .nestingType (nestingType )
156158 .build ();
157159 var futures = new ArrayList <DurableFuture <String >>();
158- var parallel = context .parallel ("sequential-parallel" , config );
160+ ParallelDurableFuture parallel = context .parallel ("sequential-parallel" , config );
159161
160162 try (parallel ) {
161163 for (var item : List .of ("a" , "b" , "c" , "d" )) {
@@ -194,7 +196,7 @@ void testParallelWithMaxConcurrency2_limitedConcurrency(NestingType nestingType,
194196 .nestingType (nestingType )
195197 .build ();
196198 var futures = new ArrayList <DurableFuture <String >>();
197- var parallel = context .parallel ("limited-parallel" , config );
199+ ParallelDurableFuture parallel = context .parallel ("limited-parallel" , config );
198200
199201 try (parallel ) {
200202 for (var item : List .of ("a" , "b" , "c" , "d" , "e" )) {
@@ -229,7 +231,7 @@ void testParallelReplayAfterInterruption_cachedResultsUsed(NestingType nestingTy
229231 var runner = LocalDurableTestRunner .create (String .class , (input , context ) -> {
230232 var config = ParallelConfig .builder ().nestingType (nestingType ).build ();
231233 var futures = new ArrayList <DurableFuture <String >>();
232- var parallel = context .parallel ("replay-parallel" , config );
234+ ParallelDurableFuture parallel = context .parallel ("replay-parallel" , config );
233235
234236 try (parallel ) {
235237 for (var item : List .of ("a" , "b" , "c" )) {
@@ -531,7 +533,7 @@ void testParallelUnlimitedConcurrencyWithToleratedFailureCount(NestingType nesti
531533 void testParallelBranchesReturnDifferentTypes (NestingType nestingType , int events ) {
532534 var runner = LocalDurableTestRunner .create (String .class , (input , context ) -> {
533535 var config = ParallelConfig .builder ().nestingType (nestingType ).build ();
534- var parallel = context .parallel ("mixed-types" , config );
536+ ParallelDurableFuture parallel = context .parallel ("mixed-types" , config );
535537
536538 DurableFuture <String > strFuture ;
537539 DurableFuture <Integer > intFuture ;
@@ -600,7 +602,7 @@ void testParallel50BranchesWithWaitForCallback(NestingType nestingType, int even
600602 var runner = LocalDurableTestRunner .create (String .class , (input , context ) -> {
601603 var config = ParallelConfig .builder ().nestingType (nestingType ).build ();
602604 var futures = new ArrayList <DurableFuture <String >>();
603- var parallel = context .parallel ("50-callbacks" , config );
605+ ParallelDurableFuture parallel = context .parallel ("50-callbacks" , config );
604606
605607 try (parallel ) {
606608 for (int i = 0 ; i < branchCount ; i ++) {
@@ -655,7 +657,7 @@ void testParallel50BranchesWithWaitForCallback_maxConcurrency5(NestingType nesti
655657 .maxConcurrency (5 )
656658 .nestingType (nestingType )
657659 .build ();
658- var parallel = context .parallel ("50-callbacks-limited" , config );
660+ ParallelDurableFuture parallel = context .parallel ("50-callbacks-limited" , config );
659661
660662 try (parallel ) {
661663 for (int i = 0 ; i < branchCount ; i ++) {
@@ -705,7 +707,7 @@ void testParallel50BranchesWithWaitForCallback_partialFailure(NestingType nestin
705707
706708 var runner = LocalDurableTestRunner .create (String .class , (input , context ) -> {
707709 var config = ParallelConfig .builder ().nestingType (nestingType ).build ();
708- var parallel = context .parallel ("50-callbacks-partial-fail" , config );
710+ ParallelDurableFuture parallel = context .parallel ("50-callbacks-partial-fail" , config );
709711
710712 try (parallel ) {
711713 for (int i = 0 ; i < branchCount ; i ++) {
@@ -859,7 +861,7 @@ void testParallel50BranchesWithWaitForCondition_someExceedMaxAttempts(NestingTyp
859861
860862 var runner = LocalDurableTestRunner .create (String .class , (input , context ) -> {
861863 var config = ParallelConfig .builder ().nestingType (nestingType ).build ();
862- var parallel = context .parallel ("50-conditions-some-fail" , config );
864+ ParallelDurableFuture parallel = context .parallel ("50-conditions-some-fail" , config );
863865
864866 try (parallel ) {
865867 for (int i = 0 ; i < branchCount ; i ++) {
@@ -910,7 +912,7 @@ void testParallel50BranchesWithWaitForCondition_replay(NestingType nestingType,
910912
911913 var runner = LocalDurableTestRunner .create (String .class , (input , context ) -> {
912914 var config = ParallelConfig .builder ().nestingType (nestingType ).build ();
913- var parallel = context .parallel ("50-conditions-replay" , config );
915+ ParallelDurableFuture parallel = context .parallel ("50-conditions-replay" , config );
914916
915917 try (parallel ) {
916918 for (int i = 0 ; i < branchCount ; i ++) {
@@ -960,7 +962,7 @@ void testParallel50BranchesMixed_callbackAndCondition(NestingType nestingType, i
960962
961963 var runner = LocalDurableTestRunner .create (String .class , (input , context ) -> {
962964 var config = ParallelConfig .builder ().nestingType (nestingType ).build ();
963- var parallel = context .parallel ("50-mixed" , config );
965+ ParallelDurableFuture parallel = context .parallel ("50-mixed" , config );
964966
965967 try (parallel ) {
966968 for (int i = 0 ; i < branchCount ; i ++) {
@@ -1089,6 +1091,71 @@ void testParallelWithMinSuccessful_earlyTermination(NestingType nestingType, int
10891091 assertEquals (events , result .getHistoryEvents ().size ());
10901092 }
10911093
1094+ @ ParameterizedTest
1095+ @ CsvSource ({"FLAT, 2" , "NESTED, 8" })
1096+ void testParallelWithMinSuccessful_earlyTermination_consistentResult (NestingType nestingType , int events ) {
1097+ var executionCount = new AtomicInteger (0 );
1098+ var initialResult = new AtomicReference <ParallelResult >();
1099+ var runner = LocalDurableTestRunner .create (String .class , (input , context ) -> {
1100+ var config = ParallelConfig .builder ()
1101+ .maxConcurrency (5 )
1102+ .completionConfig (CompletionConfig .minSuccessful (2 ))
1103+ .nestingType (nestingType )
1104+ .build ();
1105+ var futures = new ArrayList <DurableFuture <String >>();
1106+ ParallelDurableFuture parallel = context .parallel ("min-successful" , config );
1107+
1108+ try (parallel ) {
1109+ for (var item : List .of ("a" , "b" , "c" , "d" , "e" )) {
1110+ futures .add (parallel .branch ("branch-" + item , String .class , ctx -> {
1111+ executionCount .incrementAndGet ();
1112+ try {
1113+ if (executionCount .get () <= 5 && (item .equals ("a" ) || item .equals ("b" ))
1114+ || executionCount .get () > 5 && (item .equals ("c" ) || item .equals ("d" ))) {
1115+ Thread .sleep (1000 );
1116+ }
1117+ } catch (InterruptedException e ) {
1118+ throw new RuntimeException (e );
1119+ }
1120+
1121+ return item .toUpperCase ();
1122+ }));
1123+ }
1124+ }
1125+
1126+ var result = parallel .get ();
1127+ if (initialResult .get () == null ) {
1128+ initialResult .set (result );
1129+ } else {
1130+ assertEquals (initialResult .get (), result );
1131+ }
1132+ assertEquals (ConcurrencyCompletionStatus .MIN_SUCCESSFUL_REACHED , result .completionStatus ());
1133+ assertTrue (result .completionStatus ().isSucceeded ());
1134+ assertTrue (result .size () >= 2 && result .size () <= 5 );
1135+ assertEquals (ParallelResult .Status .SKIPPED , result .statuses ().get (0 ));
1136+ assertEquals (ParallelResult .Status .SKIPPED , result .statuses ().get (1 ));
1137+
1138+ return "done" ;
1139+ });
1140+
1141+ var result = runner .runUntilComplete ("test" );
1142+ assertEquals (ExecutionStatus .SUCCEEDED , result .getStatus ());
1143+ if (nestingType == NestingType .FLAT ) {
1144+ assertEquals (events , result .getHistoryEvents ().size ());
1145+ } else {
1146+ assertTrue (events <= result .getHistoryEvents ().size ());
1147+ assertTrue (result .getHistoryEvents ().size () <= events + 2 );
1148+ }
1149+
1150+ var result2 = runner .run ("test" );
1151+ assertEquals (ExecutionStatus .SUCCEEDED , result2 .getStatus ());
1152+ if (nestingType == NestingType .FLAT ) {
1153+ assertEquals (events , result2 .getHistoryEvents ().size ());
1154+ } else {
1155+ assertTrue (result2 .getHistoryEvents ().size () <= events + 2 );
1156+ }
1157+ }
1158+
10921159 @ ParameterizedTest
10931160 @ CsvSource ({"FLAT, 2" , "NESTED, 6" })
10941161 void testParallelWithAllSuccessful_stopsOnFirstFailure (NestingType nestingType , int events ) {
0 commit comments