Skip to content

Commit 98ef254

Browse files
author
Hugo Chargois
authored
Merge pull request #1 from synthesio/fix-data-races
Fix data races
2 parents 7c8c611 + 9ecac7e commit 98ef254

File tree

3 files changed

+70
-20
lines changed

3 files changed

+70
-20
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/BorisBorshevsky/timemock
22

3-
go 1.14
3+
go 1.19
44

55
require (
66
github.com/golang/mock v1.4.3

time_mock_suite_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package timemock
22

33
import (
4+
"sync"
45
"testing"
56

67
"time"
@@ -133,3 +134,47 @@ var _ = Describe("clock", func() {
133134
})
134135

135136
})
137+
138+
func TestRaces(t *testing.T) {
139+
clock := New()
140+
refTime := time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC)
141+
142+
n := 100
143+
var wg sync.WaitGroup
144+
wg.Add(n)
145+
for i := 0; i < n; i++ {
146+
go func() {
147+
for j := 0; j < n; j++ {
148+
clock.Now()
149+
clock.Freeze(refTime)
150+
clock.Return()
151+
clock.Travel(refTime)
152+
clock.Scale(2)
153+
}
154+
wg.Done()
155+
}()
156+
}
157+
wg.Wait()
158+
}
159+
160+
func BenchmarkClock(b *testing.B) {
161+
b.Run("Freezed", func(b *testing.B) {
162+
clock := New()
163+
refTime := time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC)
164+
clock.Freeze(refTime)
165+
for i := 0; i < b.N; i++ {
166+
clock.Now()
167+
}
168+
})
169+
b.Run("Unfreezed", func(b *testing.B) {
170+
clock := New()
171+
for i := 0; i < b.N; i++ {
172+
clock.Now()
173+
}
174+
})
175+
b.Run("Stdlib", func(b *testing.B) {
176+
for i := 0; i < b.N; i++ {
177+
time.Now()
178+
}
179+
})
180+
}

timemock.go

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,59 +2,64 @@ package timemock
22

33
import (
44
"sync"
5+
"sync/atomic"
56
"time"
67
)
78

8-
var (
9-
now = time.Now
10-
)
11-
129
type timemockClock struct {
1310
rw *sync.RWMutex
14-
frozen bool
15-
traveled bool
11+
frozen atomic.Bool
12+
traveled atomic.Bool
1613
freezeTime time.Time
1714
travelTime time.Time
1815
scale float64
1916
}
2017

2118
func (c *timemockClock) Scale(scale float64) {
19+
c.rw.Lock()
20+
defer c.rw.Unlock()
2221
c.scale = scale
23-
if !c.traveled {
24-
c.Travel(now())
22+
if !c.traveled.Load() {
23+
now := time.Now()
24+
c.freezeTime = now
25+
c.travelTime = now
26+
c.traveled.Store(true)
2527
}
2628
}
2729

2830
func (c *timemockClock) Now() time.Time {
29-
if c.frozen || c.traveled {
30-
c.rw.RLock()
31-
defer c.rw.RUnlock()
31+
// fast path
32+
if !c.frozen.Load() && !c.traveled.Load() {
33+
return time.Now()
3234
}
3335

34-
if c.frozen {
36+
c.rw.RLock()
37+
defer c.rw.RUnlock()
38+
39+
if c.frozen.Load() {
3540
return c.freezeTime
3641
}
3742

38-
if c.traveled {
43+
if c.traveled.Load() {
3944
return c.freezeTime.Add(time.Duration(float64(time.Since(c.travelTime)) * c.scale))
4045
}
4146

42-
return now()
47+
return time.Now()
4348
}
4449

4550
func (c *timemockClock) Freeze(t time.Time) {
4651
c.rw.Lock()
4752
defer c.rw.Unlock()
4853
c.freezeTime = t
49-
c.frozen = true
54+
c.frozen.Store(true)
5055
}
5156

5257
func (c *timemockClock) Travel(t time.Time) {
5358
c.rw.Lock()
5459
defer c.rw.Unlock()
5560
c.freezeTime = t
56-
c.travelTime = now()
57-
c.traveled = true
61+
c.travelTime = time.Now()
62+
c.traveled.Store(true)
5863
}
5964

6065
func (c *timemockClock) Since(t time.Time) time.Duration {
@@ -68,7 +73,7 @@ func (c *timemockClock) Until(t time.Time) time.Duration {
6873
func (c *timemockClock) Return() {
6974
c.rw.Lock()
7075
defer c.rw.Unlock()
71-
c.frozen = false
72-
c.traveled = false
76+
c.frozen.Store(false)
77+
c.traveled.Store(false)
7378
c.scale = 1
7479
}

0 commit comments

Comments
 (0)