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

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: Much more invasive, cancel by default, remove meter package. 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.Aft 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)) {
124 » stopC := make(chan struct{})
125 finishedC := make(chan struct{}) 132 finishedC := make(chan struct{})
133 stopC := make(chan error, 1)
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
144 var err error
135 defer func() { 145 defer func() {
136 now := c.now 146 now := c.now
137 c.Unlock() 147 c.Unlock()
138 148
139 » » » if callback != nil { 149 » » » callback(now, err)
140 » » » » callback(now)
141 » » » }
142 }() 150 }()
143 151
144 for { 152 for {
145 // Determine if we are past our signalling threshold. We can safely access 153 // 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 154 // our clock's time member directly, since we hold its l ock from the
147 // condition firing. 155 // condition firing.
148 if !c.now.Before(threshold) { 156 if !c.now.Before(threshold) {
149 return 157 return
150 } 158 }
151 159
152 // Wait for a signal from our clock's condition. 160 // Wait for a signal from our clock's condition.
153 c.timerCond.Wait() 161 c.timerCond.Wait()
154 162
155 // Have we been stopped? 163 // Have we been stopped?
156 select { 164 select {
157 » » » case <-stopC: 165 » » » case err = <-stopC:
158 » » » » callback = nil
159 return 166 return
160 167
161 default: 168 default:
162 // Nope. 169 // Nope.
163 } 170 }
164 } 171 }
165 }() 172 }()
166 173
167 » return func() { 174 » // This goroutine will monitor our Context and unblock if its Done chann el
168 » » // Signal our goroutine to stop. 175 » // has an error.
169 » » close(stopC) 176 » go func() {
177 » » select {
178 » » case <-finishedC:
179 » » » // Our timer has expired, so no need to further monitor.
180 » » » return
170 181
171 » » // Lock, then Broadcast. This ensures that if the goroutine is r unning, it 182 » » case <-ctx.Done():
172 » » // will be at a Wait() and therefore this Broadcast will wake it . 183 » » » // If we finish at the same time our Context is canceled , we don't need to
173 » » func() { 184 » » » // wake our monitor (determinism).
174 » » » c.Lock() 185 » » » select {
175 » » » defer c.Unlock() 186 » » » case <-finishedC:
187 » » » » return
188 » » » default:
189 » » » » break
190 » » » }
191
192 » » » // Our Context has been canceled. Forward this to our mo nitor goroutine
193 » » » // and wake our condition.
194 » » » stopC <- ctx.Err()
176 c.timerCond.Broadcast() 195 c.timerCond.Broadcast()
177 » » }() 196 » » }
197 » }()
198 }
178 199
179 » » // Block until it has finished. 200 // GetTags returns the tags associated with the specified timer. If the timer
180 » » <-finishedC 201 // has no tags, an empty slice (nil) will be returned.
202 func GetTags(t clock.Timer) []string {
203 » if tt, ok := t.(*timer); ok {
204 » » return clock.Tags(tt.ctx)
181 } 205 }
206 return nil
182 } 207 }
208
209 // HasTags tests if a given timer has the same tags.
210 func HasTags(t clock.Timer, tags ...string) bool {
211 timerTags := GetTags(t)
212 if len(timerTags) != len(tags) {
213 return false
214 }
215 for i, tag := range timerTags {
216 if tags[i] != tag {
217 return false
218 }
219 }
220 return true
221 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698