Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(287)

Side by Side Diff: common/clock/testclock/testclock.go

Issue 1679023005: Add Context cancellation to clock. (Closed) Base URL: https://github.com/luci/luci-go@master
Patch Set: More test coverage, cleanup, consolidation. Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package testclock 5 package testclock
6 6
7 import ( 7 import (
8 "sync" 8 "sync"
9 "time" 9 "time"
10 10
11 "github.com/luci/luci-go/common/clock" 11 "github.com/luci/luci-go/common/clock"
12 "golang.org/x/net/context"
12 ) 13 )
13 14
14 // TestClock is a Clock interface with additional methods to help instrument it. 15 // TestClock is a Clock interface with additional methods to help instrument it.
15 type TestClock interface { 16 type TestClock interface {
16 clock.Clock 17 clock.Clock
18
19 // Set sets the test clock's time.
17 Set(time.Time) 20 Set(time.Time)
21
22 // Add advances the test clock's time.
18 Add(time.Duration) 23 Add(time.Duration)
24
25 // SetTimerCallback is a goroutine-safe method to set an instance-wide
26 // callback that is invoked when any timer begins.
19 SetTimerCallback(TimerCallback) 27 SetTimerCallback(TimerCallback)
20 } 28 }
21 29
22 // TimerCallback that can be invoked when a timer has been set. This is useful 30 // TimerCallback that can be invoked when a timer has been set. This is useful
23 // for sychronizing state when testing. 31 // for sychronizing state when testing.
24 type TimerCallback func(time.Duration, clock.Timer) 32 type TimerCallback func(time.Duration, clock.Timer)
25 33
26 type cancelFunc func()
27
28 // testClock is a test-oriented implementation of the 'Clock' interface. 34 // testClock is a test-oriented implementation of the 'Clock' interface.
29 // 35 //
30 // This implementation's Clock responses are configurable by modifying its 36 // This implementation's Clock responses are configurable by modifying its
31 // member variables. Time-based events are explicitly triggered by sending on a 37 // member variables. Time-based events are explicitly triggered by sending on a
32 // Timer instance's channel. 38 // Timer instance's channel.
33 type testClock struct { 39 type testClock struct {
34 sync.Mutex 40 sync.Mutex
35 41
36 now time.Time // The current clock time. 42 now time.Time // The current clock time.
37 timerCond *sync.Cond // Condition used to manage timer blocking. 43 timerCond *sync.Cond // Condition used to manage timer blocking.
(...skipping 12 matching lines...) Expand all
50 return &c 56 return &c
51 } 57 }
52 58
53 func (c *testClock) Now() time.Time { 59 func (c *testClock) Now() time.Time {
54 c.Lock() 60 c.Lock()
55 defer c.Unlock() 61 defer c.Unlock()
56 62
57 return c.now 63 return c.now
58 } 64 }
59 65
60 func (c *testClock) Sleep(d time.Duration) { 66 func (c *testClock) Sleep(ctx context.Context, d time.Duration) error {
61 » <-c.After(d) 67 » ar := <-c.After(ctx, d)
68 » return ar.Err
62 } 69 }
63 70
64 func (c *testClock) NewTimer() clock.Timer { 71 func (c *testClock) NewTimer(ctx context.Context) clock.Timer {
65 » return newTimer(c) 72 » t := newTimer(ctx, c)
73 » return t
66 } 74 }
67 75
68 func (c *testClock) After(d time.Duration) <-chan time.Time { 76 func (c *testClock) After(ctx context.Context, d time.Duration) <-chan clock.Tim erResult {
69 » t := c.NewTimer() 77 » t := newTimer(ctx, c)
70 t.Reset(d) 78 t.Reset(d)
71 » return t.GetC() 79 » return t.afterC
72 } 80 }
73 81
74 func (c *testClock) Set(t time.Time) { 82 func (c *testClock) Set(t time.Time) {
75 c.Lock() 83 c.Lock()
76 defer c.Unlock() 84 defer c.Unlock()
77 85
78 c.setTimeLocked(t) 86 c.setTimeLocked(t)
79 } 87 }
80 88
81 func (c *testClock) Add(d time.Duration) { 89 func (c *testClock) Add(d time.Duration) {
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
113 if callback := c.getTimerCallback(); callback != nil { 121 if callback := c.getTimerCallback(); callback != nil {
114 callback(d, t) 122 callback(d, t)
115 } 123 }
116 } 124 }
117 125
118 // invokeAt invokes the specified callback when the Clock has advanced at 126 // invokeAt invokes the specified callback when the Clock has advanced at
119 // or after the specified threshold. 127 // or after the specified threshold.
120 // 128 //
121 // The returned cancelFunc can be used to cancel the blocking. If the cancel 129 // The returned cancelFunc can be used to cancel the blocking. If the cancel
122 // function is invoked before the callback, the callback will not be invoked. 130 // function is invoked before the callback, the callback will not be invoked.
123 func (c *testClock) invokeAt(threshold time.Time, callback func(time.Time)) canc elFunc { 131 func (c *testClock) invokeAt(ctx context.Context, threshold time.Time, callback func(time.Time, error)) {
132 » finishedC := make(chan struct{})
124 stopC := make(chan struct{}) 133 stopC := make(chan struct{})
125 finishedC := make(chan struct{})
126 134
127 // Our control goroutine will monitor both time and stop signals. It wil l 135 // Our control goroutine will monitor both time and stop signals. It wil l
128 // terminate when either the current time has exceeded our time threshol d or 136 // terminate when either the current time has exceeded our time threshol d or
129 » // it has been signalled to terminate via stopC. 137 » // it has been signalled to terminate via Context.
130 // 138 //
131 // The lock that we take our here is owned by the following goroutine. 139 // The lock that we take our here is owned by the following goroutine.
132 c.Lock() 140 c.Lock()
133 go func() { 141 go func() {
134 defer close(finishedC) 142 defer close(finishedC)
143
135 defer func() { 144 defer func() {
136 now := c.now 145 now := c.now
137 c.Unlock() 146 c.Unlock()
138 147
139 » » » if callback != nil { 148 » » » // If we finished naturally, but our Context is Done, pr efer the Context
140 » » » » callback(now) 149 » » » // error.
150 » » » var err error
151 » » » select {
152 » » » case <-ctx.Done():
153 » » » » err = ctx.Err()
154 » » » default:
155 » » » » break
iannucci 2016/02/10 22:30:28 merrrr
dnj (Google) 2016/02/11 01:26:54 Done.
141 } 156 }
157 callback(now, err)
iannucci 2016/02/10 22:30:28 call with TimerResult?
dnj (Google) 2016/02/11 01:26:54 Done.
142 }() 158 }()
143 159
144 for { 160 for {
145 // Determine if we are past our signalling threshold. We can safely access 161 // Determine if we are past our signalling threshold. We can safely access
146 // our clock's time member directly, since we hold its l ock from the 162 // our clock's time member directly, since we hold its l ock from the
147 // condition firing. 163 // condition firing.
148 if !c.now.Before(threshold) { 164 if !c.now.Before(threshold) {
149 return 165 return
150 } 166 }
151 167
152 // Wait for a signal from our clock's condition. 168 // Wait for a signal from our clock's condition.
153 c.timerCond.Wait() 169 c.timerCond.Wait()
154 170
155 // Have we been stopped? 171 // Have we been stopped?
156 select { 172 select {
157 case <-stopC: 173 case <-stopC:
158 callback = nil
159 return 174 return
160 175
161 default: 176 default:
162 // Nope. 177 // Nope.
163 } 178 }
164 } 179 }
165 }() 180 }()
166 181
167 » return func() { 182 » // This goroutine will monitor our Context and unblock if its Done chann el
168 » » // Signal our goroutine to stop. 183 » // has an error.
iannucci 2016/02/10 22:30:28 s/error/be all closed up in here.
dnj (Google) 2016/02/11 01:26:54 Done.
169 » » close(stopC) 184 » go func() {
185 » » select {
186 » » case <-finishedC:
187 » » » // Our timer has expired, so no need to further monitor.
188 » » » return
170 189
171 » » // Lock, then Broadcast. This ensures that if the goroutine is r unning, it 190 » » case <-ctx.Done():
172 » » // will be at a Wait() and therefore this Broadcast will wake it . 191 » » » // If we finish at the same time our Context is canceled , we don't need to
173 » » func() { 192 » » » // wake our monitor (determinism).
174 » » » c.Lock() 193 » » » select {
175 » » » defer c.Unlock() 194 » » » case <-finishedC:
195 » » » » return
196 » » » default:
197 » » » » break
198 » » » }
199
200 » » » // Our Context has been canceled. Forward this to our mo nitor goroutine
201 » » » // and wake our condition.
202 » » » close(stopC)
176 c.timerCond.Broadcast() 203 c.timerCond.Broadcast()
177 » » }() 204 » » }
178 205 » }()
179 » » // Block until it has finished.
180 » » <-finishedC
181 » }
182 } 206 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698