11import { RpcTarget } from "cloudflare:workers" ;
22import { ms } from "itty-time" ;
3+ import { abortableWait } from "./engine" ;
34import { INSTANCE_METADATA , InstanceEvent , InstanceStatus } from "./instance" ;
45import { computeHash } from "./lib/cache" ;
56import {
@@ -279,7 +280,10 @@ export class Context extends RpcTarget {
279280 // complete sleep if it didn't finish for some reason
280281 if ( retryEntryPQ !== undefined ) {
281282 await this . #engine. timeoutHandler . release ( this . #engine) ;
282- await scheduler . wait ( retryEntryPQ . targetTimestamp - Date . now ( ) ) ;
283+ await abortableWait (
284+ retryEntryPQ . targetTimestamp - Date . now ( ) ,
285+ this . #engine. engineAbortController . signal
286+ ) ;
283287 await this . #engine. timeoutHandler . acquire ( this . #engine) ;
284288 // @ts -expect-error priorityQueue is initiated in init
285289 this . #engine. priorityQueue . remove ( {
@@ -298,6 +302,11 @@ export class Context extends RpcTarget {
298302 }
299303 const { accountId, instance } = instanceMetadata ;
300304
305+ // AbortController to cancel the timeout when the step callback
306+ // wins the Promise.race — matches production's pattern of
307+ // aborting the losing side to prevent dangling promises.
308+ const stepAbortController = new AbortController ( ) ;
309+
301310 try {
302311 const timeoutPromise = async ( ) => {
303312 const priorityQueueHash = `${ cacheKey } -${ stepState . attemptedCount } ` ;
@@ -311,7 +320,13 @@ export class Context extends RpcTarget {
311320 targetTimestamp : Date . now ( ) + timeout ,
312321 type : "timeout" ,
313322 } ) ;
314- await scheduler . wait ( timeout ) ;
323+ await abortableWait (
324+ timeout ,
325+ AbortSignal . any ( [
326+ stepAbortController . signal ,
327+ this . #engine. engineAbortController . signal ,
328+ ] )
329+ ) ;
315330 // if we reach here, means that we can try to delete the timeout from the PQ
316331 // because we managed to wait in the same lifetime
317332 // @ts -expect-error priorityQueue is initiated in init
@@ -378,6 +393,9 @@ export class Context extends RpcTarget {
378393 ] ) ;
379394 }
380395
396+ // Cancel the timeout so its scheduler.wait doesn't dangle
397+ stepAbortController . abort ( "step finished" ) ;
398+
381399 // if we reach here, means that the clouse ran successfully and we can remove the timeout from the PQ
382400 // @ts -expect-error priorityQueue is initiated in init
383401 await this . #engine. priorityQueue . remove ( {
@@ -448,6 +466,9 @@ export class Context extends RpcTarget {
448466 }
449467 ) ;
450468 } catch ( e ) {
469+ // Cancel the timeout so its scheduler.wait doesn't dangle
470+ stepAbortController . abort ( "step errored" ) ;
471+
451472 const error = e as Error ;
452473 // if we reach here, means that the clouse ran but errored out and we can remove the timeout from the PQ
453474 // @ts -expect-error priorityQueue is initiated in init
@@ -512,7 +533,10 @@ export class Context extends RpcTarget {
512533 } ) ;
513534 await this . #engine. timeoutHandler . release ( this . #engine) ;
514535 // this may never finish because of the grace period - but waker will take of it
515- await scheduler . wait ( durationMs ) ;
536+ await abortableWait (
537+ durationMs ,
538+ this . #engine. engineAbortController . signal
539+ ) ;
516540
517541 // if it ever reaches here, we can try to remove it from the priority queue since it's no longer useful
518542 // @ts -expect-error priorityQueue is initiated in init
@@ -590,8 +614,9 @@ export class Context extends RpcTarget {
590614 ) ;
591615 // in case the engine dies while sleeping and wakes up before the retry period
592616 if ( entryPQ !== undefined ) {
593- await scheduler . wait (
594- disableSleep ? 0 : entryPQ . targetTimestamp - Date . now ( )
617+ await abortableWait (
618+ disableSleep ? 0 : entryPQ . targetTimestamp - Date . now ( ) ,
619+ this . #engine. engineAbortController . signal
595620 ) ;
596621 // @ts -expect-error priorityQueue is initiated in init
597622 this . #engine. priorityQueue . remove ( { hash : cacheKey , type : "sleep" } ) ;
@@ -635,7 +660,10 @@ export class Context extends RpcTarget {
635660 } ) ;
636661
637662 // this probably will never finish except if sleep is less than the grace period
638- await scheduler . wait ( disableSleep ? 0 : duration ) ;
663+ await abortableWait (
664+ disableSleep ? 0 : duration ,
665+ this . #engine. engineAbortController . signal
666+ ) ;
639667
640668 this . #engine. writeLog (
641669 InstanceEvent . SLEEP_COMPLETE ,
@@ -771,7 +799,10 @@ export class Context extends RpcTarget {
771799 type : "timeout" ,
772800 } ) ;
773801 }
774- await scheduler . wait ( timeoutToWait ) ;
802+ await abortableWait (
803+ timeoutToWait ,
804+ this . #engine. engineAbortController . signal
805+ ) ;
775806 // if we reach here, means that we can try to delete the timeout from the PQ
776807 // because we managed to wait in the same lifetime
777808
0 commit comments