@@ -17,7 +17,6 @@ import {
1717import { calcRetryDuration } from "./lib/retries" ;
1818import {
1919 parseRollbackOptions ,
20- registerRollbackFn ,
2120 ROLLBACK_CACHE_KEY_PREFIX ,
2221} from "./lib/rollback" ;
2322import {
@@ -247,7 +246,7 @@ export class Context extends RpcTarget {
247246 const { cacheKey, rollbackFn, stepContext, output, rollbackConfig } =
248247 options ;
249248 if ( rollbackFn && this . #rollbackStep === undefined ) {
250- registerRollbackFn ( this . #engine. rollbackRegistry , {
249+ this . #engine. registerRollbackFn ( {
251250 cacheKey,
252251 fn : rollbackFn ,
253252 stepContext,
@@ -297,6 +296,12 @@ export class Context extends RpcTarget {
297296 const { rollback : rollbackFn , rollbackConfig } = rollbackOptions ?? { } ;
298297
299298 const isRollback = this . #rollbackStep !== undefined ;
299+ if ( this . #engine. rollbackPhase === "rollback" && ! isRollback ) {
300+ throw new WorkflowFatalError (
301+ "Cannot execute steps during rollback phase"
302+ ) ;
303+ }
304+
300305 const events = isRollback
301306 ? {
302307 start : InstanceEvent . ROLLBACK_STEP_START ,
@@ -454,6 +459,17 @@ export class Context extends RpcTarget {
454459 ) as Error | undefined ;
455460
456461 if ( maybeError ) {
462+ const cachedState = maybeMap . get ( stepStateKey ) as StepState | undefined ;
463+ this . #registerRollback( {
464+ cacheKey,
465+ rollbackFn,
466+ stepContext : {
467+ step : { name, count } ,
468+ attempt : cachedState ?. attemptedCount ?? 1 ,
469+ config : cachedConfig ?? config ,
470+ } ,
471+ rollbackConfig,
472+ } ) ;
457473 maybeError . isUserError = true ;
458474 throw maybeError ;
459475 }
@@ -465,6 +481,21 @@ export class Context extends RpcTarget {
465481 config = cachedConfig ;
466482 }
467483
484+ if ( this . #engine. rollbackPhase === "replay" && ! isRollback ) {
485+ const cachedState = maybeMap . get ( stepStateKey ) as StepState | undefined ;
486+ this . #registerRollback( {
487+ cacheKey,
488+ rollbackFn,
489+ stepContext : {
490+ step : { name, count } ,
491+ attempt : cachedState ?. attemptedCount ?? 1 ,
492+ config,
493+ } ,
494+ rollbackConfig,
495+ } ) ;
496+ return undefined ;
497+ }
498+
468499 const attemptLogs = this . #engine
469500 . readLogsFromStep ( cacheKey )
470501 . filter ( ( val ) =>
@@ -538,6 +569,7 @@ export class Context extends RpcTarget {
538569 if ( stepState . attemptedCount == 0 ) {
539570 this . #engine. writeLog ( events . start , cacheKey , stepNameWithCounter , {
540571 config,
572+ ...( ! isRollback && rollbackFn ? { hasRollback : true } : { } ) ,
541573 } ) ;
542574 } else {
543575 // in case the engine dies while retrying and wakes up before the retry period
@@ -623,6 +655,12 @@ export class Context extends RpcTarget {
623655 }
624656 ) ;
625657 stepState . attemptedCount ++ ;
658+ this . #registerRollback( {
659+ cacheKey,
660+ rollbackFn,
661+ stepContext : forwardStepContext ( ) ,
662+ rollbackConfig,
663+ } ) ;
626664 await this . #state. storage . put ( stepStateKey , stepState ) ;
627665 const priorityQueueHash = `${ cacheKey } -${ stepState . attemptedCount } ` ;
628666
@@ -937,7 +975,7 @@ export class Context extends RpcTarget {
937975 events . failure ,
938976 cacheKey ,
939977 stepNameWithCounter ,
940- { }
978+ ! isRollback && rollbackFn ? { hasRollback : true } : { }
941979 ) ;
942980 this . #registerRollback( {
943981 cacheKey,
@@ -1039,7 +1077,7 @@ export class Context extends RpcTarget {
10391077 events . failure ,
10401078 cacheKey ,
10411079 stepNameWithCounter ,
1042- { }
1080+ ! isRollback && rollbackFn ? { hasRollback : true } : { }
10431081 ) ;
10441082 this . #registerRollback( {
10451083 cacheKey,
@@ -1059,6 +1097,7 @@ export class Context extends RpcTarget {
10591097 ...( lastStreamMeta && {
10601098 streamOutput : { cacheKey, meta : lastStreamMeta } ,
10611099 } ) ,
1100+ ...( ! isRollback && rollbackFn ? { hasRollback : true } : { } ) ,
10621101 } ) ;
10631102 this . #registerRollback( {
10641103 cacheKey,
@@ -1073,13 +1112,21 @@ export class Context extends RpcTarget {
10731112
10741113 const result = await doWrapper ( closure ) ;
10751114
1076- // Check if a pause was requested while this step was running
1077- await this . #checkForPendingPause( ) ;
1115+ // Check if a pause was requested while this step was running.
1116+ // Rollback steps may run while the instance is paused; don't let stale pause
1117+ // state abort rollback cleanup after the rollback step itself succeeds.
1118+ if ( ! isRollback ) {
1119+ await this . #checkForPendingPause( ) ;
1120+ }
10781121
10791122 return result ;
10801123 }
10811124
10821125 async sleep ( name : string , duration : WorkflowSleepDuration ) : Promise < void > {
1126+ if ( this . #engine. rollbackPhase === "replay" ) {
1127+ return ;
1128+ }
1129+
10831130 if ( typeof duration == "string" ) {
10841131 duration = ms ( duration ) ;
10851132 }
@@ -1201,6 +1248,10 @@ export class Context extends RpcTarget {
12011248 }
12021249
12031250 async sleepUntil ( name : string , timestamp : Date | number ) : Promise < void > {
1251+ if ( this . #engine. rollbackPhase === "replay" ) {
1252+ return ;
1253+ }
1254+
12041255 if ( timestamp instanceof Date ) {
12051256 timestamp = timestamp . valueOf ( ) ;
12061257 }
@@ -1223,6 +1274,12 @@ export class Context extends RpcTarget {
12231274 timeout ?: string | number ;
12241275 }
12251276 ) : Promise < WorkflowStepEvent < T > > {
1277+ if ( this . #engine. rollbackPhase === "rollback" ) {
1278+ throw new WorkflowFatalError (
1279+ "Cannot execute steps during rollback phase"
1280+ ) ;
1281+ }
1282+
12261283 if ( ! options . timeout ) {
12271284 options . timeout = "24 hours" ;
12281285 }
@@ -1242,6 +1299,10 @@ export class Context extends RpcTarget {
12421299
12431300 const maybeResult = await this . #state. storage . get < Event > ( waitForEventKey ) ;
12441301
1302+ if ( this . #engine. rollbackPhase === "replay" ) {
1303+ return maybeResult as WorkflowStepEvent < T > ;
1304+ }
1305+
12451306 if ( maybeResult ) {
12461307 const shouldWriteLog =
12471308 ( await this . #state. storage . get ( waitForEventKey ) ) == undefined ;
0 commit comments