-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtimer.go
More file actions
151 lines (132 loc) · 3.46 KB
/
timer.go
File metadata and controls
151 lines (132 loc) · 3.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package hpt
import (
"runtime"
"sync"
"sync/atomic"
"time"
)
// Timer represents a single event. When the Timer expires, the current time
// will be sent on C. A Timer must be created with NewTimer or AfterFunc.
//
// Each Timer consumes a dedicated OS thread (via runtime.LockOSThread)
// until it fires or is stopped.
type Timer struct {
C <-chan time.Time
c chan time.Time
f func() // non-nil for AfterFunc timers
stopped atomic.Bool
fired atomic.Bool
mu sync.Mutex
resetCh chan time.Duration
done chan struct{}
}
// NewTimer creates a new Timer that will send the current time on its
// channel after at least duration d using high-precision OS primitives.
func NewTimer(d time.Duration) *Timer {
c := make(chan time.Time, 1)
done := make(chan struct{})
resetCh := make(chan time.Duration, 1)
t := &Timer{
C: c,
c: c,
resetCh: resetCh,
done: done,
}
go t.run(d, c, nil, done, resetCh)
return t
}
// AfterFunc waits for the duration to elapse and then calls f in its own
// goroutine. It returns a Timer that can be used to cancel the call using
// its Stop method. The returned Timer's C field is nil.
func AfterFunc(d time.Duration, f func()) *Timer {
done := make(chan struct{})
resetCh := make(chan time.Duration, 1)
t := &Timer{
f: f,
resetCh: resetCh,
done: done,
}
go t.run(d, nil, f, done, resetCh)
return t
}
// After waits for the duration to elapse and then sends the current time
// on the returned channel. It is equivalent to NewTimer(d).C.
func After(d time.Duration) <-chan time.Time {
return NewTimer(d).C
}
// Stop prevents the Timer from firing. It returns true if the call stops
// the timer, false if the timer has already expired or been stopped.
func (t *Timer) Stop() bool {
wasActive := !t.stopped.Swap(true) && !t.fired.Load()
t.mu.Lock()
defer t.mu.Unlock()
select {
case <-t.done:
default:
close(t.done)
}
return wasActive
}
// Reset changes the timer to expire after duration d. It returns true if
// the timer had been active, false if the timer had expired or been stopped.
//
// A timer must be stopped or expired and its channel drained before Reset
// is called.
func (t *Timer) Reset(d time.Duration) bool {
t.mu.Lock()
defer t.mu.Unlock()
wasActive := !t.fired.Load() && !t.stopped.Load()
// Restart the timer.
t.stopped.Store(false)
t.fired.Store(false)
// If the previous goroutine is still alive, signal a reset.
select {
case t.resetCh <- d:
default:
}
// If the previous goroutine finished, start a new one.
select {
case <-t.done:
done := make(chan struct{})
resetCh := make(chan time.Duration, 1)
t.done = done
t.resetCh = resetCh
go t.run(d, t.c, t.f, done, resetCh)
default:
}
return wasActive
}
// run is the timer goroutine. It captures c, f, done, and resetCh as local
// values to avoid races with Stop/Reset which modify the struct under the mutex.
func (t *Timer) run(d time.Duration, c chan time.Time, f func(), done <-chan struct{}, resetCh <-chan time.Duration) {
threadStarted()
defer threadStopped()
runtime.LockOSThread()
defer runtime.UnlockOSThread()
for {
deadline := monotonicNow() + d.Nanoseconds()
sleepUntil(deadline)
// Check for stop or reset.
select {
case <-done:
return
case newD := <-resetCh:
d = newD
continue
default:
}
if t.stopped.Load() {
return
}
t.fired.Store(true)
if f != nil {
go f()
} else if c != nil {
select {
case c <- time.Now():
default:
}
}
return
}
}