@@ -40,6 +40,7 @@ const (
4040 StateCrashed = "CRASHED"
4141 StatePrune = "PRUNE"
4242 StateToRetry = "TO_RETRY"
43+ StateRetryNow = "RETRY_NOW"
4344 StateAfterrunError = "AFTERRUN_ERROR"
4445
4546 // steps that carry a foreach list of arguments
@@ -50,12 +51,14 @@ const (
5051 stepRefThis = utask .This
5152
5253 defaultMaxRetries = 10000
54+
55+ maxExecutionDelay = time .Duration (20 ) * time .Second
5356)
5457
5558var (
56- builtinStates = []string {StateTODO , StateRunning , StateDone , StateClientError , StateServerError , StateFatalError , StateCrashed , StatePrune , StateToRetry , StateAfterrunError , StateAny , StateExpanded }
57- stepConditionValidStates = []string {StateDone , StatePrune , StateToRetry , StateFatalError , StateClientError }
58- runnableStates = []string {StateTODO , StateServerError , StateClientError , StateFatalError , StateCrashed , StateToRetry , StateAfterrunError , StateExpanded } // everything but RUNNING, DONE, PRUNE
59+ builtinStates = []string {StateTODO , StateRunning , StateDone , StateClientError , StateServerError , StateFatalError , StateCrashed , StatePrune , StateToRetry , StateRetryNow , StateAfterrunError , StateAny , StateExpanded }
60+ stepConditionValidStates = []string {StateDone , StatePrune , StateToRetry , StateRetryNow , StateFatalError , StateClientError }
61+ runnableStates = []string {StateTODO , StateServerError , StateClientError , StateFatalError , StateCrashed , StateToRetry , StateRetryNow , StateAfterrunError , StateExpanded } // everything but RUNNING, DONE, PRUNE
5962 retriableStates = []string {StateServerError , StateToRetry , StateAfterrunError }
6063)
6164
@@ -90,10 +93,11 @@ type Step struct {
9093 State string `json:"state,omitempty"`
9194 // hints about ETA latency, async, for retrier to define strategy
9295 // how often VS how many times
93- RetryPattern string `json:"retry_pattern,omitempty"` // seconds, minutes, hours
94- TryCount int `json:"try_count,omitempty"`
95- MaxRetries int `json:"max_retries,omitempty"`
96- LastRun time.Time `json:"last_run,omitempty"`
96+ RetryPattern string `json:"retry_pattern,omitempty"` // seconds, minutes, hours
97+ TryCount int `json:"try_count,omitempty"`
98+ MaxRetries int `json:"max_retries,omitempty"`
99+ LastRun time.Time `json:"last_run,omitempty"`
100+ ExecutionDelay time.Duration `json:"execution_delay,omitempty"`
97101
98102 // flow control
99103 Dependencies []string `json:"dependencies,omitempty"`
@@ -321,7 +325,10 @@ func Run(st *Step, baseConfig map[string]json.RawMessage, stepValues *values.Val
321325 if st .MaxRetries == 0 {
322326 st .MaxRetries = defaultMaxRetries
323327 }
324- if st .TryCount > st .MaxRetries {
328+
329+ // we can set "max_retries" to a negative number to have full control
330+ // over the repetition of a step with a check condition using RETRY_NOW
331+ if st .MaxRetries > 0 && st .TryCount > st .MaxRetries {
325332 st .State = StateFatalError
326333 st .Error = fmt .Sprintf ("Step reached max retries %d: %s" , st .MaxRetries , st .Error )
327334 go noopStep (st , stepChan )
@@ -372,6 +379,8 @@ func Run(st *Step, baseConfig map[string]json.RawMessage, stepValues *values.Val
372379 go func () {
373380 defer wg .Done ()
374381
382+ time .Sleep (st .ExecutionDelay )
383+
375384 // Wait the prehook execution is done
376385 preHookWg .Wait ()
377386
@@ -533,6 +542,13 @@ func (st *Step) ValidAndNormalize(name string, baseConfigs map[string]json.RawMe
533542 }
534543 }
535544
545+ // valid execution delay
546+ if st .ExecutionDelay < 0 || st .ExecutionDelay > maxExecutionDelay {
547+ return errors .NewNotValid (nil ,
548+ fmt .Sprintf ("execution_delay: expected %s to be a duration between 0s and %s" ,
549+ st .ExecutionDelay , maxExecutionDelay ))
550+ }
551+
536552 // valid retry pattern, accept empty
537553 switch st .RetryPattern {
538554 case "" , RetrySeconds , RetryMinutes , RetryHours :
0 commit comments