@@ -92,6 +92,20 @@ public void testAsyncAwaitCancellation() {
9292 assertEquals ("cancelled" , result );
9393 }
9494
95+ @ Test
96+ public void testTimedAsyncAwaitCancellation () {
97+ TestWorkflow1 workflow = testWorkflowRule .newWorkflowStubTimeoutOptions (TestWorkflow1 .class );
98+ String result = workflow .execute ("timed-cancellation" );
99+ assertEquals ("timed-cancelled" , result );
100+ }
101+
102+ @ Test
103+ public void testAsyncAwaitConditionThrows () {
104+ TestWorkflow1 workflow = testWorkflowRule .newWorkflowStubTimeoutOptions (TestWorkflow1 .class );
105+ String result = workflow .execute ("condition-throws" );
106+ assertEquals ("caught:simulated error" , result );
107+ }
108+
95109 /** Combined workflow that handles all test scenarios. */
96110 public static class TestAsyncAwaitWorkflow implements TestWorkflow1 {
97111 private boolean condition1 = false ;
@@ -122,6 +136,10 @@ public String execute(String testCase) {
122136 return testChaining ();
123137 case "cancellation" :
124138 return testCancellation ();
139+ case "timed-cancellation" :
140+ return testTimedCancellation ();
141+ case "condition-throws" :
142+ return testConditionThrows ();
125143 default :
126144 return "unknown test case" ;
127145 }
@@ -287,5 +305,59 @@ private String testCancellation() {
287305 return "cancelled" ;
288306 }
289307 }
308+
309+ private String testTimedCancellation () {
310+ condition1 = false ;
311+ final Promise <Boolean >[] promiseHolder = new Promise [1 ];
312+
313+ CancellationScope scope =
314+ Workflow .newCancellationScope (
315+ () -> {
316+ // Create a timed async await that will never complete on its own
317+ promiseHolder [0 ] = Async .await (Duration .ofHours (1 ), () -> condition1 );
318+ });
319+
320+ // Run the scope (this is non-blocking since Async.await returns immediately)
321+ scope .run ();
322+
323+ // Cancel the scope
324+ scope .cancel ();
325+
326+ // The promise should fail with CanceledFailure when we try to get it
327+ try {
328+ promiseHolder [0 ].get ();
329+ return "timed-not-cancelled" ;
330+ } catch (CanceledFailure e ) {
331+ return "timed-cancelled" ;
332+ }
333+ }
334+
335+ private String testConditionThrows () {
336+ // Start with condition that doesn't throw, but will throw on subsequent evaluation
337+ // Initial check returns false (doesn't throw), then later evaluation throws
338+ counter = 0 ;
339+
340+ Promise <Void > promise =
341+ Async .await (
342+ () -> {
343+ counter ++;
344+ // First evaluation (initial check) returns false
345+ // Second evaluation (in evaluateConditionWatchers) throws
346+ if (counter > 1 ) {
347+ throw new RuntimeException ("simulated error" );
348+ }
349+ return false ;
350+ });
351+
352+ // Trigger re-evaluation by sleeping (causes event loop iteration)
353+ Workflow .sleep (Duration .ofMillis (1 ));
354+
355+ try {
356+ promise .get ();
357+ return "no-exception" ;
358+ } catch (RuntimeException e ) {
359+ return "caught:" + e .getMessage ();
360+ }
361+ }
290362 }
291363}
0 commit comments