Index: go/src/infra/libs/clock/testclock/testtimer.go |
diff --git a/go/src/infra/libs/clock/testclock/testtimer.go b/go/src/infra/libs/clock/testclock/testtimer.go |
new file mode 100644 |
index 0000000000000000000000000000000000000000..4699f5ac07ee2a9ec17f2676aa43bb8fd4ae78c6 |
--- /dev/null |
+++ b/go/src/infra/libs/clock/testclock/testtimer.go |
@@ -0,0 +1,138 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package testclock |
+ |
+import ( |
+ "sync" |
+ "time" |
+ |
+ "infra/libs/clock" |
+) |
+ |
+// timer is an implementation of clock.TestTimer that uses a channel |
+// to signal the timer to fire. |
+// |
+// The channel is buffered so it can be used without requiring a separate signalling |
+// goroutine. |
+type timer struct { |
+ sync.Mutex |
+ |
+ clock *testClock // The underlying test clock instance. |
+ signalC chan time.Time // Underlying signal channel. |
+ |
+ stopC chan struct{} // Signal channel from Stop() to terminate goroutines. |
+ finishedC chan struct{} // Signal channel to indicate when |
+} |
+ |
+var _ clock.Timer = (*timer)(nil) |
+ |
+// NewTimer returns a new, instantiated timer. |
+func newTimer(clock *testClock) clock.Timer { |
+ t := timer{ |
+ clock: clock, |
+ } |
+ return &t |
+} |
+ |
+func (t *timer) GetC() (c <-chan time.Time) { |
+ t.Lock() |
+ defer t.Unlock() |
+ |
+ if t.stopC != nil { |
+ c = t.signalC |
+ } |
+ return |
+} |
+ |
+func (t *timer) Reset(d time.Duration) (active bool) { |
+ now := t.clock.Now() |
+ triggerTime := now.Add(d) |
+ |
+ // Signal our timerSet callback. |
+ t.clock.signalTimerSet(t) |
+ |
+ // Stop our current polling goroutine, if it's running. |
+ active = t.Stop() |
dnj
2015/06/03 03:41:43
Racy
|
+ |
+ // Start a new polling goroutine. |
+ t.Lock() |
+ defer t.Unlock() |
+ |
+ stopC := make(chan struct{}) |
+ finishedC := make(chan struct{}) |
+ |
+ // Our control goroutine will monitor both time and stop signals. It will only terminate |
+ // when stopC has been closed. |
+ // |
+ // The lock that we take our here is owned by the following goroutine. |
+ t.clock.Lock() |
+ go func() { |
+ defer close(finishedC) |
+ defer t.clock.Unlock() |
+ |
+ // If the time has already been adjusted, trigger immediately. |
+ if !t.clock.now.Before(triggerTime) { |
+ t.signal(t.clock.now) |
+ return |
+ } |
+ |
+ for { |
+ // Wait for a signal from our clock's condition. |
+ t.clock.timerCond.Wait() |
+ |
+ // Have we been stopped? |
+ select { |
+ case <-stopC: |
+ return |
+ |
+ default: |
+ // Nope. |
+ } |
+ |
+ // Determine if we are past our signalling threshold. We can safely access our |
+ // clock's time member directly, since we hold its lock from the condition firing. |
+ if !t.clock.now.Before(triggerTime) { |
+ t.signal(t.clock.now) |
+ return |
+ } |
+ } |
+ }() |
+ |
dnj
2015/06/03 03:41:43
Move up
|
+ t.stopC = stopC |
+ t.finishedC = finishedC |
+ t.signalC = make(chan time.Time, 1) |
+ return |
+} |
+ |
+func (t *timer) Stop() bool { |
+ t.Lock() |
+ defer t.Unlock() |
+ |
+ // If the timer is not running, we're done. |
+ if t.stopC == nil { |
+ return false |
+ } |
+ |
+ // Close our stop channel and block pending goroutine termination. |
+ close(t.stopC) |
+ t.clock.pokeTimers() |
+ <-t.finishedC |
+ |
+ // Clear our state. |
+ t.stopC = nil |
+ t.finishedC = nil |
+ return true |
+} |
+ |
+// Sends a single signal, clearing the channel afterwards. |
+func (t *timer) signal(now time.Time) { |
+ t.Lock() |
+ defer t.Unlock() |
+ |
+ if t.signalC != nil { |
+ t.signalC <- now |
+ t.signalC = nil |
+ } |
+} |