@@ -365,6 +365,40 @@ function makeSetPatchMarker(myPatchId: string, deprecated: boolean): coresdk.wor
365
365
} ;
366
366
}
367
367
368
+ function makeUpdateActivationJob (
369
+ id : string ,
370
+ protocolInstanceId : string ,
371
+ name : string ,
372
+ input : unknown [ ]
373
+ ) : coresdk . workflow_activation . IWorkflowActivationJob {
374
+ return {
375
+ doUpdate : {
376
+ id,
377
+ protocolInstanceId,
378
+ name,
379
+ input : toPayloads ( defaultPayloadConverter , ...input ) ,
380
+ } ,
381
+ } ;
382
+ }
383
+
384
+ function makeUpdateAcceptedResponse ( id : string ) : coresdk . workflow_commands . IWorkflowCommand {
385
+ return {
386
+ updateResponse : {
387
+ protocolInstanceId : id ,
388
+ accepted : { } ,
389
+ } ,
390
+ } ;
391
+ }
392
+
393
+ function makeUpdateCompleteResponse ( id : string , result : unknown ) : coresdk . workflow_commands . IWorkflowCommand {
394
+ return {
395
+ updateResponse : {
396
+ protocolInstanceId : id ,
397
+ completed : defaultPayloadConverter . toPayload ( result ) ,
398
+ } ,
399
+ } ;
400
+ }
401
+
368
402
test ( 'random' , async ( t ) => {
369
403
const { logs, workflowType } = t . context ;
370
404
{
@@ -2404,23 +2438,9 @@ test('Signals/Updates/Activities/Timers - Trace promises completion order - pre-
2404
2438
...makeActivation (
2405
2439
undefined ,
2406
2440
makeSignalWorkflowJob ( 'aaSignal' , [ 'signal1' ] ) ,
2407
- {
2408
- doUpdate : {
2409
- id : 'first' ,
2410
- name : 'aaUpdate' ,
2411
- protocolInstanceId : '1' ,
2412
- input : toPayloads ( defaultPayloadConverter , [ 'update1' ] ) ,
2413
- } ,
2414
- } ,
2441
+ makeUpdateActivationJob ( 'first' , '1' , 'aaUpdate' , [ 'update1' ] ) ,
2415
2442
makeSignalWorkflowJob ( 'aaSignal' , [ 'signal2' ] ) ,
2416
- {
2417
- doUpdate : {
2418
- id : 'second' ,
2419
- name : 'aaUpdate' ,
2420
- protocolInstanceId : '2' ,
2421
- input : toPayloads ( defaultPayloadConverter , [ 'update2' ] ) ,
2422
- } ,
2423
- } ,
2443
+ makeUpdateActivationJob ( 'second' , '2' , 'aaUpdate' , [ 'update2' ] ) ,
2424
2444
makeFireTimerJob ( 1 ) ,
2425
2445
makeResolveActivityJob ( 1 , { completed : { } } )
2426
2446
) ,
@@ -2483,23 +2503,9 @@ test('Signals/Updates/Activities/Timers - Trace promises completion order - 1.11
2483
2503
...makeActivation (
2484
2504
undefined ,
2485
2505
makeSignalWorkflowJob ( 'aaSignal' , [ 'signal1' ] ) ,
2486
- {
2487
- doUpdate : {
2488
- id : 'first' ,
2489
- name : 'aaUpdate' ,
2490
- protocolInstanceId : '1' ,
2491
- input : toPayloads ( defaultPayloadConverter , [ 'update1' ] ) ,
2492
- } ,
2493
- } ,
2506
+ makeUpdateActivationJob ( 'first' , '1' , 'aaUpdate' , [ 'update1' ] ) ,
2494
2507
makeSignalWorkflowJob ( 'aaSignal' , [ 'signal2' ] ) ,
2495
- {
2496
- doUpdate : {
2497
- id : 'second' ,
2498
- name : 'aaUpdate' ,
2499
- protocolInstanceId : '2' ,
2500
- input : toPayloads ( defaultPayloadConverter , [ 'update2' ] ) ,
2501
- } ,
2502
- } ,
2508
+ makeUpdateActivationJob ( 'second' , '2' , 'aaUpdate' , [ 'update2' ] ) ,
2503
2509
makeFireTimerJob ( 1 ) ,
2504
2510
makeResolveActivityJob ( 1 , { completed : { } } )
2505
2511
) ,
@@ -2530,3 +2536,138 @@ test('Signals/Updates/Activities/Timers - Trace promises completion order - 1.11
2530
2536
) ;
2531
2537
}
2532
2538
} ) ;
2539
+
2540
+ test ( 'Buffered updates are dispatched in the correct order - updatesOrdering' , async ( t ) => {
2541
+ const { workflowType } = t . context ;
2542
+ {
2543
+ const completion = await activate (
2544
+ t ,
2545
+ makeActivation (
2546
+ undefined ,
2547
+ makeInitializeWorkflowJob ( workflowType ) ,
2548
+ makeUpdateActivationJob ( '1' , '1' , 'non-existant' , [ 1 ] ) ,
2549
+ makeUpdateActivationJob ( '2' , '2' , 'updateA' , [ 2 ] ) ,
2550
+ makeUpdateActivationJob ( '3' , '3' , 'updateA' , [ 3 ] ) ,
2551
+ makeUpdateActivationJob ( '4' , '4' , 'updateC' , [ 4 ] ) ,
2552
+ makeUpdateActivationJob ( '5' , '5' , 'updateB' , [ 5 ] ) ,
2553
+ makeUpdateActivationJob ( '6' , '6' , 'non-existant' , [ 6 ] ) ,
2554
+ makeUpdateActivationJob ( '7' , '7' , 'updateB' , [ 7 ] )
2555
+ )
2556
+ ) ;
2557
+
2558
+ // The activation above:
2559
+ // - initializes the workflow
2560
+ // - buffers all its updates (we attempt update jobs first, but since there are no handlers, they get buffered)
2561
+ // - enters the workflow code
2562
+ // - workflow code sets handler for updateA
2563
+ // - handler is registered for updateA
2564
+ // - we attempt to dispatch buffered updates
2565
+ // - buffered updates for handler A are *accepted* but not executed
2566
+ // (executing an update is a promise/async, so it instead goes on the node event queue)
2567
+ // - we continue/re-enter the workflow code
2568
+ // - ...and do the same pattern for updateB, the default update handler, the updateC
2569
+ // - once updates have been accepted, node processes the waiting events in its queue (the waiting updates)
2570
+ // - these are processesed in FIFO order, so we get execution for updateA, then updateB, the default handler, then updateC
2571
+
2572
+ // As such, the expected order of these updates is the order that the handlers were registered.
2573
+ // Note that because the default handler was registered *before* updateC, all remaining buffered updates were dispatched
2574
+ // to it, including the update for updateC.
2575
+
2576
+ compareCompletion (
2577
+ t ,
2578
+ completion ,
2579
+ makeSuccess (
2580
+ [
2581
+ // FIFO accepted order
2582
+ makeUpdateAcceptedResponse ( '2' ) ,
2583
+ makeUpdateAcceptedResponse ( '3' ) ,
2584
+ makeUpdateAcceptedResponse ( '5' ) ,
2585
+ makeUpdateAcceptedResponse ( '7' ) ,
2586
+ makeUpdateAcceptedResponse ( '1' ) ,
2587
+ makeUpdateAcceptedResponse ( '4' ) ,
2588
+ makeUpdateAcceptedResponse ( '6' ) ,
2589
+ // FIFO executed order
2590
+ makeUpdateCompleteResponse ( '2' , { handler : 'updateA' , args : [ 2 ] } ) ,
2591
+ makeUpdateCompleteResponse ( '3' , { handler : 'updateA' , args : [ 3 ] } ) ,
2592
+ makeUpdateCompleteResponse ( '5' , { handler : 'updateB' , args : [ 5 ] } ) ,
2593
+ makeUpdateCompleteResponse ( '7' , { handler : 'updateB' , args : [ 7 ] } ) ,
2594
+ makeUpdateCompleteResponse ( '1' , { handler : 'default' , updateName : 'non-existant' , args : [ 1 ] } ) ,
2595
+ // updateC handled by default handler.
2596
+ makeUpdateCompleteResponse ( '4' , { handler : 'default' , updateName : 'updateC' , args : [ 4 ] } ) ,
2597
+ makeUpdateCompleteResponse ( '6' , { handler : 'default' , updateName : 'non-existant' , args : [ 6 ] } ) ,
2598
+ // No expected update response from updateC handler
2599
+ makeCompleteWorkflowExecution ( ) ,
2600
+ ]
2601
+ // [SdkFlags.ProcessWorkflowActivationJobsAsSingleBatch]
2602
+ )
2603
+ ) ;
2604
+ }
2605
+ } ) ;
2606
+
2607
+ test ( 'Buffered updates are reentrant - updatesAreReentrant' , async ( t ) => {
2608
+ const { workflowType } = t . context ;
2609
+ {
2610
+ const completion = await activate (
2611
+ t ,
2612
+ makeActivation (
2613
+ undefined ,
2614
+ makeInitializeWorkflowJob ( workflowType ) ,
2615
+ makeUpdateActivationJob ( '1' , '1' , 'non-existant' , [ 1 ] ) ,
2616
+ makeUpdateActivationJob ( '2' , '2' , 'updateA' , [ 2 ] ) ,
2617
+ makeUpdateActivationJob ( '3' , '3' , 'updateA' , [ 3 ] ) ,
2618
+ makeUpdateActivationJob ( '4' , '4' , 'updateC' , [ 4 ] ) ,
2619
+ makeUpdateActivationJob ( '5' , '5' , 'updateB' , [ 5 ] ) ,
2620
+ makeUpdateActivationJob ( '6' , '6' , 'non-existant' , [ 6 ] ) ,
2621
+ makeUpdateActivationJob ( '7' , '7' , 'updateB' , [ 7 ] ) ,
2622
+ makeUpdateActivationJob ( '8' , '8' , 'updateC' , [ 8 ] )
2623
+ )
2624
+ ) ;
2625
+
2626
+ // The activation above:
2627
+ // - initializes the workflow
2628
+ // - buffers all its updates (we attempt update jobs first, but since there are no handlers, they get buffered)
2629
+ // - enters the workflow code
2630
+ // - workflow code sets handler for updateA
2631
+ // - handler is registered for updateA
2632
+ // - we attempt to dispatch buffered updates
2633
+ // - buffered updates for handler A are *accepted* but not executed
2634
+ // (executing an update is a promise/async, so it instead goes on the node event queue)
2635
+ // - however, there is no more workflow code, node dequues event queue and we immediately run the update handler
2636
+ // (we begin executing the update which...)
2637
+ // - deletes the current handler and registers the next one (updateB)
2638
+ // - this pattern repeats (updateA -> updateB -> updateC -> default) until there are no more updates to handle
2639
+ // - at this point, all updates have been accepted and are executing
2640
+ // - due to the call order in the workflow, the completion order of the updates follows the call stack, LIFO
2641
+
2642
+ // This workflow is interesting in that updates are accepted FIFO, but executed LIFO
2643
+
2644
+ compareCompletion (
2645
+ t ,
2646
+ completion ,
2647
+ makeSuccess (
2648
+ [
2649
+ // FIFO accepted order
2650
+ makeUpdateAcceptedResponse ( '2' ) ,
2651
+ makeUpdateAcceptedResponse ( '5' ) ,
2652
+ makeUpdateAcceptedResponse ( '4' ) ,
2653
+ makeUpdateAcceptedResponse ( '1' ) ,
2654
+ makeUpdateAcceptedResponse ( '3' ) ,
2655
+ makeUpdateAcceptedResponse ( '7' ) ,
2656
+ makeUpdateAcceptedResponse ( '8' ) ,
2657
+ makeUpdateAcceptedResponse ( '6' ) ,
2658
+ // LIFO executed order
2659
+ makeUpdateCompleteResponse ( '6' , { handler : 'default' , updateName : 'non-existant' , args : [ 6 ] } ) ,
2660
+ makeUpdateCompleteResponse ( '8' , { handler : 'updateC' , args : [ 8 ] } ) ,
2661
+ makeUpdateCompleteResponse ( '7' , { handler : 'updateB' , args : [ 7 ] } ) ,
2662
+ makeUpdateCompleteResponse ( '3' , { handler : 'updateA' , args : [ 3 ] } ) ,
2663
+ makeUpdateCompleteResponse ( '1' , { handler : 'default' , updateName : 'non-existant' , args : [ 1 ] } ) ,
2664
+ makeUpdateCompleteResponse ( '4' , { handler : 'updateC' , args : [ 4 ] } ) ,
2665
+ makeUpdateCompleteResponse ( '5' , { handler : 'updateB' , args : [ 5 ] } ) ,
2666
+ makeUpdateCompleteResponse ( '2' , { handler : 'updateA' , args : [ 2 ] } ) ,
2667
+ makeCompleteWorkflowExecution ( ) ,
2668
+ ]
2669
+ // [SdkFlags.ProcessWorkflowActivationJobsAsSingleBatch]
2670
+ )
2671
+ ) ;
2672
+ }
2673
+ } ) ;
0 commit comments