Skip to content

Commit 914947d

Browse files
joamakidylandreimerink
authored andcommitted
script: Exponential backoff for retrying
It's easy to get pretty spammy logs when failing retried commands. We don't expect the need to retry many times in successful tests, so add an exponential backoff to make the output more reasonable on failures. Signed-off-by: Jussi Maki <[email protected]>
1 parent 0e864ff commit 914947d

File tree

2 files changed

+33
-12
lines changed

2 files changed

+33
-12
lines changed

script/engine.go

+28-8
Original file line numberDiff line numberDiff line change
@@ -77,21 +77,28 @@ type Engine struct {
7777
// section when starting a new section.
7878
Quiet bool
7979

80-
// RetryInterval for retrying commands marked with '*'. If zero, then
81-
// the default retry interval is used.
80+
// RetryInterval is the minimal interval for retrying commands marked with '*'.
81+
// If zero, then the default retry interval is used.
8282
RetryInterval time.Duration
83+
84+
// MaxRetryInterval is the maximum time to wait before retrying.
85+
MaxRetryInterval time.Duration
8386
}
8487

8588
// NewEngine returns an Engine configured with a basic set of commands and conditions.
8689
func NewEngine() *Engine {
8790
return &Engine{
88-
Cmds: DefaultCmds(),
89-
Conds: DefaultConds(),
90-
RetryInterval: defaultRetryInterval,
91+
Cmds: DefaultCmds(),
92+
Conds: DefaultConds(),
93+
RetryInterval: defaultRetryInterval,
94+
MaxRetryInterval: defaultMaxRetryInterval,
9195
}
9296
}
9397

94-
const defaultRetryInterval = 100 * time.Millisecond
98+
const (
99+
defaultRetryInterval = 100 * time.Millisecond
100+
defaultMaxRetryInterval = 500 * time.Millisecond
101+
)
95102

96103
// A Cmd is a command that is available to a script.
97104
type Cmd interface {
@@ -331,14 +338,16 @@ func (e *Engine) Execute(s *State, file string, script *bufio.Reader, log io.Wri
331338
if cmd.want == successRetryOnFailure || cmd.want == failureRetryOnSuccess {
332339
// Command wants retries. Retry the whole section
333340
numRetries := 0
341+
backoff := exponentialBackoff{max: e.MaxRetryInterval, interval: e.RetryInterval}
334342
for err != nil {
335343
s.FlushLog()
344+
retryDuration := backoff.get()
345+
s.Logf("(command %q failed, retrying in %s...)\n", line, retryDuration)
336346
select {
337347
case <-s.Context().Done():
338348
return lineErr(s.Context().Err())
339-
case <-time.After(retryInterval):
349+
case <-time.After(retryDuration):
340350
}
341-
s.Logf("(command %q failed, retrying...)\n", line)
342351
numRetries++
343352
for _, cmd := range sectionCmds {
344353
impl := e.Cmds[cmd.name]
@@ -997,3 +1006,14 @@ func (e *Engine) ListConds(w io.Writer, s *State, tags ...string) error {
9971006

9981007
return nil
9991008
}
1009+
1010+
type exponentialBackoff struct {
1011+
max time.Duration
1012+
interval time.Duration
1013+
}
1014+
1015+
func (eb *exponentialBackoff) get() time.Duration {
1016+
d := eb.interval
1017+
eb.interval = min(eb.interval*2, eb.max)
1018+
return d
1019+
}

script/scripttest/scripttest_test.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ func TestAll(t *testing.T) {
2222
env := os.Environ()
2323
scripttest.Test(t, ctx, func(t testing.TB, scriptArgs []string) *script.Engine {
2424
engine := &script.Engine{
25-
Conds: scripttest.DefaultConds(),
26-
Cmds: scripttest.DefaultCmds(),
27-
Quiet: !testing.Verbose(),
28-
RetryInterval: 10 * time.Millisecond,
25+
Conds: scripttest.DefaultConds(),
26+
Cmds: scripttest.DefaultCmds(),
27+
Quiet: !testing.Verbose(),
28+
RetryInterval: 10 * time.Millisecond,
29+
MaxRetryInterval: 100 * time.Millisecond,
2930
}
3031
engine.Cmds["args"] = script.Command(
3132
script.CmdUsage{},

0 commit comments

Comments
 (0)