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

Unified Diff: common/clock/systemtimer.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 side-by-side diff with in-line comments
Download patch
Index: common/clock/systemtimer.go
diff --git a/common/clock/systemtimer.go b/common/clock/systemtimer.go
index 0389a2ebf664b91e4790f925c3fe9e5f44c27664..926154b0aab9afd28b696042f403b1b981f4a4fe 100644
--- a/common/clock/systemtimer.go
+++ b/common/clock/systemtimer.go
@@ -6,32 +6,89 @@ package clock
import (
"time"
+
+ "golang.org/x/net/context"
)
type systemTimer struct {
- T *time.Timer // The underlying timer. Starts as nil, is initialized on Reset.
+ // base is the underlying timer. It starts as nil, and is initialized on
+ // Reset.
+ ctx context.Context
iannucci 2016/02/10 22:30:28 base? ctx?
dnj (Google) 2016/02/11 01:26:54 Done.
+
+ // timerC is the current timer channel.
+ timerC chan TimerResult
+ // timerDoneC is a signal channel to alert our monitor that this timer has
+ // been canceled.
+ timerStoppedC chan struct{}
iannucci 2016/02/10 22:30:28 comments
dnj (Google) 2016/02/11 01:26:54 Done.
+ // timerMonitorResultC returns true if the timer monitor was prematurely
+ // terminated, false if not.
+ timerMonitorResultC chan bool
}
var _ Timer = (*systemTimer)(nil)
-func (t *systemTimer) GetC() <-chan time.Time {
- if t.T == nil {
- return nil
- }
- return t.T.C
+func (t *systemTimer) GetC() <-chan TimerResult {
+ return t.timerC
}
-func (t *systemTimer) Reset(d time.Duration) bool {
- if t.T == nil {
- t.T = time.NewTimer(d)
- return false
+func (t *systemTimer) Reset(d time.Duration) (running bool) {
+ running = t.Stop()
+ t.reset()
+
+ // If our Context is already done, finish immediately.
+ select {
iannucci 2016/02/10 22:30:28 I'd use t.ctx.Err() instead
dnj (Google) 2016/02/11 01:26:54 Done.
+ case <-t.ctx.Done():
+ t.timerC <- TimerResult{Time: time.Now(), Err: t.ctx.Err()}
+ return
+ default:
+ break
}
- return t.T.Reset(d)
+
+ // Start a monitor goroutine and our actual timer. Copy our channels, since
+ // future stop/reset will change the systemTimer's values and our goroutine
+ // should only operate on this round's values.
+ timerC := t.timerC
+ timerStoppedC := make(chan struct{})
+ timerMonitorResultC := make(chan bool, 1)
+ go func() {
+ interrupted := false
+ defer func() {
+ timerMonitorResultC <- interrupted
+ }()
+
+ select {
+ case <-timerStoppedC:
+ interrupted = true
+
+ case <-t.ctx.Done():
+ timerC <- TimerResult{Time: time.Now(), Err: t.ctx.Err()}
+ case now := <-time.After(d):
+ // For determinism, prioritize Context cancellation over timer expiration.
+ select {
+ case <-t.ctx.Done():
+ timerC <- TimerResult{Time: now, Err: t.ctx.Err()}
+ default:
+ break
+ }
iannucci 2016/02/10 22:30:28 also Err()
dnj (Google) 2016/02/11 01:26:54 Done.
+
+ timerC <- TimerResult{Time: now}
+ }
+ }()
+
+ t.timerStoppedC = timerStoppedC
+ t.timerMonitorResultC = timerMonitorResultC
+ return
}
func (t *systemTimer) Stop() bool {
- if t.T == nil {
+ if t.timerStoppedC == nil {
return false
}
- return t.T.Stop()
+ close(t.timerStoppedC)
+ t.timerStoppedC = nil
+ return <-t.timerMonitorResultC
+}
+
+func (t *systemTimer) reset() {
iannucci 2016/02/10 22:30:28 let's inline this
dnj (Google) 2016/02/11 01:26:54 Done.
+ t.timerC = make(chan TimerResult, 1)
}

Powered by Google App Engine
This is Rietveld 408576698