Skip to content

Commit

Permalink
Merge pull request #108 from mailgun/cclark/dev
Browse files Browse the repository at this point in the history
PIP-1837: Fix race condition caused by clock.Freeze
  • Loading branch information
Takumi2008 authored Apr 25, 2022
2 parents 7f44ee5 + 0c59bbe commit b3c1e97
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 47 deletions.
34 changes: 2 additions & 32 deletions clock/clock.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build !holster_test_mode

// Package clock provides the same functions as the system package time. In
// production it forwards all calls to the system time package, but in tests
// the time can be frozen by calling Freeze function and from that point it has
Expand Down Expand Up @@ -82,13 +84,6 @@ func After(d time.Duration) <-chan time.Time {
return provider.After(d)
}

// Timer see time.Timer.
type Timer interface {
C() <-chan time.Time
Stop() bool
Reset(d time.Duration) bool
}

// NewTimer see time.NewTimer.
func NewTimer(d time.Duration) Timer {
return provider.NewTimer(d)
Expand All @@ -99,12 +94,6 @@ func AfterFunc(d time.Duration, f func()) Timer {
return provider.AfterFunc(d, f)
}

// Ticker see time.Ticker.
type Ticker interface {
C() <-chan time.Time
Stop()
}

// NewTicker see time.Ticker.
func NewTicker(d time.Duration) Ticker {
return provider.NewTicker(d)
Expand All @@ -114,22 +103,3 @@ func NewTicker(d time.Duration) Ticker {
func Tick(d time.Duration) <-chan time.Time {
return provider.Tick(d)
}

// NewStoppedTimer returns a stopped timer. Call Reset to get it ticking.
func NewStoppedTimer() Timer {
t := NewTimer(42 * time.Hour)
t.Stop()
return t
}

// Clock is an interface that mimics the one of the SDK time package.
type Clock interface {
Now() time.Time
Sleep(d time.Duration)
After(d time.Duration) <-chan time.Time
NewTimer(d time.Duration) Timer
AfterFunc(d time.Duration, f func()) Timer
NewTicker(d time.Duration) Ticker
Tick(d time.Duration) <-chan time.Time
Wait4Scheduled(n int, timeout time.Duration) bool
}
131 changes: 131 additions & 0 deletions clock/clock_mutex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//go:build holster_test_mode

// Package clock provides the same functions as the system package time. In
// production it forwards all calls to the system time package, but in tests
// the time can be frozen by calling Freeze function and from that point it has
// to be advanced manually with Advance function making all scheduled calls
// deterministic.
//
// The functions provided by the package have the same parameters and return
// values as their system counterparts with a few exceptions. Where either
// *time.Timer or *time.Ticker is returned by a system function, the clock
// package counterpart returns clock.Timer or clock.Ticker interface
// respectively. The interfaces provide API as respective structs except C is
// not a channel, but a function that returns <-chan time.Time.
package clock

import (
"sync"
"time"
)

var (
frozenAt time.Time
realtime = &systemTime{}
provider Clock = realtime
rwMutex = sync.RWMutex{}
)

// Freeze after this function is called all time related functions start
// generate deterministic timers that are triggered by Advance function. It is
// supposed to be used in tests only. Returns an Unfreezer so it can be a
// one-liner in tests: defer clock.Freeze(clock.Now()).Unfreeze()
func Freeze(now time.Time) Unfreezer {
frozenAt = now.UTC()
rwMutex.Lock()
defer rwMutex.Unlock()
provider = &frozenTime{now: now}
return Unfreezer{}
}

type Unfreezer struct{}

func (u Unfreezer) Unfreeze() {
Unfreeze()
}

// Unfreeze reverses effect of Freeze.
func Unfreeze() {
rwMutex.Lock()
defer rwMutex.Unlock()
provider = realtime
}

// Realtime returns a clock provider wrapping the SDK's time package. It is
// supposed to be used in tests when time is frozen to schedule test timeouts.
func Realtime() Clock {
return realtime
}

// Makes the deterministic time move forward by the specified duration, firing
// timers along the way in the natural order. It returns how much time has
// passed since it was frozen. So you can assert on the return value in tests
// to make it explicit where you stand on the deterministic time scale.
func Advance(d time.Duration) time.Duration {
rwMutex.RLock()
ft, ok := provider.(*frozenTime)
rwMutex.RUnlock()
if !ok {
panic("Freeze time first!")
}
ft.advance(d)
return Now().UTC().Sub(frozenAt)
}

// Wait4Scheduled blocks until either there are n or more scheduled events, or
// the timeout elapses. It returns true if the wait condition has been met
// before the timeout expired, false otherwise.
func Wait4Scheduled(count int, timeout time.Duration) bool {
rwMutex.RLock()
defer rwMutex.RUnlock()
return provider.Wait4Scheduled(count, timeout)
}

// Now see time.Now.
func Now() time.Time {
rwMutex.RLock()
defer rwMutex.RUnlock()
return provider.Now()
}

// Sleep see time.Sleep.
func Sleep(d time.Duration) {
rwMutex.RLock()
defer rwMutex.RUnlock()
provider.Sleep(d)
}

// After see time.After.
func After(d time.Duration) <-chan time.Time {
rwMutex.RLock()
defer rwMutex.RUnlock()
return provider.After(d)
}

// NewTimer see time.NewTimer.
func NewTimer(d time.Duration) Timer {
rwMutex.RLock()
defer rwMutex.RUnlock()
return provider.NewTimer(d)
}

// AfterFunc see time.AfterFunc.
func AfterFunc(d time.Duration, f func()) Timer {
rwMutex.RLock()
defer rwMutex.RUnlock()
return provider.AfterFunc(d, f)
}

// NewTicker see time.Ticker.
func NewTicker(d time.Duration) Ticker {
rwMutex.RLock()
defer rwMutex.RUnlock()
return provider.NewTicker(d)
}

// Tick see time.Tick.
func Tick(d time.Duration) <-chan time.Time {
rwMutex.RLock()
defer rwMutex.RUnlock()
return provider.Tick(d)
}
35 changes: 35 additions & 0 deletions clock/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package clock

import "time"

// Timer see time.Timer.
type Timer interface {
C() <-chan time.Time
Stop() bool
Reset(d time.Duration) bool
}

// Ticker see time.Ticker.
type Ticker interface {
C() <-chan time.Time
Stop()
}

// NewStoppedTimer returns a stopped timer. Call Reset to get it ticking.
func NewStoppedTimer() Timer {
t := NewTimer(42 * time.Hour)
t.Stop()
return t
}

// Clock is an interface that mimics the one of the SDK time package.
type Clock interface {
Now() time.Time
Sleep(d time.Duration)
After(d time.Duration) <-chan time.Time
NewTimer(d time.Duration) Timer
AfterFunc(d time.Duration, f func()) Timer
NewTicker(d time.Duration) Ticker
Tick(d time.Duration) <-chan time.Time
Wait4Scheduled(n int, timeout time.Duration) bool
}
17 changes: 2 additions & 15 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,8 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
Expand Down Expand Up @@ -227,12 +224,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/uptrace/opentelemetry-go-extra/otellogrus v0.1.7 h1:1Lz7FaOfNuV8ZDOIFzLjzZn/aR+Q/F5OIwzqiXA2j78=
github.com/uptrace/opentelemetry-go-extra/otellogrus v0.1.7/go.mod h1:cBtIJGc/SoEXBaNdAlF75dSdwuXDe2xv2fG4r0UV7Wg=
github.com/uptrace/opentelemetry-go-extra/otellogrus v0.1.9 h1:kQc7EjbDgfsy2bUYRQ/fCdtVOke8bPSWBwHY4FvN0Pg=
github.com/uptrace/opentelemetry-go-extra/otellogrus v0.1.9/go.mod h1:ocTJjrGIZZpk4GcJlOZ5Iaw4jPqz65YbBbLp/cKlWPk=
github.com/uptrace/opentelemetry-go-extra/otelutil v0.1.7 h1:GxcTFwIDGO48uLODBNVrs+gmlghqIZuk0OaPN1Ogd2Y=
github.com/uptrace/opentelemetry-go-extra/otelutil v0.1.7/go.mod h1:VoJEtpolPhk7ThZaHZR+3OQe/e3M1Oz3vJymTInLwmg=
github.com/uptrace/opentelemetry-go-extra/otelutil v0.1.9 h1:Z5KSytLgwOBMZWwCPGeW2Q7olCBj0yWH+uNXtogjYM0=
github.com/uptrace/opentelemetry-go-extra/otelutil v0.1.9/go.mod h1:Q9Auy29b37RW2Y00aYqjaSUcKoYUVN/+0BCtLU3eMQQ=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand All @@ -245,20 +238,14 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3
go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.opentelemetry.io/otel v1.0.1/go.mod h1:OPEOD4jIT2SlZPMmwT6FqZz2C0ZNdQqiWcoK6M0SNFU=
go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
go.opentelemetry.io/otel v1.4.1 h1:QbINgGDDcoQUoMJa2mMaWno49lja9sHwp6aoa2n3a4g=
go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4=
go.opentelemetry.io/otel/exporters/jaeger v1.3.0 h1:HfydzioALdtcB26H5WHc4K47iTETJCdloL7VN579/L0=
go.opentelemetry.io/otel/exporters/jaeger v1.3.0/go.mod h1:KoYHi1BtkUPncGSRtCe/eh1ijsnePhSkxwzz07vU0Fc=
go.opentelemetry.io/otel/exporters/jaeger v1.4.1 h1:VHCK+2yTZDqDaVXj7JH2Z/khptuydo6C0ttBh2bxAbc=
go.opentelemetry.io/otel/exporters/jaeger v1.4.1/go.mod h1:ZW7vkOu9nC1CxsD8bHNHCia5JUbwP39vxgd1q4Z5rCI=
go.opentelemetry.io/otel/sdk v1.0.1/go.mod h1:HrdXne+BiwsOHYYkBE5ysIcv2bvdZstxzmCQhxTcZkI=
go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI=
go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs=
go.opentelemetry.io/otel/sdk v1.4.1 h1:J7EaW71E0v87qflB4cDolaqq3AcujGrtyIPGQoZOB0Y=
go.opentelemetry.io/otel/sdk v1.4.1/go.mod h1:NBwHDgDIBYjwK2WNu1OPgsIc2IJzmBXNnvIJxJc8BpE=
go.opentelemetry.io/otel/trace v1.0.1/go.mod h1:5g4i4fKLaX2BQpSBsxw8YYcgKpMMSW3x7ZTuYBr3sUk=
go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
go.opentelemetry.io/otel/trace v1.4.1 h1:O+16qcdTrT7zxv2J6GejTPFinSwA++cYerC5iSiF8EQ=
go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
Expand Down Expand Up @@ -340,11 +327,11 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486 h1:5hpz5aRr+W1erYCL5JRhSUBJRph7l9XkNveoExlrKYk=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 h1:8IVLkfbr2cLhv0a/vKq4UFUcJym8RmDoDboxCFWEjYE=
golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
Expand Down

0 comments on commit b3c1e97

Please sign in to comment.