@@ -356,6 +356,73 @@ describe("In-Memory Backend", () => {
356356 expect ( state ) . toBeUndefined ( ) ;
357357 } ) ;
358358
359+ it ( "should cancel pending timers when purging a terminated orchestration" , async ( ) => {
360+ const orchestrator : TOrchestrator = async function * ( ctx : OrchestrationContext ) : any {
361+ // Create a timer far in the future — it will still be pending when we terminate
362+ yield ctx . createTimer ( 3600 ) ;
363+ return "done" ;
364+ } ;
365+
366+ worker . addOrchestrator ( orchestrator ) ;
367+ await worker . start ( ) ;
368+
369+ const id = await client . scheduleNewOrchestration ( orchestrator ) ;
370+ // Wait for the orchestration to start so the timer action is processed by the backend
371+ await client . waitForOrchestrationStart ( id , false , 5 ) ;
372+
373+ // Terminate while the long timer is still pending
374+ await client . terminateOrchestration ( id , "terminated" ) ;
375+ const state = await client . waitForOrchestrationCompletion ( id , true , 10 ) ;
376+ expect ( state ?. runtimeStatus ) . toEqual ( OrchestrationStatus . TERMINATED ) ;
377+
378+ // Timer should still be pending before purge
379+ const pendingTimersBefore = ( backend as any ) . pendingTimers . size ;
380+ expect ( pendingTimersBefore ) . toBeGreaterThan ( 0 ) ;
381+
382+ // Purge the terminated orchestration
383+ const result = await client . purgeOrchestration ( id ) ;
384+ expect ( result . deletedInstanceCount ) . toEqual ( 1 ) ;
385+
386+ // After purge, pending timers for this instance should be cancelled
387+ expect ( ( backend as any ) . pendingTimers . size ) . toBe ( 0 ) ;
388+ expect ( ( backend as any ) . instanceTimers . size ) . toBe ( 0 ) ;
389+ } ) ;
390+
391+ it ( "should cancel pending timers for only the purged orchestration" , async ( ) => {
392+ const timerOrchestrator : TOrchestrator = async function * ( ctx : OrchestrationContext ) : any {
393+ yield ctx . createTimer ( 3600 ) ;
394+ return "done" ;
395+ } ;
396+
397+ const waitOrchestrator : TOrchestrator = async function * ( ctx : OrchestrationContext ) : any {
398+ yield ctx . createTimer ( 7200 ) ;
399+ return "done" ;
400+ } ;
401+
402+ worker . addOrchestrator ( timerOrchestrator ) ;
403+ worker . addOrchestrator ( waitOrchestrator ) ;
404+ await worker . start ( ) ;
405+
406+ // Start two orchestrations that both create long timers
407+ const id1 = await client . scheduleNewOrchestration ( timerOrchestrator ) ;
408+ const id2 = await client . scheduleNewOrchestration ( waitOrchestrator ) ;
409+
410+ await client . waitForOrchestrationStart ( id1 , false , 5 ) ;
411+ await client . waitForOrchestrationStart ( id2 , false , 5 ) ;
412+
413+ // Terminate and purge only the first orchestration
414+ await client . terminateOrchestration ( id1 , "terminated" ) ;
415+ await client . waitForOrchestrationCompletion ( id1 , false , 10 ) ;
416+
417+ const result = await client . purgeOrchestration ( id1 ) ;
418+ expect ( result . deletedInstanceCount ) . toEqual ( 1 ) ;
419+
420+ // The second orchestration's timer should still be pending
421+ expect ( ( backend as any ) . pendingTimers . size ) . toBe ( 1 ) ;
422+ expect ( ( backend as any ) . instanceTimers . has ( id2 ) ) . toBe ( true ) ;
423+ expect ( ( backend as any ) . instanceTimers . has ( id1 ) ) . toBe ( false ) ;
424+ } ) ;
425+
359426 it ( "should allow reusing instance IDs after reset" , async ( ) => {
360427 const orchestrator : TOrchestrator = async ( _ : OrchestrationContext , input : number ) => {
361428 return input * 2 ;
0 commit comments