Chromium Code Reviews| 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 |
| + } |
| +} |