OLD | NEW |
---|---|
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of dart.io; | 5 part of dart.io; |
6 | 6 |
7 // Timer heap implemented as a array-based binary heap[0]. | 7 // Timer heap implemented as a array-based binary heap[0]. |
8 // This allows for O(1) `first`, O(log(n)) `remove`/`removeFirst` and O(log(n)) | 8 // This allows for O(1) `first`, O(log(n)) `remove`/`removeFirst` and O(log(n)) |
9 // `add`. | 9 // `add`. |
10 // | 10 // |
11 // To ensure the timers are ordered by insertion time, the _Timer class has a | 11 // To ensure the timers are ordered by insertion time, the _Timer class has a |
12 // `_id` field set when added to the heap. | 12 // `_id` field set when added to the heap. |
13 // | 13 // |
14 // [0] http://en.wikipedia.org/wiki/Binary_heap | 14 // [0] http://en.wikipedia.org/wiki/Binary_heap |
15 class _TimerHeap { | 15 class _TimerHeap { |
16 List<_Timer> _list; | 16 List<_Timer> _list; |
17 int _used = 0; | 17 int _used = 0; |
18 | 18 |
19 _TimerHeap([int initSize = 7]) | 19 _TimerHeap([int initSize = 7]) |
20 : _list = new List<_Timer>(initSize); | 20 : _list = new List<_Timer>(initSize); |
21 | 21 |
22 bool get isEmpty => _used == 0; | 22 bool get isEmpty => _used == 0; |
23 bool get isNotEmpty => _used > 0; | |
24 | 23 |
25 _Timer get first => _list[0]; | 24 _Timer get first => _list[0]; |
26 | 25 |
27 bool isFirst(_Timer timer) => timer._indexOrNext == 0; | 26 bool isFirst(_Timer timer) => timer._indexOrNext == 0; |
28 | 27 |
29 void add(_Timer timer) { | 28 void add(_Timer timer) { |
30 if (_used == _list.length) { | 29 if (_used == _list.length) { |
31 _resize(); | 30 _resize(); |
32 } | 31 } |
33 timer._indexOrNext = _used++; | 32 timer._indexOrNext = _used++; |
34 _list[timer._indexOrNext] = timer; | 33 _list[timer._indexOrNext] = timer; |
35 _bubbleUp(timer); | 34 _bubbleUp(timer); |
36 } | 35 } |
37 | 36 |
38 _Timer removeFirst() { | 37 _Timer removeFirst() { |
39 var f = first; | 38 var f = first; |
40 remove(f); | 39 remove(f); |
41 return f; | 40 return f; |
42 } | 41 } |
43 | 42 |
44 void remove(_Timer timer) { | 43 void remove(_Timer timer) { |
45 _used--; | 44 _used--; |
46 timer._id = -1; | |
47 if (isEmpty) { | 45 if (isEmpty) { |
48 _list[0] = null; | 46 _list[0] = null; |
49 timer._indexOrNext = null; | 47 timer._indexOrNext = null; |
50 return; | 48 return; |
51 } | 49 } |
52 var last = _list[_used]; | 50 var last = _list[_used]; |
53 if (!identical(last, timer)) { | 51 if (!identical(last, timer)) { |
54 last._indexOrNext = timer._indexOrNext; | 52 last._indexOrNext = timer._indexOrNext; |
55 _list[last._indexOrNext] = last; | 53 _list[last._indexOrNext] = last; |
56 if (last._compareTo(timer) < 0) { | 54 if (last._compareTo(timer) < 0) { |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
111 Timer _leftChild(_Timer timer) => _list[_leftChildIndex(timer._indexOrNext)]; | 109 Timer _leftChild(_Timer timer) => _list[_leftChildIndex(timer._indexOrNext)]; |
112 Timer _rightChild(_Timer timer) => | 110 Timer _rightChild(_Timer timer) => |
113 _list[_rightChildIndex(timer._indexOrNext)]; | 111 _list[_rightChildIndex(timer._indexOrNext)]; |
114 | 112 |
115 static int _parentIndex(int index) => (index - 1) ~/ 2; | 113 static int _parentIndex(int index) => (index - 1) ~/ 2; |
116 static int _leftChildIndex(int index) => 2 * index + 1; | 114 static int _leftChildIndex(int index) => 2 * index + 1; |
117 static int _rightChildIndex(int index) => 2 * index + 2; | 115 static int _rightChildIndex(int index) => 2 * index + 2; |
118 } | 116 } |
119 | 117 |
120 class _Timer implements Timer { | 118 class _Timer implements Timer { |
121 // Disables the timer. | 119 // Cancels the timer in the event handler. |
122 static const int _NO_TIMER = -1; | 120 static const int _NO_TIMER = -1; |
123 | 121 |
124 // Timers are ordered by wakeup time. | 122 // Timers are ordered by wakeup time. |
125 static _TimerHeap _heap = new _TimerHeap(); | 123 static _TimerHeap _heap = new _TimerHeap(); |
126 static _Timer _firstZeroTimer; | 124 static _Timer _firstZeroTimer; |
127 static _Timer _lastZeroTimer; | 125 static _Timer _lastZeroTimer; |
126 | |
127 // We use an id to be able to sort timers with the same expiration time. | |
128 // ids are recycled after ID_MASK enqueues or when the timer queue is empty. | |
129 static int _ID_MASK = 0x1fffffff; | |
128 static int _idCount = 0; | 130 static int _idCount = 0; |
129 | 131 |
130 static RawReceivePort _receivePort; | 132 static RawReceivePort _receivePort; |
131 static SendPort _sendPort; | 133 static SendPort _sendPort; |
132 static bool _handlingCallbacks = false; | 134 static bool _handlingCallbacks = false; |
133 | 135 |
134 Function _callback; | 136 Function _callback; // Closure to call when timer fires. null if canceled. |
135 int _milliSeconds; | 137 int _wakeupTime; // Expiration time. |
136 int _wakeupTime = 0; | 138 int _milliSeconds; // Duration specified at creation. |
137 var _indexOrNext; | 139 bool _repeating; // Indicates periodic timers. |
138 int _id = -1; | 140 var _indexOrNext; // Index if part of the TimerHeap, link otherwise. |
141 int _id; // Incrementing id to enable sorting of timers with same expiry. | |
142 | |
143 // Get the next available id. We accept collisions and reordering when the | |
144 // _idCount overflows and the timers expire at the same millisecond. | |
145 static int _nextId() { | |
146 var result = _idCount; | |
147 _idCount = (_idCount + 1) & _ID_MASK; | |
148 return result; | |
149 } | |
150 | |
151 _Timer._internal(this._callback, | |
152 this._wakeupTime, | |
153 this._milliSeconds, | |
154 this._repeating) : _id = _nextId(); | |
139 | 155 |
140 static Timer _createTimer(void callback(Timer timer), | 156 static Timer _createTimer(void callback(Timer timer), |
141 int milliSeconds, | 157 int milliSeconds, |
142 bool repeating) { | 158 bool repeating) { |
143 _Timer timer = new _Timer._internal(); | 159 // Negative timeouts are treated as if 0 timeout. |
144 timer._callback = callback; | 160 if (milliSeconds < 0) { |
145 if (milliSeconds > 0) { | 161 milliSeconds = 0; |
146 // Add one because DateTime.now() is assumed to round down | |
147 // to nearest millisecond, not up, so that time + duration is before | |
148 // duration milliseconds from now. Using micosecond timers like | |
149 // Stopwatch allows detecting that the timer fires early. | |
150 timer._wakeupTime = | |
151 new DateTime.now().millisecondsSinceEpoch + 1 + milliSeconds; | |
152 } | 162 } |
153 timer._milliSeconds = repeating ? milliSeconds : -1; | 163 // Add one because DateTime.now() is assumed to round down |
164 // to nearest millisecond, not up, so that time + duration is before | |
165 // duration milliseconds from now. Using microsecond timers like | |
166 // Stopwatch allows detecting that the timer fires early. | |
167 int now = new DateTime.now().millisecondsSinceEpoch; | |
168 int wakeupTime = (milliSeconds == 0) ? now : (now + 1 + milliSeconds); | |
169 | |
170 _Timer timer = new _Timer._internal(callback, | |
171 wakeupTime, | |
172 milliSeconds, | |
173 repeating); | |
174 | |
154 if (timer._addTimerToHeap()) { | 175 if (timer._addTimerToHeap()) { |
155 // The new timer is the first in queue. Update event handler. | 176 // The new timer is the first in queue. Update event handler. |
156 _notifyEventHandler(); | 177 _notifyEventHandler(); |
157 } | 178 } |
158 return timer; | 179 return timer; |
159 } | 180 } |
160 | 181 |
161 factory _Timer(int milliSeconds, void callback(Timer timer)) { | 182 factory _Timer(int milliSeconds, void callback(Timer timer)) { |
162 return _createTimer(callback, milliSeconds, false); | 183 return _createTimer(callback, milliSeconds, false); |
163 } | 184 } |
164 | 185 |
165 factory _Timer.periodic(int milliSeconds, void callback(Timer timer)) { | 186 factory _Timer.periodic(int milliSeconds, void callback(Timer timer)) { |
166 return _createTimer(callback, milliSeconds, true); | 187 return _createTimer(callback, milliSeconds, true); |
167 } | 188 } |
168 | 189 |
169 _Timer._internal() {} | 190 bool get _isInHeap => _indexOrNext is int; |
170 | |
171 bool get _isInHeap => _id >= 0; | |
172 | 191 |
173 void _clear() { | 192 void _clear() { |
174 _callback = null; | 193 _callback = null; |
175 } | 194 } |
176 | 195 |
177 int _compareTo(_Timer other) { | 196 int _compareTo(_Timer other) { |
178 int c = _wakeupTime - other._wakeupTime; | 197 int c = _wakeupTime - other._wakeupTime; |
179 if (c != 0) return c; | 198 if (c != 0) return c; |
180 return _id - other._id; | 199 return _id - other._id; |
181 } | 200 } |
182 | 201 |
183 bool get _repeating => _milliSeconds >= 0; | |
184 | |
185 bool get isActive => _callback != null; | 202 bool get isActive => _callback != null; |
186 | 203 |
187 // Cancels a set timer. The timer is removed from the timer list and if | 204 // Cancels a set timer. The timer is removed from the timer list and if |
188 // the given timer is the earliest timer the native timer is reset. | 205 // the given timer is the earliest timer the native timer is reset. |
zra
2015/01/07 15:48:03
"the native timer is reset" -> "the event handler
Ivan Posva
2015/01/07 20:52:00
Done.
| |
189 void cancel() { | 206 void cancel() { |
190 _clear(); | 207 _clear(); |
191 if (!_isInHeap) return; | 208 if (!_isInHeap) return; |
192 assert(_wakeupTime != 0); | 209 // Only heap timers are really removed. Others are just dropped on |
210 // notification. | |
193 bool update = (_firstZeroTimer == null) && _heap.isFirst(this); | 211 bool update = (_firstZeroTimer == null) && _heap.isFirst(this); |
194 _heap.remove(this); | 212 _heap.remove(this); |
195 if (update) { | 213 if (update) { |
196 _notifyEventHandler(); | 214 _notifyEventHandler(); |
197 } | 215 } |
198 } | 216 } |
199 | 217 |
200 void _advanceWakeupTime() { | 218 void _advanceWakeupTime() { |
201 assert(_milliSeconds >= 0); | 219 // Recalculate the next wakeup time. For repeating timers with a 0 timeout |
202 _wakeupTime += _milliSeconds; | 220 // the next wakeup time is now. |
221 _id = _nextId(); | |
222 if (_milliSeconds > 0) { | |
223 _wakeupTime += _milliSeconds; | |
224 } else { | |
225 _wakeupTime = new DateTime.now().millisecondsSinceEpoch; | |
226 } | |
203 } | 227 } |
204 | 228 |
205 // Adds a timer to the timer list. Timers with the same wakeup time are | 229 // Adds a timer to the heap or timer list. Timers with the same wakeup time |
206 // enqueued in order and notified in FIFO order. | 230 // are enqueued in order and notified in FIFO order. |
207 bool _addTimerToHeap() { | 231 bool _addTimerToHeap() { |
208 if (_wakeupTime == 0) { | 232 if (_milliSeconds == 0) { |
209 if (_firstZeroTimer == null) { | 233 if (_firstZeroTimer == null) { |
210 _lastZeroTimer = this; | 234 _lastZeroTimer = this; |
211 _firstZeroTimer = this; | 235 _firstZeroTimer = this; |
212 return true; | 236 return true; |
213 } else { | 237 } else { |
214 _lastZeroTimer._indexOrNext = this; | 238 _lastZeroTimer._indexOrNext = this; |
215 _lastZeroTimer = this; | 239 _lastZeroTimer = this; |
216 return false; | 240 return false; |
217 } | 241 } |
218 } else { | 242 } else { |
219 _id = _idCount++; | |
220 _heap.add(this); | 243 _heap.add(this); |
221 return _firstZeroTimer == null && _heap.isFirst(this); | 244 return _firstZeroTimer == null && _heap.isFirst(this); |
222 } | 245 } |
223 } | 246 } |
224 | 247 |
225 | 248 |
226 static void _notifyEventHandler() { | 249 static void _notifyEventHandler() { |
227 if (_handlingCallbacks) { | 250 if (_handlingCallbacks) { |
228 // While we are already handling callbacks we will not notify the event | 251 // While we are already handling callbacks we will not notify the event |
229 // handler. _handleTimeout will call _notifyEventHandler once all pending | 252 // handler. _handleTimeout will call _notifyEventHandler once all pending |
(...skipping 17 matching lines...) Expand all Loading... | |
247 if (_firstZeroTimer != null) { | 270 if (_firstZeroTimer != null) { |
248 _sendPort.send(null); | 271 _sendPort.send(null); |
249 } else { | 272 } else { |
250 _EventHandler._sendData(null, | 273 _EventHandler._sendData(null, |
251 _sendPort, | 274 _sendPort, |
252 _heap.first._wakeupTime); | 275 _heap.first._wakeupTime); |
253 } | 276 } |
254 } | 277 } |
255 } | 278 } |
256 | 279 |
257 static void _handleTimeout(_) { | 280 static void _handleTimeout(pendingImmediateCallback) { |
258 int currentTime = new DateTime.now().millisecondsSinceEpoch; | 281 int currentTime = new DateTime.now().millisecondsSinceEpoch; |
259 // Collect all pending timers. | 282 // Collect all pending timers. |
260 var timer = _firstZeroTimer; | 283 var head = null; |
261 var nextTimer = _lastZeroTimer; | 284 var tail = null; |
262 _firstZeroTimer = null; | 285 // Keep track of the lowest wakeup times for both the list and heap. If |
263 _lastZeroTimer = null; | 286 // the respective queue is empty move its time beyond the current time. |
264 while (_heap.isNotEmpty && _heap.first._wakeupTime <= currentTime) { | 287 var heapTime = _heap.isEmpty ? |
265 var next = _heap.removeFirst(); | 288 (currentTime + 1) : _heap.first._wakeupTime; |
266 if (timer == null) { | 289 var listTime = (_firstZeroTimer == null) ? |
267 nextTimer = next; | 290 (currentTime + 1) : _firstZeroTimer._wakeupTime; |
268 timer = next; | 291 |
292 while ((heapTime <= currentTime) || (listTime <= currentTime)) { | |
293 var timer; | |
294 // Consume the timers in order by removing from heap or list based on | |
295 // their wakeup time and update the queue's time. | |
296 if ((heapTime < listTime) || | |
297 ((heapTime == listTime) && | |
zra
2015/01/07 15:48:03
(heapTime == listTime) implies that both heapTime
Ivan Posva
2015/01/07 20:52:00
Simplified the expression and added an assertion a
| |
298 (_heap.first != null) && (_firstZeroTimer != null) && | |
299 (_heap.first._id < _firstZeroTimer._id))) { | |
300 timer = _heap.removeFirst(); | |
301 heapTime = _heap.isEmpty ? (currentTime + 1) : _heap.first._wakeupTime; | |
269 } else { | 302 } else { |
270 nextTimer._indexOrNext = next; | 303 timer = _firstZeroTimer; |
271 nextTimer = next; | 304 assert(timer._milliSeconds == 0); |
305 _firstZeroTimer = timer._indexOrNext; | |
306 if (_firstZeroTimer == null) { | |
307 _lastZeroTimer = null; | |
308 listTime = currentTime + 1; | |
309 } else { | |
310 // We want to drain all entries from the list as they should have | |
311 // been pending for 0 ms. To prevent issues with current time moving | |
312 // we ensure that the listTime does not go beyond current, unless the | |
313 // list is empty. | |
314 listTime = _firstZeroTimer._wakeupTime; | |
315 if (listTime > currentTime) { | |
316 listTime = currentTime; | |
317 } | |
318 } | |
272 } | 319 } |
320 | |
321 // Append this timer to the pending timer list. | |
322 timer._indexOrNext = null; | |
323 if (head == null) { | |
324 assert(tail == null); | |
325 head = timer; | |
326 tail = timer; | |
327 } else { | |
328 tail._indexOrNext = timer; | |
329 tail = timer; | |
330 } | |
331 } | |
332 | |
333 // If there are no pending timers currently reset the id space before we | |
334 // have a chance to enqueue new timers. | |
335 assert(_firstZeroTimer == null); | |
336 if (_heap.isEmpty) { | |
337 _idCount = 0; | |
273 } | 338 } |
274 | 339 |
275 // Trigger all of the pending timers. New timers added as part of the | 340 // Trigger all of the pending timers. New timers added as part of the |
276 // callbacks will be enqueued now and notified in the next spin at the | 341 // callbacks will be enqueued now and notified in the next spin at the |
277 // earliest. | 342 // earliest. |
278 _handlingCallbacks = true; | 343 _handlingCallbacks = true; |
279 try { | 344 try { |
280 while (timer != null) { | 345 while (head != null) { |
281 var next = timer._indexOrNext; | 346 // Dequeue the first candidate timer. |
347 var timer = head; | |
348 head = timer._indexOrNext; | |
282 timer._indexOrNext = null; | 349 timer._indexOrNext = null; |
350 | |
283 // One of the timers in the pending_timers list can cancel | 351 // One of the timers in the pending_timers list can cancel |
284 // one of the later timers which will set the callback to | 352 // one of the later timers which will set the callback to |
285 // null. | 353 // null. |
286 if (timer._callback != null) { | 354 if (timer._callback != null) { |
287 var callback = timer._callback; | 355 var callback = timer._callback; |
288 if (!timer._repeating) { | 356 if (!timer._repeating) { |
289 // Mark timer as inactive. | 357 // Mark timer as inactive. |
290 timer._callback = null; | 358 timer._callback = null; |
291 } | 359 } |
292 callback(timer); | 360 callback(timer); |
293 // Re-insert repeating timer if not canceled. | 361 // Re-insert repeating timer if not canceled. |
294 if (timer._repeating && timer._callback != null) { | 362 if (timer._repeating && (timer._callback != null)) { |
295 timer._advanceWakeupTime(); | 363 timer._advanceWakeupTime(); |
296 timer._addTimerToHeap(); | 364 timer._addTimerToHeap(); |
297 } | 365 } |
366 // Execute pending micro tasks. | |
367 pendingImmediateCallback(); | |
298 } | 368 } |
299 timer = next; | |
300 } | 369 } |
301 } finally { | 370 } finally { |
302 _handlingCallbacks = false; | 371 _handlingCallbacks = false; |
303 _notifyEventHandler(); | 372 _notifyEventHandler(); |
304 } | 373 } |
305 } | 374 } |
306 | 375 |
307 // Creates a receive port and registers the timer handler on that | 376 // Creates a receive port and registers an empty handler on that port. Just |
308 // receive port. | 377 // the triggering of the event loop will ensure that timers are executed. |
378 static _ignoreMessage(_) => null; | |
379 | |
309 static void _createTimerHandler() { | 380 static void _createTimerHandler() { |
310 if(_receivePort == null) { | 381 assert(_receivePort == null); |
311 _receivePort = new RawReceivePort(_handleTimeout); | 382 _receivePort = new RawReceivePort(_ignoreMessage); |
312 _sendPort = _receivePort.sendPort; | 383 _sendPort = _receivePort.sendPort; |
313 } | |
314 } | 384 } |
315 | 385 |
316 static void _shutdownTimerHandler() { | 386 static void _shutdownTimerHandler() { |
317 _receivePort.close(); | 387 _receivePort.close(); |
318 _receivePort = null; | 388 _receivePort = null; |
319 _sendPort = null; | 389 _sendPort = null; |
320 } | 390 } |
391 | |
392 // The Timer factory registered with the dart:async library by the embedder. | |
393 static Timer factory(int milliSeconds, | |
394 void callback(Timer timer), | |
395 bool repeating) { | |
396 if (repeating) { | |
397 return new _Timer.periodic(milliSeconds, callback); | |
398 } | |
399 return new _Timer(milliSeconds, callback); | |
400 } | |
321 } | 401 } |
322 | 402 |
323 // Provide a closure which will allocate a Timer object to be able to hook | 403 // Provide a closure which will allocate a Timer object to be able to hook |
324 // up the Timer interface in dart:isolate with the implementation here. | 404 // up the Timer interface in dart:isolate with the implementation here. |
325 _getTimerFactoryClosure() { | 405 _getTimerFactoryClosure() { |
326 return (int milliSeconds, void callback(Timer timer), bool repeating) { | 406 runTimerClosure = _Timer._handleTimeout; |
327 if (repeating) { | 407 return _Timer.factory; |
328 return new _Timer.periodic(milliSeconds, callback); | |
329 } | |
330 return new _Timer(milliSeconds, callback); | |
331 }; | |
332 } | 408 } |
333 | 409 |
334 | 410 |
OLD | NEW |