@@ -15,25 +15,29 @@ import {
1515 isAbortError ,
1616 PreservedNonRetryableError ,
1717 shouldPreserveNonRetryableError ,
18+ stepNotFoundError ,
1819 WorkflowFatalError ,
1920} from "./lib/errors" ;
2021import {
2122 ENGINE_TIMEOUT ,
2223 GracePeriodSemaphore ,
2324 startGracePeriod ,
2425} from "./lib/gracePeriodSemaphore" ;
26+ import {
27+ readAndClearRestartFromStep ,
28+ resolveGroupKeysToWipe ,
29+ storeRestartFromStep ,
30+ wipeRestartState ,
31+ } from "./lib/restart" ;
2532import {
2633 createReplayReadableStream ,
2734 getInvalidStoredStreamOutputError ,
2835 getStoredStreamOutputPreview ,
2936 StreamOutputState ,
3037} from "./lib/streams" ;
3138import { TimePriorityQueue } from "./lib/timePriorityQueue" ;
32- import {
33- isModifierKey ,
34- MODIFIER_KEYS ,
35- WorkflowInstanceModifier ,
36- } from "./modifier" ;
39+ import { MODIFIER_KEYS , WorkflowInstanceModifier } from "./modifier" ;
40+ import type { RestartFromStep } from "./binding" ;
3741import type { Event } from "./context" ;
3842import type { InstanceMetadata , RawInstanceLog } from "./instance" ;
3943import type { StreamOutputMeta } from "./lib/streams" ;
@@ -753,7 +757,8 @@ export class Engine extends DurableObject<Env> {
753757 }
754758
755759 async changeInstanceStatus (
756- newStatus : "resume" | "pause" | "terminate" | "restart"
760+ newStatus : "resume" | "pause" | "terminate" | "restart" ,
761+ from ?: RestartFromStep
757762 ) {
758763 const metadata =
759764 await this . ctx . storage . get < InstanceMetadata > ( INSTANCE_METADATA ) ;
@@ -802,6 +807,12 @@ export class Engine extends DurableObject<Env> {
802807 break ;
803808 }
804809 case "restart" :
810+ if ( from ) {
811+ if ( ! resolveGroupKeysToWipe ( this . ctx . storage . sql , from ) ) {
812+ throw stepNotFoundError ( from . name ) ;
813+ }
814+ await storeRestartFromStep ( this . ctx . storage , from ) ;
815+ }
805816 await this . userTriggeredRestart ( ) ;
806817 break ;
807818 }
@@ -889,61 +900,27 @@ export class Engine extends DurableObject<Env> {
889900 await this . abort ( ABORT_REASONS . USER_RESTART ) ;
890901 }
891902
892- private getMockedEventMapKeys ( allKeys : Map < string , unknown > ) : Set < string > {
893- const mockEventTypes = new Set < string > ( ) ;
894- for ( const key of allKeys . keys ( ) ) {
895- if ( key . startsWith ( MODIFIER_KEYS . MOCK_EVENT ) ) {
896- mockEventTypes . add ( key . slice ( MODIFIER_KEYS . MOCK_EVENT . length ) ) ;
897- }
898- }
899-
900- if ( mockEventTypes . size === 0 ) {
901- return new Set ( ) ;
902- }
903+ async attemptRestart ( ) {
904+ const restartFromStep = await readAndClearRestartFromStep ( this . ctx . storage ) ;
903905
904- const preserved = new Set < string > ( ) ;
905- for ( const key of allKeys . keys ( ) ) {
906- if ( key . startsWith ( ` ${ EVENT_MAP_PREFIX } \n` ) ) {
907- // EVENT_MAP keys are formatted as "EVENT_MAP\n{type}\n{idx}"
908- const eventType = key . split ( "\n" ) [ 1 ] ;
909- if ( eventType !== undefined && mockEventTypes . has ( eventType ) ) {
910- preserved . add ( key ) ;
911- }
906+ let groupKeysToWipe : Set < string > | null = null ;
907+ if ( restartFromStep ) {
908+ groupKeysToWipe = resolveGroupKeysToWipe (
909+ this . ctx . storage . sql ,
910+ restartFromStep
911+ ) ;
912+ if ( ! groupKeysToWipe ) {
913+ throw stepNotFoundError ( restartFromStep . name ) ;
912914 }
913915 }
914916
915- return preserved ;
916- }
917-
918- async attemptRestart ( ) {
919- this . ctx . storage . sql . exec ( "DELETE FROM states" ) ;
920- this . ctx . storage . sql . exec ( "DELETE FROM priority_queue" ) ;
921- // Only delete non-mock streaming chunks. Mock stream outputs are stored
922- // at attempt=0 (see modifier.ts mockStepResult) and their sentinels
923- // survive restart via isModifierKey(), so the underlying SQL rows must
924- // be preserved too.
925- this . ctx . storage . sql . exec (
926- "DELETE FROM streaming_step_chunks WHERE attempt != 0"
917+ await wipeRestartState (
918+ this . ctx . storage ,
919+ ENGINE_STATUS_KEY ,
920+ PAUSE_DATETIME ,
921+ groupKeysToWipe
927922 ) ;
928923
929- const allKeys = await this . ctx . storage . list ( ) ;
930- const preservedEventMapKeys = this . getMockedEventMapKeys ( allKeys ) ;
931-
932- // Remove all KV keys except:
933- // - INSTANCE_METADATA (needed to re-run the workflow)
934- // - Modifier/mock keys (so mocks survive restart)
935- // - EVENT_MAP entries for mocked event types
936- for ( const key of allKeys . keys ( ) ) {
937- if (
938- key === INSTANCE_METADATA ||
939- isModifierKey ( key ) ||
940- preservedEventMapKeys . has ( key )
941- ) {
942- continue ;
943- }
944- await this . ctx . storage . delete ( key ) ;
945- }
946-
947924 const metadata =
948925 await this . ctx . storage . get < InstanceMetadata > ( INSTANCE_METADATA ) ;
949926
@@ -956,14 +933,16 @@ export class Engine extends DurableObject<Env> {
956933
957934 const { accountId, workflow, version, instance, event } = metadata ;
958935
959- this . writeLog ( InstanceEvent . WORKFLOW_QUEUED , null , null , {
960- params : event . payload ,
961- versionId : version . id ,
962- trigger : {
963- source : InstanceTrigger . API ,
964- } ,
965- } ) ;
966- this . writeLog ( InstanceEvent . WORKFLOW_START , null , null , { } ) ;
936+ if ( ! groupKeysToWipe ) {
937+ this . writeLog ( InstanceEvent . WORKFLOW_QUEUED , null , null , {
938+ params : event . payload ,
939+ versionId : version . id ,
940+ trigger : {
941+ source : InstanceTrigger . API ,
942+ } ,
943+ } ) ;
944+ this . writeLog ( InstanceEvent . WORKFLOW_START , null , null , { } ) ;
945+ }
967946
968947 void this . init ( accountId , workflow , version , instance , event ) ;
969948 }
0 commit comments