Skip to content

Commit 23236ef

Browse files
fix: jitter backoff strategy reset (#9342)
* fix: jitter backoff strategy reset * add PR to changelog fragment
1 parent bcf8023 commit 23236ef

File tree

3 files changed

+93
-8
lines changed

3 files changed

+93
-8
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Kind can be one of:
2+
# - breaking-change: a change to previously-documented behavior
3+
# - deprecation: functionality that is being removed in a later release
4+
# - bug-fix: fixes a problem in a previous version
5+
# - enhancement: extends functionality but does not break or fix existing behavior
6+
# - feature: new functionality
7+
# - known-issue: problems that we are aware of in a given version
8+
# - security: impacts on the security of a product or a user’s deployment.
9+
# - upgrade: important information for someone upgrading from a prior version
10+
# - other: does not fit into any of the other categories
11+
kind: bug-fix
12+
13+
# Change summary; a 80ish characters long description of the change.
14+
summary: Fixed jitter backoff strategy reset
15+
16+
# Long description; in case the summary is not enough to describe the change
17+
# this field accommodate a description without length limits.
18+
# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment.
19+
#description:
20+
21+
# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
22+
component: elastic-agent
23+
24+
# PR URL; optional; the PR number that added the changeset.
25+
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
26+
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
27+
# Please provide it if you are adding a fragment for a different PR.
28+
pr: https://github.com/elastic/elastic-agent/pull/9342
29+
30+
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
31+
# If not present is automatically filled by the tooling with the issue linked to the PR number.
32+
issue: https://github.com/elastic/elastic-agent/issues/8864

internal/pkg/core/backoff/backoff_test.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func TestCloseChannel(t *testing.T) {
3131
c := make(chan struct{})
3232
b := f(c)
3333
close(c)
34-
assert.False(t, b.Wait(), "should return false because the channel shuld get closed faster than the next wait duration")
34+
assert.False(t, b.Wait(), "should return false because the channel should get closed faster than the next wait duration")
3535
})
3636
}
3737
}
@@ -95,3 +95,46 @@ func TestNextWait(t *testing.T) {
9595
})
9696
}
9797
}
98+
99+
func TestReset(t *testing.T) {
100+
init := time.Millisecond
101+
max := 5 * time.Second
102+
103+
tests := map[string]factory{
104+
"ExpBackoff": func(done <-chan struct{}) Backoff {
105+
return NewExpBackoff(done, init, max)
106+
},
107+
"EqualJitterBackoff": func(done <-chan struct{}) Backoff {
108+
// Use a custom randFn to ensure the jitter value is always the same for testing
109+
// purposes. Constructor uses rand.N which is not deterministic.
110+
bo := &EqualJitterBackoff{
111+
done: done,
112+
init: init,
113+
max: max,
114+
// force the random function to return the max value for deterministic behavior
115+
randFn: func(t time.Duration) time.Duration { return t },
116+
}
117+
bo.Reset()
118+
return bo
119+
},
120+
}
121+
122+
for name, f := range tests {
123+
t.Run(name, func(t *testing.T) {
124+
c := make(chan struct{})
125+
b := f(c)
126+
127+
startWait := b.NextWait()
128+
129+
b.Wait()
130+
nextWait := b.NextWait()
131+
t.Logf("startWait: %s nextWait: %s", startWait, nextWait)
132+
assert.Greater(t, nextWait, startWait, "nextWait value did not increase after Wait")
133+
134+
b.Reset()
135+
restartWait := b.NextWait()
136+
t.Logf("startWait: %s restartWait: %s", startWait, restartWait)
137+
assert.Equal(t, startWait, restartWait, "wait value did not reset to initial value after reset")
138+
})
139+
}
140+
}

internal/pkg/core/backoff/equal_jitter.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import (
99
"time"
1010
)
1111

12+
// randFn defines a signature for functions that return a random duration.
13+
// It's used to allow injecting a deterministic random function for testing purposes.
14+
type randFn func(n time.Duration) time.Duration
15+
1216
// EqualJitterBackoff implements an equal jitter strategy, meaning the wait time will consist of two parts,
1317
// the first will be exponential and the other half will be random and will provide the jitter
1418
// necessary to distribute the wait on remote endpoint.
@@ -21,23 +25,29 @@ type EqualJitterBackoff struct {
2125
nextRand time.Duration
2226

2327
last time.Time
28+
29+
// Expose randFn to ensure the jitter is always the same for testing
30+
// purposes. Constructor uses rand.N which is not deterministic.
31+
randFn randFn
2432
}
2533

2634
// NewEqualJitterBackoff returns a new EqualJitter object.
2735
func NewEqualJitterBackoff(done <-chan struct{}, init, max time.Duration) Backoff {
28-
return &EqualJitterBackoff{
29-
duration: init * 2, // Allow to sleep at least the init period on the first wait.
30-
done: done,
31-
init: init,
32-
max: max,
33-
nextRand: rand.N(init),
36+
bo := &EqualJitterBackoff{
37+
done: done,
38+
init: init,
39+
max: max,
40+
randFn: rand.N[time.Duration],
3441
}
42+
bo.Reset()
43+
return bo
3544
}
3645

3746
// Reset resets the duration of the backoff.
3847
func (b *EqualJitterBackoff) Reset() {
3948
// Allow to sleep at least the init period on the first wait.
4049
b.duration = b.init * 2
50+
b.nextRand = b.randFn(b.init)
4151
}
4252

4353
func (b *EqualJitterBackoff) NextWait() time.Duration {
@@ -51,7 +61,7 @@ func (b *EqualJitterBackoff) Wait() bool {
5161
backoff := b.NextWait()
5262

5363
// increase duration for next wait.
54-
b.nextRand = rand.N(b.duration)
64+
b.nextRand = b.randFn(b.duration)
5565
b.duration *= 2
5666
if b.duration > b.max {
5767
b.duration = b.max

0 commit comments

Comments
 (0)