Skip to content

Commit

Permalink
Use strict synchronization for revision getter to minimize flaky resu…
Browse files Browse the repository at this point in the history
…lt caused by time racing.

Signed-off-by: Joshua Zhang <joshuazh@microsoft.com>
  • Loading branch information
joshuazh-x committed Mar 7, 2024
1 parent e68afe7 commit 5a02f05
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 17 deletions.
6 changes: 5 additions & 1 deletion client/pkg/testutil/recorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package testutil
import (
"errors"
"fmt"
"math"
"sync"
"time"
)
Expand Down Expand Up @@ -115,7 +116,10 @@ func (r *recorderStream) Chan() <-chan Action {

func (r *recorderStream) Wait(n int) ([]Action, error) {
acts := make([]Action, n)
timeoutC := time.After(r.waitTimeout)
var timeoutC <-chan time.Time
if r.waitTimeout < math.MaxInt64 {
timeoutC = time.After(r.waitTimeout)
}
for i := 0; i < n; i++ {
select {
case acts[i] = <-r.ch:
Expand Down
39 changes: 23 additions & 16 deletions server/etcdserver/api/v3compactor/periodic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package v3compactor

import (
"errors"
"math"
"reflect"
"testing"
"time"
Expand All @@ -33,7 +34,7 @@ func TestPeriodicHourly(t *testing.T) {

fc := clockwork.NewFakeClock()
// TODO: Do not depand or real time (Recorder.Wait) in unit tests.
rg := &fakeRevGetter{testutil.NewRecorderStreamWithWaitTimout(10 * time.Millisecond), 0}
rg := &fakeRevGetter{testutil.NewRecorderStreamWithWaitTimout(math.MaxInt64), 0}
compactable := &fakeCompactable{testutil.NewRecorderStreamWithWaitTimout(10 * time.Millisecond)}
tb := newPeriodic(zaptest.NewLogger(t), fc, retentionDuration, rg, compactable)

Expand All @@ -43,8 +44,8 @@ func TestPeriodicHourly(t *testing.T) {
initialIntervals, intervalsPerPeriod := tb.getRetentions(), 10

// compaction doesn't happen til 2 hours elapse
for i := 0; i < initialIntervals; i++ {
rg.Wait(1)
for i := 0; i < initialIntervals-1; i++ {
waitOneAction(t, rg)
fc.Advance(tb.getRetryInterval())
}

Expand All @@ -63,7 +64,7 @@ func TestPeriodicHourly(t *testing.T) {
for i := 0; i < 3; i++ {
// advance one hour, one revision for each interval
for j := 0; j < intervalsPerPeriod; j++ {
rg.Wait(1)
waitOneAction(t, rg)
fc.Advance(tb.getRetryInterval())
}

Expand All @@ -84,7 +85,7 @@ func TestPeriodicMinutes(t *testing.T) {
retentionDuration := time.Duration(retentionMinutes) * time.Minute

fc := clockwork.NewFakeClock()
rg := &fakeRevGetter{testutil.NewRecorderStreamWithWaitTimout(10 * time.Millisecond), 0}
rg := &fakeRevGetter{testutil.NewRecorderStreamWithWaitTimout(math.MaxInt64), 0}
compactable := &fakeCompactable{testutil.NewRecorderStreamWithWaitTimout(10 * time.Millisecond)}
tb := newPeriodic(zaptest.NewLogger(t), fc, retentionDuration, rg, compactable)

Expand All @@ -94,8 +95,8 @@ func TestPeriodicMinutes(t *testing.T) {
initialIntervals, intervalsPerPeriod := tb.getRetentions(), 10

// compaction doesn't happen til 5 minutes elapse
for i := 0; i < initialIntervals; i++ {
rg.Wait(1)
for i := 0; i < initialIntervals-1; i++ {
waitOneAction(t, rg)
fc.Advance(tb.getRetryInterval())
}

Expand All @@ -113,7 +114,7 @@ func TestPeriodicMinutes(t *testing.T) {
for i := 0; i < 5; i++ {
// advance 5-minute, one revision for each interval
for j := 0; j < intervalsPerPeriod; j++ {
rg.Wait(1)
waitOneAction(t, rg)
fc.Advance(tb.getRetryInterval())
}

Expand All @@ -132,7 +133,7 @@ func TestPeriodicMinutes(t *testing.T) {
func TestPeriodicPause(t *testing.T) {
fc := clockwork.NewFakeClock()
retentionDuration := time.Hour
rg := &fakeRevGetter{testutil.NewRecorderStreamWithWaitTimout(10 * time.Millisecond), 0}
rg := &fakeRevGetter{testutil.NewRecorderStreamWithWaitTimout(math.MaxInt64), 0}
compactable := &fakeCompactable{testutil.NewRecorderStreamWithWaitTimout(10 * time.Millisecond)}
tb := newPeriodic(zaptest.NewLogger(t), fc, retentionDuration, rg, compactable)

Expand All @@ -143,7 +144,7 @@ func TestPeriodicPause(t *testing.T) {

// tb will collect 3 hours of revisions but not compact since paused
for i := 0; i < n*3; i++ {
rg.Wait(1)
waitOneAction(t, rg)
fc.Advance(tb.getRetryInterval())
}
// t.revs = [21 22 23 24 25 26 27 28 29 30]
Expand All @@ -156,7 +157,7 @@ func TestPeriodicPause(t *testing.T) {

// tb resumes to being blocked on the clock
tb.Resume()
rg.Wait(1)
waitOneAction(t, rg)

// unblock clock, will kick off a compaction at T=3h6m by retry
fc.Advance(tb.getRetryInterval())
Expand All @@ -179,7 +180,7 @@ func TestPeriodicSkipRevNotChange(t *testing.T) {
retentionDuration := time.Duration(retentionMinutes) * time.Minute

fc := clockwork.NewFakeClock()
rg := &fakeRevGetter{testutil.NewRecorderStreamWithWaitTimout(10 * time.Millisecond), 0}
rg := &fakeRevGetter{testutil.NewRecorderStreamWithWaitTimout(math.MaxInt64), 0}
compactable := &fakeCompactable{testutil.NewRecorderStreamWithWaitTimout(10 * time.Millisecond)}
tb := newPeriodic(zaptest.NewLogger(t), fc, retentionDuration, rg, compactable)

Expand All @@ -189,10 +190,10 @@ func TestPeriodicSkipRevNotChange(t *testing.T) {
initialIntervals, intervalsPerPeriod := tb.getRetentions(), 10

// first compaction happens til 5 minutes elapsed
for i := 0; i < initialIntervals; i++ {
for i := 0; i < initialIntervals-1; i++ {
// every time set the same revision with 100
rg.SetRev(int64(100))
rg.Wait(1)
waitOneAction(t, rg)
fc.Advance(tb.getRetryInterval())
}

Expand All @@ -212,7 +213,7 @@ func TestPeriodicSkipRevNotChange(t *testing.T) {
for i := 0; i < 5; i++ {
for j := 0; j < intervalsPerPeriod; j++ {
rg.SetRev(int64(100))
rg.Wait(1)
waitOneAction(t, rg)
fc.Advance(tb.getRetryInterval())
}

Expand All @@ -224,7 +225,7 @@ func TestPeriodicSkipRevNotChange(t *testing.T) {

// when revision changed, compaction is normally
for i := 0; i < initialIntervals; i++ {
rg.Wait(1)
waitOneAction(t, rg)
fc.Advance(tb.getRetryInterval())
}

Expand All @@ -238,3 +239,9 @@ func TestPeriodicSkipRevNotChange(t *testing.T) {
t.Errorf("compact request = %v, want %v", a[0].Params[0], &pb.CompactionRequest{Revision: expectedRevision})
}
}

func waitOneAction(t *testing.T, r testutil.Recorder) {
if actions, _ := r.Wait(1); len(actions) != 1 {
t.Errorf("expect 1 action, got %v instead", len(actions))
}
}

0 comments on commit 5a02f05

Please sign in to comment.