@@ -164,6 +164,10 @@ func (engine *Engine) Start(ctx context.Context, workflowID string, input json.R
164164 return fmt .Errorf ("get workflow definition: %w" , err )
165165 }
166166
167+ if err := engine .validateDefinition (def ); err != nil {
168+ return fmt .Errorf ("invalid workflow definition: %w" , err )
169+ }
170+
167171 instance , err := engine .store .CreateInstance (ctx , workflowID , input )
168172 if err != nil {
169173 return fmt .Errorf ("create instance: %w" , err )
@@ -323,6 +327,17 @@ func (engine *Engine) ExecuteNext(ctx context.Context, workerID string) (empty b
323327 }
324328 }
325329
330+ // Do not execute skipped or paused steps
331+ if step .Status == StepStatusSkipped ||
332+ step .Status == StepStatusPaused ||
333+ step .Status == StepStatusRolledBack {
334+ if err := engine .store .RemoveFromQueue (ctx , step .ID ); err != nil {
335+ return fmt .Errorf ("remove step from queue: %w" , err )
336+ }
337+
338+ return nil
339+ }
340+
326341 // Check if this is a compensation
327342 if step .Status == StepStatusCompensation {
328343 // For distributed setup: if local engine doesn't have the compensation handler,
@@ -369,8 +384,6 @@ func (engine *Engine) ExecuteNext(ctx context.Context, workerID string) (empty b
369384 }
370385
371386 return engine .executeCompensationStep (ctx , instance , step )
372- } else if step .Status == StepStatusRolledBack {
373- return nil
374387 }
375388
376389 // Distributed handlers: if this is a task step and no local handler is registered,
@@ -919,7 +932,11 @@ func (engine *Engine) executeCompensationStep(ctx context.Context, instance *Wor
919932 }
920933
921934 // Re-enqueue for retry
922- if err := engine .store .EnqueueStep (ctx , step .InstanceID , & step .ID , PriorityHigh , stepDef .Delay ); err != nil {
935+ retryDelay := CalculateRetryDelay (onFailureStep .RetryStrategy , onFailureStep .RetryDelay , newRetryCount )
936+ if retryDelay == 0 {
937+ retryDelay = onFailureStep .Delay
938+ }
939+ if err := engine .store .EnqueueStep (ctx , step .InstanceID , & step .ID , PriorityHigh , retryDelay ); err != nil {
923940 return fmt .Errorf ("enqueue compensation retry: %w" , err )
924941 }
925942
@@ -1659,11 +1676,6 @@ func (engine *Engine) notifyJoinStepsForStep(
16591676 return err
16601677 }
16611678
1662- steps , err := engine .store .GetStepsByInstance (ctx , instanceID )
1663- if err != nil {
1664- return err
1665- }
1666-
16671679 // Get Join step definition to check strategy
16681680 def , err := engine .store .GetWorkflowDefinition (ctx , instance .WorkflowID )
16691681 if err != nil {
@@ -1681,9 +1693,15 @@ func (engine *Engine) notifyJoinStepsForStep(
16811693 return fmt .Errorf ("update join state for %s: %w" , joinStepName , err )
16821694 }
16831695
1696+ var readySteps []WorkflowStep
16841697 // Additional check: don't consider join ready if there are still pending/running steps
16851698 if isReady {
1686- hasPendingSteps := engine .hasPendingStepsInParallelBranches (ctx , instanceID , stepDef , steps )
1699+ readySteps , err = engine .store .GetStepsByInstance (ctx , instanceID )
1700+ if err != nil {
1701+ return err
1702+ }
1703+
1704+ hasPendingSteps := engine .hasPendingStepsInParallelBranches (ctx , instanceID , stepDef , readySteps )
16871705 if hasPendingSteps {
16881706 isReady = false
16891707 _ , _ = engine .store .UpdateJoinState (ctx , instanceID , joinStepName , completedStepName , success )
@@ -1699,8 +1717,8 @@ func (engine *Engine) notifyJoinStepsForStep(
16991717
17001718 if isReady {
17011719 joinStepExists := false
1702- for _ , s := range steps {
1703- if s .StepName == joinStepName {
1720+ for _ , readyStep := range readySteps {
1721+ if readyStep .StepName == joinStepName {
17041722 joinStepExists = true
17051723
17061724 break
@@ -1709,9 +1727,9 @@ func (engine *Engine) notifyJoinStepsForStep(
17091727
17101728 if ! joinStepExists {
17111729 var joinInput json.RawMessage
1712- for _ , s := range steps {
1713- if s .StepName == completedStepName {
1714- joinInput = s .Input
1730+ for _ , readyStep := range readySteps {
1731+ if readyStep .StepName == completedStepName {
1732+ joinInput = readyStep .Input
17151733
17161734 break
17171735 }
@@ -1767,11 +1785,6 @@ func (engine *Engine) notifyJoinSteps(
17671785 return err
17681786 }
17691787
1770- steps , err := engine .store .GetStepsByInstance (ctx , instanceID )
1771- if err != nil {
1772- return err
1773- }
1774-
17751788 for stepName , stepDef := range def .Definition .Steps {
17761789 if stepDef .Type != StepTypeJoin {
17771790 continue
@@ -1815,8 +1828,14 @@ func (engine *Engine) notifyJoinSteps(
18151828
18161829 // Additional check: don't consider join ready if there are still pending/running steps
18171830 // in parallel branches that could affect the join result
1831+ var readySteps []WorkflowStep
18181832 if isReady {
1819- hasPendingSteps := engine .hasPendingStepsInParallelBranches (ctx , instanceID , stepDef , steps )
1833+ readySteps , err = engine .store .GetStepsByInstance (ctx , instanceID )
1834+ if err != nil {
1835+ return err
1836+ }
1837+
1838+ hasPendingSteps := engine .hasPendingStepsInParallelBranches (ctx , instanceID , stepDef , readySteps )
18201839 if hasPendingSteps {
18211840 isReady = false
18221841 // Update the join state to reflect that it's not ready
@@ -1833,8 +1852,8 @@ func (engine *Engine) notifyJoinSteps(
18331852
18341853 if isReady {
18351854 joinStepExists := false
1836- for _ , s := range steps {
1837- if s .StepName == stepName {
1855+ for _ , readyStep := range readySteps {
1856+ if readyStep .StepName == stepName {
18381857 joinStepExists = true
18391858
18401859 break
@@ -1843,9 +1862,9 @@ func (engine *Engine) notifyJoinSteps(
18431862
18441863 if ! joinStepExists {
18451864 var joinInput json.RawMessage
1846- for _ , s := range steps {
1847- if s .StepName == completedStepName {
1848- joinInput = s .Input
1865+ for _ , readyStep := range readySteps {
1866+ if readyStep .StepName == completedStepName {
1867+ joinInput = readyStep .Input
18491868
18501869 break
18511870 }
@@ -2010,7 +2029,7 @@ func (engine *Engine) enqueueCompletedStepsForRollback(ctx context.Context, inst
20102029 // Mark each step as requiring compensation and enqueue for processing
20112030 for idx , step := range stepsToRollback {
20122031 // Update step status to compensation with retry count = 0
2013- if err := engine .store .UpdateStepCompensationRetry (ctx , step .ID , 1 , StepStatusCompensation ); err != nil {
2032+ if err := engine .store .UpdateStepCompensationRetry (ctx , step .ID , 0 , StepStatusCompensation ); err != nil {
20142033 slog .Warn ("[floxy] failed to mark step for compensation" , "step_id" , step .ID , "error" , err )
20152034 continue
20162035 }
@@ -2215,28 +2234,7 @@ func (engine *Engine) validateDefinition(def *WorkflowDefinition) error {
22152234 return fmt .Errorf ("start step not found: %s" , def .Definition .Start )
22162235 }
22172236
2218- for stepName , stepDef := range def .Definition .Steps {
2219- for _ , nextStep := range stepDef .Next {
2220- if _ , ok := def .Definition .Steps [nextStep ]; ! ok {
2221- return fmt .Errorf ("step %s references unknown step: %s" , stepName , nextStep )
2222- }
2223- }
2224-
2225- if stepDef .OnFailure != "" {
2226- if _ , ok := def .Definition .Steps [stepDef .OnFailure ]; ! ok {
2227- return fmt .Errorf ("step %s references unknown compensation step: %s" ,
2228- stepName , stepDef .OnFailure )
2229- }
2230- }
2231-
2232- for _ , parallelStep := range stepDef .Parallel {
2233- if _ , ok := def .Definition .Steps [parallelStep ]; ! ok {
2234- return fmt .Errorf ("step %s references unknown parallel step: %s" , stepName , parallelStep )
2235- }
2236- }
2237- }
2238-
2239- return nil
2237+ return ValidateWorkflowDefinition (def )
22402238}
22412239
22422240func (engine * Engine ) rollbackToSavePointOrRoot (
@@ -2448,7 +2446,10 @@ func (engine *Engine) rollbackStep(ctx context.Context, step *WorkflowStep, def
24482446 }
24492447
24502448 // Enqueue compensation step for execution
2451- retryDelay := CalculateRetryDelay (stepDef .RetryStrategy , stepDef .RetryDelay , newRetryCount )
2449+ retryDelay := CalculateRetryDelay (onFailureStep .RetryStrategy , onFailureStep .RetryDelay , newRetryCount )
2450+ if retryDelay == 0 {
2451+ retryDelay = onFailureStep .Delay
2452+ }
24522453 if err := engine .store .EnqueueStep (ctx , step .InstanceID , & step .ID , PriorityHigh , retryDelay ); err != nil {
24532454 return fmt .Errorf ("enqueue compensation step: %w" , err )
24542455 }
0 commit comments