1616import java .util .concurrent .CompletableFuture ;
1717import java .util .concurrent .ExecutionException ;
1818import java .util .concurrent .TimeUnit ;
19- import java .util .concurrent .TimeoutException ;
2019
2120import org .junit .jupiter .api .DisplayName ;
2221import org .junit .jupiter .api .Test ;
@@ -58,59 +57,30 @@ private WorkflowExecutor executorFor(WorkflowClient workflowClient) {
5857 }
5958
6059 @ Test
61- @ DisplayName ("the monitor should keep polling after a transient getWorkflow failure" )
62- void monitorSurvivesTransientPollFailure () throws Exception {
63- Workflow completed = new Workflow ();
64- completed .setStatus (Workflow .WorkflowStatus .COMPLETED );
65-
66- WorkflowClient workflowClient = mock (WorkflowClient .class );
67- when (workflowClient .startWorkflow (any (StartWorkflowRequest .class ))).thenReturn (WORKFLOW_ID );
68- when (workflowClient .getWorkflow (anyString (), anyBoolean ()))
69- .thenThrow (new RuntimeException ("transient failure" ))
70- .thenReturn (completed );
71-
72- WorkflowExecutor executor = executorFor (workflowClient );
73- try {
74- CompletableFuture <Workflow > future = executor .executeWorkflow ("wf" , 1 , Map .of ());
75-
76- Workflow result = future .get (5 , TimeUnit .SECONDS );
77-
78- assertEquals (Workflow .WorkflowStatus .COMPLETED , result .getStatus ());
79- } finally {
80- executor .shutdown ();
81- }
82- }
83-
84- @ Test
85- @ DisplayName ("the monitor should give up and fail the future once the failure budget is spent" )
86- void monitorGivesUpOnPersistentPollFailure () {
60+ @ DisplayName ("a failed poll should complete that workflow's future exceptionally" )
61+ void monitorFailsTheFutureOnPollFailure () {
8762 WorkflowClient workflowClient = mock (WorkflowClient .class );
8863 when (workflowClient .startWorkflow (any (StartWorkflowRequest .class ))).thenReturn (WORKFLOW_ID );
8964 when (workflowClient .getWorkflow (anyString (), anyBoolean ()))
90- .thenThrow (new RuntimeException ("permanent failure" ));
65+ .thenThrow (new RuntimeException ("poll failure" ));
9166
9267 WorkflowExecutor executor = executorFor (workflowClient );
9368 try {
94- // 1ms budget: the monitor ticks every 100ms, so the second consecutive failure is
95- // already past it. Keeps the test off the wall clock while still exercising a real
96- // (positive, opt-in) budget rather than the never-give-up default.
97- executor .setMonitorFailureGiveUpMillis (1 );
98-
9969 CompletableFuture <Workflow > future = executor .executeWorkflow ("wf" , 1 , Map .of ());
10070
10171 ExecutionException thrown = assertThrows (
10272 ExecutionException .class , () -> future .get (5 , TimeUnit .SECONDS ));
10373
10474 assertInstanceOf (RuntimeException .class , thrown .getCause ());
105- assertEquals ("permanent failure" , thrown .getCause ().getMessage ());
75+ assertEquals ("poll failure" , thrown .getCause ().getMessage ());
10676 } finally {
10777 executor .shutdown ();
10878 }
10979 }
11080
11181 @ Test
112- @ DisplayName ("by default the monitor should never give up, and one bad workflow should not stall the rest" )
113- void monitorDoesNotGiveUpByDefault () throws Exception {
82+ @ DisplayName ("one workflow's poll failure should not stop the monitor tracking the rest" )
83+ void monitorKeepsTrackingOtherWorkflowsAfterAFailure () throws Exception {
11484 Workflow completed = new Workflow ();
11585 completed .setStatus (Workflow .WorkflowStatus .COMPLETED );
11686
@@ -119,24 +89,20 @@ void monitorDoesNotGiveUpByDefault() throws Exception {
11989 .thenReturn (WORKFLOW_ID )
12090 .thenReturn (OTHER_WORKFLOW_ID );
12191 when (workflowClient .getWorkflow (eq (WORKFLOW_ID ), anyBoolean ()))
122- .thenThrow (new RuntimeException ("permanent failure" ));
92+ .thenThrow (new RuntimeException ("poll failure" ));
12393 when (workflowClient .getWorkflow (eq (OTHER_WORKFLOW_ID ), anyBoolean ()))
12494 .thenReturn (completed );
12595
12696 WorkflowExecutor executor = executorFor (workflowClient );
12797 try {
128- assertEquals (0 , executor .getMonitorFailureGiveUpMillis (), "give-up should be off by default" );
129-
13098 CompletableFuture <Workflow > failing = executor .executeWorkflow ("wf" , 1 , Map .of ());
131- CompletableFuture < Workflow > healthy = executor . executeWorkflow ( "wf" , 1 , Map . of ( ));
99+ assertThrows ( ExecutionException . class , () -> failing . get ( 5 , TimeUnit . SECONDS ));
132100
133- // The unresolvable workflow must not take the monitor down with it: a sibling
134- // registered on the same tick loop still completes.
101+ // Registered only after the failure has already happened, so it can complete at all
102+ // only if the tick loop survived it -- scheduleAtFixedRate would have cancelled every
103+ // future tick had the exception been allowed to escape.
104+ CompletableFuture <Workflow > healthy = executor .executeWorkflow ("wf" , 1 , Map .of ());
135105 assertEquals (Workflow .WorkflowStatus .COMPLETED , healthy .get (5 , TimeUnit .SECONDS ).getStatus ());
136-
137- // ...and the unresolvable one stays pending rather than being failed, which is the
138- // pre-bounded-retry behavior callers depend on across a server restart.
139- assertThrows (TimeoutException .class , () -> failing .get (1 , TimeUnit .SECONDS ));
140106 } finally {
141107 executor .shutdown ();
142108 }
0 commit comments