LEFT | RIGHT |
(no file at all) | |
1 // Copyright 2009 The Go Authors. All rights reserved. | 1 // Copyright 2009 The Go Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style | 2 // Use of this source code is governed by a BSD-style |
3 // license that can be found in the LICENSE file. | 3 // license that can be found in the LICENSE file. |
4 | 4 |
5 package time | 5 package time |
6 | 6 |
7 import ( | 7 import "errors" |
8 » "errors" | |
9 » "sync" | |
10 ) | |
11 | 8 |
12 // A Ticker holds a synchronous channel that delivers `ticks' of a clock | 9 // A Ticker holds a synchronous channel that delivers `ticks' of a clock |
13 // at intervals. | 10 // at intervals. |
14 type Ticker struct { | 11 type Ticker struct { |
15 » C <-chan int64 // The channel on which the ticks are delivered. | 12 » C <-chan int64 // The channel on which the ticks are delivered. |
16 » c chan<- int64 // The same channel, but the end we use. | 13 » r runtimeTimer |
17 » ns int64 | 14 } |
18 » shutdown chan bool // Buffered channel used to signal shutdown. | 15 |
19 » nextTick int64 | 16 // NewTicker returns a new Ticker containing a channel that will |
20 » next *Ticker | 17 // send the time, in nanoseconds, every ns nanoseconds. It adjusts the |
| 18 // intervals to make up for pauses in delivery of the ticks. The value of |
| 19 // ns must be greater than zero; if not, NewTicker will panic. |
| 20 func NewTicker(ns int64) *Ticker { |
| 21 » if ns <= 0 { |
| 22 » » panic(errors.New("non-positive interval for NewTicker")) |
| 23 » } |
| 24 » // Give the channel a 1-element time buffer. |
| 25 » // If the client falls behind while reading, we drop ticks |
| 26 » // on the floor until the client catches up. |
| 27 » c := make(chan int64, 1) |
| 28 » t := &Ticker{ |
| 29 » » C: c, |
| 30 » » r: runtimeTimer{ |
| 31 » » » when: Nanoseconds() + ns, |
| 32 » » » period: ns, |
| 33 » » » f: sendTime, |
| 34 » » » arg: c, |
| 35 » » }, |
| 36 » } |
| 37 » startTimer(&t.r) |
| 38 » return t |
21 } | 39 } |
22 | 40 |
23 // Stop turns off a ticker. After Stop, no more ticks will be sent. | 41 // Stop turns off a ticker. After Stop, no more ticks will be sent. |
24 func (t *Ticker) Stop() { | 42 func (t *Ticker) Stop() { |
25 » select { | 43 » stopTimer(&t.r) |
26 » case t.shutdown <- true: | |
27 » » // ok | |
28 » default: | |
29 » » // Stop in progress already | |
30 » } | |
31 } | 44 } |
32 | 45 |
33 // Tick is a convenience wrapper for NewTicker providing access to the ticking | 46 // Tick is a convenience wrapper for NewTicker providing access to the ticking |
34 // channel only. Useful for clients that have no need to shut down the ticker. | 47 // channel only. Useful for clients that have no need to shut down the ticker. |
35 func Tick(ns int64) <-chan int64 { | 48 func Tick(ns int64) <-chan int64 { |
36 if ns <= 0 { | 49 if ns <= 0 { |
37 return nil | 50 return nil |
38 } | 51 } |
39 return NewTicker(ns).C | 52 return NewTicker(ns).C |
40 } | 53 } |
41 | |
42 type alarmer struct { | |
43 wakeUp chan bool // wakeup signals sent/received here | |
44 wakeMeAt chan int64 | |
45 wakeTime int64 | |
46 } | |
47 | |
48 // Set alarm to go off at time ns, if not already set earlier. | |
49 func (a *alarmer) set(ns int64) { | |
50 switch { | |
51 case a.wakeTime > ns: | |
52 // Next tick we expect is too late; shut down the late runner | |
53 // and (after fallthrough) start a new wakeLoop. | |
54 close(a.wakeMeAt) | |
55 fallthrough | |
56 case a.wakeMeAt == nil: | |
57 // There's no wakeLoop, start one. | |
58 a.wakeMeAt = make(chan int64) | |
59 a.wakeUp = make(chan bool, 1) | |
60 go wakeLoop(a.wakeMeAt, a.wakeUp) | |
61 fallthrough | |
62 case a.wakeTime == 0: | |
63 // Nobody else is waiting; it's just us. | |
64 a.wakeTime = ns | |
65 a.wakeMeAt <- ns | |
66 default: | |
67 // There's already someone scheduled. | |
68 } | |
69 } | |
70 | |
71 // Channel to notify tickerLoop of new Tickers being created. | |
72 var newTicker chan *Ticker | |
73 | |
74 func startTickerLoop() { | |
75 newTicker = make(chan *Ticker) | |
76 go tickerLoop() | |
77 } | |
78 | |
79 // wakeLoop delivers ticks at scheduled times, sleeping until the right moment. | |
80 // If another, earlier Ticker is created while it sleeps, tickerLoop() will star
t a new | |
81 // wakeLoop and signal that this one is done by closing the wakeMeAt channel. | |
82 func wakeLoop(wakeMeAt chan int64, wakeUp chan bool) { | |
83 for wakeAt := range wakeMeAt { | |
84 Sleep(wakeAt - Nanoseconds()) | |
85 wakeUp <- true | |
86 } | |
87 } | |
88 | |
89 // A single tickerLoop serves all ticks to Tickers. It waits for two events: | |
90 // either the creation of a new Ticker or a tick from the alarm, | |
91 // signaling a time to wake up one or more Tickers. | |
92 func tickerLoop() { | |
93 // Represents the next alarm to be delivered. | |
94 var alarm alarmer | |
95 var now, wakeTime int64 | |
96 var tickers *Ticker | |
97 for { | |
98 select { | |
99 case t := <-newTicker: | |
100 // Add Ticker to list | |
101 t.next = tickers | |
102 tickers = t | |
103 // Arrange for a new alarm if this one precedes the exis
ting one. | |
104 alarm.set(t.nextTick) | |
105 case <-alarm.wakeUp: | |
106 now = Nanoseconds() | |
107 wakeTime = now + 1e15 // very long in the future | |
108 var prev *Ticker = nil | |
109 // Scan list of tickers, delivering updates to those | |
110 // that need it and determining the next wake time. | |
111 // TODO(r): list should be sorted in time order. | |
112 for t := tickers; t != nil; t = t.next { | |
113 select { | |
114 case <-t.shutdown: | |
115 // Ticker is done; remove it from list. | |
116 if prev == nil { | |
117 tickers = t.next | |
118 } else { | |
119 prev.next = t.next | |
120 } | |
121 continue | |
122 default: | |
123 } | |
124 if t.nextTick <= now { | |
125 if len(t.c) == 0 { | |
126 // Only send if there's room. W
e must not block. | |
127 // The channel is allocated with
a one-element | |
128 // buffer, which is sufficient:
if he hasn't picked | |
129 // up the last tick, no point in
sending more. | |
130 t.c <- now | |
131 } | |
132 t.nextTick += t.ns | |
133 if t.nextTick <= now { | |
134 // Still behind; advance in one
big step. | |
135 t.nextTick += (now - t.nextTick
+ t.ns) / t.ns * t.ns | |
136 } | |
137 } | |
138 if t.nextTick < wakeTime { | |
139 wakeTime = t.nextTick | |
140 } | |
141 prev = t | |
142 } | |
143 if tickers != nil { | |
144 // Please send wakeup at earliest required time. | |
145 // If there are no tickers, don't bother. | |
146 alarm.wakeTime = wakeTime | |
147 alarm.wakeMeAt <- wakeTime | |
148 } else { | |
149 alarm.wakeTime = 0 | |
150 } | |
151 } | |
152 } | |
153 } | |
154 | |
155 var onceStartTickerLoop sync.Once | |
156 | |
157 // NewTicker returns a new Ticker containing a channel that will | |
158 // send the time, in nanoseconds, every ns nanoseconds. It adjusts the | |
159 // intervals to make up for pauses in delivery of the ticks. The value of | |
160 // ns must be greater than zero; if not, NewTicker will panic. | |
161 func NewTicker(ns int64) *Ticker { | |
162 if ns <= 0 { | |
163 panic(errors.New("non-positive interval for NewTicker")) | |
164 } | |
165 c := make(chan int64, 1) // See comment on send in tickerLoop | |
166 t := &Ticker{ | |
167 C: c, | |
168 c: c, | |
169 ns: ns, | |
170 shutdown: make(chan bool, 1), | |
171 nextTick: Nanoseconds() + ns, | |
172 } | |
173 onceStartTickerLoop.Do(startTickerLoop) | |
174 // must be run in background so global Tickers can be created | |
175 go func() { newTicker <- t }() | |
176 return t | |
177 } | |
LEFT | RIGHT |