Skip to content

Commit

Permalink
Move mockclock to separate file
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcoPolo committed Mar 21, 2023
1 parent 9b616d2 commit 22be1b5
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 135 deletions.
147 changes: 16 additions & 131 deletions p2p/host/autorelay/autorelay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ package autorelay_test
import (
"context"
"fmt"
"sort"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
Expand All @@ -14,6 +12,7 @@ import (
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/test"
"github.com/libp2p/go-libp2p/p2p/host/autorelay"
circuitv2_proto "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/proto"

Expand Down Expand Up @@ -184,7 +183,7 @@ func TestWaitForCandidates(t *testing.T) {

func TestBackoff(t *testing.T) {
const backoff = 20 * time.Second
cl := newMockClock()
cl := test.NewMockClock()
r, err := libp2p.New(
libp2p.DisableRelay(),
libp2p.ForceReachabilityPublic(),
Expand Down Expand Up @@ -230,10 +229,10 @@ func TestBackoff(t *testing.T) {
}, 10*time.Second, 20*time.Millisecond, "reservations load should be 1 was %d", reservations.Load())
// make sure we don't add any relays yet
for i := 0; i < 2; i++ {
cl.advanceBy(backoff / 3)
cl.AdvanceBy(backoff / 3)
require.Equal(t, 1, int(reservations.Load()))
}
cl.advanceBy(backoff)
cl.AdvanceBy(backoff)
require.Eventually(t, func() bool {
return reservations.Load() == 2
}, 10*time.Second, 100*time.Millisecond, "reservations load should be 2 was %d", reservations.Load())
Expand Down Expand Up @@ -296,122 +295,8 @@ func TestConnectOnDisconnect(t *testing.T) {
require.NotEqualf(t, oldRelay, relaysInUse[0], "old relay should not be used again")
}

type mockClock struct {
mu sync.Mutex
now time.Time
timers []*mockInstantTimer
}

var _ autorelay.ClockWithInstantTimer = &mockClock{}

type mockInstantTimer struct {
c *mockClock
mu sync.Mutex
when time.Time
active bool
ch chan time.Time
}

// Ch implements autorelay.InstantTimer
func (t *mockInstantTimer) Ch() <-chan time.Time {
return t.ch
}

// Reset implements autorelay.InstantTimer
func (t *mockInstantTimer) Reset(d time.Time) bool {
t.mu.Lock()
defer t.mu.Unlock()
wasActive := t.active
t.active = true
t.when = d

// Schedule any timers that need to run. This will run this timer if t.when is before c.now
go t.c.advanceBy(0)

return wasActive
}

// Stop implements autorelay.InstantTimer
func (t *mockInstantTimer) Stop() bool {
t.mu.Lock()
defer t.mu.Unlock()
wasActive := t.active
t.active = false
return wasActive
}

var _ autorelay.InstantTimer = &mockInstantTimer{}

func newMockClock() *mockClock {
return &mockClock{now: time.Unix(0, 0)}
}

// InstantTimer implements autorelay.ClockWithInstantTimer
func (c *mockClock) InstantTimer(when time.Time) autorelay.InstantTimer {
c.mu.Lock()
defer c.mu.Unlock()
t := &mockInstantTimer{
c: c,
when: when,
ch: make(chan time.Time, 1),
active: true,
}
c.timers = append(c.timers, t)
return t
}

// Since implements autorelay.ClockWithInstantTimer
func (c *mockClock) Since(t time.Time) time.Duration {
c.mu.Lock()
defer c.mu.Unlock()
return c.now.Sub(t)
}

func (c *mockClock) Now() time.Time {
c.mu.Lock()
defer c.mu.Unlock()
return c.now
}

func (c *mockClock) advanceBy(dur time.Duration) {
c.mu.Lock()
defer c.mu.Unlock()
endTime := c.now.Add(dur)

// sort timers by when
if len(c.timers) > 1 {
sort.Slice(c.timers, func(i, j int) bool {
return c.timers[i].when.Before(c.timers[j].when)
})
}

for _, t := range c.timers {
t.mu.Lock()
if !t.active {
t.mu.Unlock()
continue
}
if !t.when.After(c.now) {
t.active = false
t.mu.Unlock()
// This may block if the channel is full, but that's intended. This way our mock clock never gets too far ahead of consumer.
// This also prevents us from dropping times because we're advancing too fast.
t.ch <- c.now
} else if !t.when.After(endTime) {
c.now = t.when
t.active = false
t.mu.Unlock()
// This may block if the channel is full, but that's intended. See comment above
t.ch <- c.now
} else {
t.mu.Unlock()
}
}
c.now = endTime
}

func TestMaxAge(t *testing.T) {
cl := newMockClock()
cl := test.NewMockClock()

const num = 4
peerChan1 := make(chan peer.AddrInfo, num)
Expand Down Expand Up @@ -456,16 +341,16 @@ func TestMaxAge(t *testing.T) {
relays := usedRelays(h)
require.Len(t, relays, 1)

cl.advanceBy(time.Minute)
cl.AdvanceBy(time.Minute)
require.Eventually(t, func() bool {
return len(peerChans) == 0
}, 10*time.Second, 100*time.Millisecond)

cl.advanceBy(10 * time.Minute)
cl.AdvanceBy(10 * time.Minute)
for _, r := range relays2 {
peerChan2 <- peer.AddrInfo{ID: r.ID(), Addrs: r.Addrs()}
}
cl.advanceBy(11 * time.Minute)
cl.AdvanceBy(11 * time.Minute)

require.Eventually(t, func() bool {
relays = usedRelays(h)
Expand Down Expand Up @@ -510,7 +395,7 @@ func TestMaxAge(t *testing.T) {
}

func TestReconnectToStaticRelays(t *testing.T) {
cl := newMockClock()
cl := test.NewMockClock()
var staticRelays []peer.AddrInfo
const numStaticRelays = 1
relays := make([]host.Host, 0, numStaticRelays)
Expand All @@ -528,7 +413,7 @@ func TestReconnectToStaticRelays(t *testing.T) {
)
defer h.Close()

cl.advanceBy(time.Minute)
cl.AdvanceBy(time.Minute)
require.Eventually(t, func() bool {
return numRelays(h) == 1
}, 10*time.Second, 100*time.Millisecond)
Expand All @@ -544,14 +429,14 @@ func TestReconnectToStaticRelays(t *testing.T) {
return numRelays(h) == 0
}, 10*time.Second, 100*time.Millisecond)

cl.advanceBy(time.Hour)
cl.AdvanceBy(time.Hour)
require.Eventually(t, func() bool {
return numRelays(h) == 1
}, 10*time.Second, 100*time.Millisecond)
}

func TestMinInterval(t *testing.T) {
cl := newMockClock()
cl := test.NewMockClock()
h := newPrivateNode(t,
func(context.Context, int) <-chan peer.AddrInfo {
peerChan := make(chan peer.AddrInfo, 1)
Expand All @@ -569,16 +454,16 @@ func TestMinInterval(t *testing.T) {
)
defer h.Close()

cl.advanceBy(400 * time.Millisecond)
cl.AdvanceBy(400 * time.Millisecond)
// The second call to peerSource should happen after 1 second
require.Never(t, func() bool { return numRelays(h) > 0 }, 500*time.Millisecond, 100*time.Millisecond)
cl.advanceBy(600 * time.Millisecond)
cl.AdvanceBy(600 * time.Millisecond)
require.Eventually(t, func() bool { return numRelays(h) > 0 }, 3*time.Second, 100*time.Millisecond)
}

func TestNoBusyLoop0MinInterval(t *testing.T) {
var calledTimes uint64
cl := newMockClock()
cl := test.NewMockClock()
h := newPrivateNode(t,
func(context.Context, int) <-chan peer.AddrInfo {
atomic.AddUint64(&calledTimes, 1)
Expand All @@ -599,7 +484,7 @@ func TestNoBusyLoop0MinInterval(t *testing.T) {
defer h.Close()

require.Never(t, func() bool {
cl.advanceBy(time.Second)
cl.AdvanceBy(time.Second)
val := atomic.LoadUint64(&calledTimes)
return val >= 2
}, 500*time.Millisecond, 100*time.Millisecond)
Expand Down
4 changes: 0 additions & 4 deletions p2p/host/autorelay/relay_finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,6 @@ func (rf *relayFinder) background(ctx context.Context) {
if rsvpRefreshInterval > leastFrequentInterval || leastFrequentInterval == 0 {
leastFrequentInterval = rsvpRefreshInterval
}
if leastFrequentInterval == 0 {
// fallback to avoid busy looping in case the config is set to all 0s
leastFrequentInterval = time.Hour
}

scheduledWork := &scheduledWorkTimes{
leastFrequentInterval: leastFrequentInterval,
Expand Down

0 comments on commit 22be1b5

Please sign in to comment.