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

Side by Side Diff: runtime/lib/timer_impl.dart

Issue 958123002: Adjust handling of timers: (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 5 years, 9 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « runtime/lib/isolate_patch.dart ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 // Timer heap implemented as a array-based binary heap[0]. 5 // Timer heap implemented as a array-based binary heap[0].
6 // This allows for O(1) `first`, O(log(n)) `remove`/`removeFirst` and O(log(n)) 6 // This allows for O(1) `first`, O(log(n)) `remove`/`removeFirst` and O(log(n))
7 // `add`. 7 // `add`.
8 // 8 //
9 // To ensure the timers are ordered by insertion time, the _Timer class has a 9 // To ensure the timers are ordered by insertion time, the _Timer class has a
10 // `_id` field set when added to the heap. 10 // `_id` field set when added to the heap.
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after
110 110
111 static int _parentIndex(int index) => (index - 1) ~/ 2; 111 static int _parentIndex(int index) => (index - 1) ~/ 2;
112 static int _leftChildIndex(int index) => 2 * index + 1; 112 static int _leftChildIndex(int index) => 2 * index + 1;
113 static int _rightChildIndex(int index) => 2 * index + 2; 113 static int _rightChildIndex(int index) => 2 * index + 2;
114 } 114 }
115 115
116 class _Timer implements Timer { 116 class _Timer implements Timer {
117 // Cancels the timer in the event handler. 117 // Cancels the timer in the event handler.
118 static const int _NO_TIMER = -1; 118 static const int _NO_TIMER = -1;
119 119
120 // Timers are ordered by wakeup time. 120 // We distinguish what kind of message arrived based on the value being sent.
121 static const _ZERO_EVENT = 1;
122 static const _TIMEOUT_EVENT = null;
123
124 // Timers are ordered by wakeup time. Timers with a timeout value of > 0 do
125 // end up on the TimerHeap. Timers with a timeout of 0 are queued in a list.
121 static _TimerHeap _heap = new _TimerHeap(); 126 static _TimerHeap _heap = new _TimerHeap();
122 static _Timer _firstZeroTimer; 127 static _Timer _firstZeroTimer;
123 static _Timer _lastZeroTimer; 128 static _Timer _lastZeroTimer;
124 129
125 // We use an id to be able to sort timers with the same expiration time. 130 // We use an id to be able to sort timers with the same expiration time.
126 // ids are recycled after ID_MASK enqueues or when the timer queue is empty. 131 // ids are recycled after ID_MASK enqueues or when the timer queue is empty.
127 static int _ID_MASK = 0x1fffffff; 132 static int _ID_MASK = 0x1fffffff;
128 static int _idCount = 0; 133 static int _idCount = 0;
129 134
130 static RawReceivePort _receivePort; 135 static RawReceivePort _receivePort;
131 static SendPort _sendPort; 136 static SendPort _sendPort;
132 static int _scheduledWakeupTime; 137 static int _scheduledWakeupTime;
133 // Keep track whether at least one message is pending in the event loop. This
134 // way we do not have to notify for every pending _firstZeroTimer.
135 static var _messagePending = false;
136 138
137 static bool _handlingCallbacks = false; 139 static bool _handlingCallbacks = false;
138 140
139 Function _callback; // Closure to call when timer fires. null if canceled. 141 Function _callback; // Closure to call when timer fires. null if canceled.
140 int _wakeupTime; // Expiration time. 142 int _wakeupTime; // Expiration time.
141 int _milliSeconds; // Duration specified at creation. 143 int _milliSeconds; // Duration specified at creation.
142 bool _repeating; // Indicates periodic timers. 144 bool _repeating; // Indicates periodic timers.
143 var _indexOrNext; // Index if part of the TimerHeap, link otherwise. 145 var _indexOrNext; // Index if part of the TimerHeap, link otherwise.
144 int _id; // Incrementing id to enable sorting of timers with same expiry. 146 int _id; // Incrementing id to enable sorting of timers with same expiry.
145 147
(...skipping 21 matching lines...) Expand all
167 // to nearest millisecond, not up, so that time + duration is before 169 // to nearest millisecond, not up, so that time + duration is before
168 // duration milliseconds from now. Using microsecond timers like 170 // duration milliseconds from now. Using microsecond timers like
169 // Stopwatch allows detecting that the timer fires early. 171 // Stopwatch allows detecting that the timer fires early.
170 int now = new DateTime.now().millisecondsSinceEpoch; 172 int now = new DateTime.now().millisecondsSinceEpoch;
171 int wakeupTime = (milliSeconds == 0) ? now : (now + 1 + milliSeconds); 173 int wakeupTime = (milliSeconds == 0) ? now : (now + 1 + milliSeconds);
172 174
173 _Timer timer = new _Timer._internal(callback, 175 _Timer timer = new _Timer._internal(callback,
174 wakeupTime, 176 wakeupTime,
175 milliSeconds, 177 milliSeconds,
176 repeating); 178 repeating);
177 179 // Enqueue this newly created timer in the appropriate structure and
178 if (timer._addTimerToHeap()) { 180 // notify if necessary.
179 // The new timer is the first in queue. Update event handler. 181 timer._enqueue();
180 _notifyEventHandler();
181 }
182 return timer; 182 return timer;
183 } 183 }
184 184
185 factory _Timer(int milliSeconds, void callback(Timer timer)) { 185 factory _Timer(int milliSeconds, void callback(Timer timer)) {
186 return _createTimer(callback, milliSeconds, false); 186 return _createTimer(callback, milliSeconds, false);
187 } 187 }
188 188
189 factory _Timer.periodic(int milliSeconds, void callback(Timer timer)) { 189 factory _Timer.periodic(int milliSeconds, void callback(Timer timer)) {
190 return _createTimer(callback, milliSeconds, true); 190 return _createTimer(callback, milliSeconds, true);
191 } 191 }
192 192
193 bool get _isInHeap => _indexOrNext is int; 193 bool get _isInHeap => _indexOrNext is int;
194 194
195 void _clear() {
196 _callback = null;
197 }
198
199 int _compareTo(_Timer other) { 195 int _compareTo(_Timer other) {
200 int c = _wakeupTime - other._wakeupTime; 196 int c = _wakeupTime - other._wakeupTime;
201 if (c != 0) return c; 197 if (c != 0) return c;
202 return _id - other._id; 198 return _id - other._id;
203 } 199 }
204 200
205 bool get isActive => _callback != null; 201 bool get isActive => _callback != null;
206 202
207 // Cancels a set timer. The timer is removed from the timer list and if 203 // Cancels a set timer. The timer is removed from the timer heap if it is a
208 // the given timer is the earliest timer the event handler is notified. 204 // non-zero timer. Zero timers are kept in the list as they need to consume
205 // the corresponding pending message.
209 void cancel() { 206 void cancel() {
210 _clear(); 207 _callback = null;
208 // Only heap timers are really removed. Zero timers need to consume their
209 // corresponding wakeup message so they are left in the queue.
211 if (!_isInHeap) return; 210 if (!_isInHeap) return;
212 // Only heap timers are really removed. Others are just dropped on 211 bool update = _heap.isFirst(this);
213 // notification.
214 bool update = (_firstZeroTimer == null) && _heap.isFirst(this);
215 _heap.remove(this); 212 _heap.remove(this);
216 if (update) { 213 if (update) {
217 _notifyEventHandler(); 214 _notifyEventHandler();
218 } 215 }
219 } 216 }
220 217
221 void _advanceWakeupTime() { 218 void _advanceWakeupTime() {
222 // Recalculate the next wakeup time. For repeating timers with a 0 timeout 219 // Recalculate the next wakeup time. For repeating timers with a 0 timeout
223 // the next wakeup time is now. 220 // the next wakeup time is now.
224 _id = _nextId(); 221 _id = _nextId();
225 if (_milliSeconds > 0) { 222 if (_milliSeconds > 0) {
226 _wakeupTime += _milliSeconds; 223 _wakeupTime += _milliSeconds;
227 } else { 224 } else {
228 _wakeupTime = new DateTime.now().millisecondsSinceEpoch; 225 _wakeupTime = new DateTime.now().millisecondsSinceEpoch;
229 } 226 }
230 } 227 }
231 228
232 // Adds a timer to the heap or timer list. Timers with the same wakeup time 229 // Adds a timer to the heap or timer list. Timers with the same wakeup time
233 // are enqueued in order and notified in FIFO order. 230 // are enqueued in order and notified in FIFO order.
234 bool _addTimerToHeap() { 231 void _enqueue() {
235 if (_milliSeconds == 0) { 232 if (_milliSeconds == 0) {
236 if (_firstZeroTimer == null) { 233 if (_firstZeroTimer == null) {
zra 2015/02/26 19:04:35 Code like the following 6 lines is repeated ~5 tim
Ivan Posva 2015/02/27 11:47:41 Left the zero timer queueing alone, but as discuss
237 _lastZeroTimer = this; 234 _lastZeroTimer = this;
238 _firstZeroTimer = this; 235 _firstZeroTimer = this;
239 return true;
240 } else { 236 } else {
241 _lastZeroTimer._indexOrNext = this; 237 _lastZeroTimer._indexOrNext = this;
242 _lastZeroTimer = this; 238 _lastZeroTimer = this;
243 return false;
244 } 239 }
240 // Every zero timer gets its own event.
241 _notifyZeroHandler();
245 } else { 242 } else {
246 _heap.add(this); 243 _heap.add(this);
247 return _firstZeroTimer == null && _heap.isFirst(this); 244 if (_heap.isFirst(this)) {
245 _notifyEventHandler();
246 }
248 } 247 }
249 } 248 }
250 249
251 250
251 // Enqeue one message for each zero timer. To be able to distinguish from
252 // EventHandler messages we send a _ZERO_EVENT instead of a _TIMEOUT_EVENT.
253 static void _notifyZeroHandler() {
254 if (_sendPort == null) {
255 _createTimerHandler();
256 }
257 _sendPort.send(_ZERO_EVENT);
258 }
259
260
261 // Handle the notification of a zero timer. Make sure to also execute non-zero
262 // timers with a lower expiration time.
263 static Timer _queueFromZeroEvent() {
264 assert(_firstZeroTimer != null);
265 // Collect pending timers from the timer heap that have an expiration prior
266 // to the currently notified zero timer.
267 var head = null;
268 var tail = null;
269 var timer;
270 while (!_heap.isEmpty && (_heap.first._compareTo(_firstZeroTimer) < 0)) {
271 timer = _heap.removeFirst();
272 timer._indexOrNext = null;
273 if (head == null) {
274 assert(tail == null);
275 head = timer;
276 tail = timer;
277 } else {
278 tail._indexOrNext = timer;
279 tail = timer;
280 }
281 }
282 // Append the first zero timer to the pending timers.
283 timer = _firstZeroTimer;
284 _firstZeroTimer = timer._indexOrNext;
285 timer._indexOrNext = null;
286 if (head == null) {
287 assert(tail == null);
288 head = timer;
289 tail = timer;
290 } else {
291 tail._indexOrNext = timer;
292 tail = timer;
293 }
294 return head;
295 }
296
297
252 static void _notifyEventHandler() { 298 static void _notifyEventHandler() {
253 if (_handlingCallbacks) { 299 if (_handlingCallbacks) {
254 // While we are already handling callbacks we will not notify the event 300 // While we are already handling callbacks we will not notify the event
255 // handler. _handleTimeout will call _notifyEventHandler once all pending 301 // handler. _handleTimeout will call _notifyEventHandler once all pending
256 // timers are processed. 302 // timers are processed.
257 return; 303 return;
258 } 304 }
259 305
306 // If there are no pending timers. Close down the receive port.
260 if ((_firstZeroTimer == null) && _heap.isEmpty) { 307 if ((_firstZeroTimer == null) && _heap.isEmpty) {
261 // No pending timers: Close the receive port and let the event handler 308 // No pending timers: Close the receive port and let the event handler
262 // know. 309 // know.
263 if (_receivePort != null) { 310 if (_sendPort != null) {
264 VMLibraryHooks.eventHandlerSendData(null, _sendPort, _NO_TIMER); 311 VMLibraryHooks.eventHandlerSendData(null, _sendPort, _NO_TIMER);
265 _shutdownTimerHandler(); 312 _shutdownTimerHandler();
266 } 313 }
267 } else { 314 return;
268 if (_receivePort == null) { 315 } else if (_heap.isEmpty) {
316 // Only zero timers are left. Cancel any scheduled wakeups.
317 VMLibraryHooks.eventHandlerSendData(null, _sendPort, _NO_TIMER);
318 return;
319 }
320
321 // Only send a message if the requested wakeup time differs from the
322 // already scheduled wakeup time.
323 var wakeupTime = _heap.first._wakeupTime;
324 if ((_scheduledWakeupTime == null) ||
325 (wakeupTime != _scheduledWakeupTime)) {
326 if (_sendPort == null) {
269 // Create a receive port and register a message handler for the timer 327 // Create a receive port and register a message handler for the timer
270 // events. 328 // events.
271 _createTimerHandler(); 329 _createTimerHandler();
272 } 330 }
273 if (_firstZeroTimer != null) { 331 VMLibraryHooks.eventHandlerSendData(null, _sendPort, wakeupTime);
274 if (!_messagePending) { 332 _scheduledWakeupTime = wakeupTime;
275 _sendPort.send(null);
276 _messagePending = true; // Reset when the port receives a message.
277 }
278 } else {
279 var wakeupTime = _heap.first._wakeupTime;
280 if ((_scheduledWakeupTime == null) ||
281 (wakeupTime != _scheduledWakeupTime)) {
282 VMLibraryHooks.eventHandlerSendData(null, _sendPort, wakeupTime);
283 _scheduledWakeupTime = wakeupTime;
284 }
285 }
286 } 333 }
287 } 334 }
288 335
289 static void _handleTimeout() { 336 static Timer _queueFromTimeoutEvent() {
290 // Fast exit if no timers have been scheduled.
291 if (_heap.isEmpty && (_firstZeroTimer == null)) {
292 assert(_receivePort == null);
293 return;
294 }
295
296 // Collect all pending timers.
297 var head = null; 337 var head = null;
298 var tail = null; 338 if (_firstZeroTimer != null) {
299 if (_heap.isEmpty) { 339 // Collect pending timers from the timer heap that have an expiration
300 // Only immediate timers are scheduled. Take over the whole list as is. 340 // prior to the next zero timer.
301 assert(_firstZeroTimer != null); 341 // By definition the first zero timer has been scheduled before the
302 assert(_lastZeroTimer != null); 342 // current time, meaning all timers which are "less than" the first zero
303 head = _firstZeroTimer; 343 // timer are expired. The first zero timer will be dispatched when its
304 tail = _lastZeroTimer; 344 // corresponding message is delivered.
305 _firstZeroTimer = null; 345 var tail = null;
306 _lastZeroTimer = null; 346 var timer;
307 } else { 347 while (!_heap.isEmpty && (_heap.first._compareTo(_firstZeroTimer) < 0)) {
308 assert(!_heap.isEmpty); 348 timer = _heap.removeFirst();
309 // Keep track of the lowest wakeup times for both the list and heap. If
310 // the respective queue is empty move its time beyond the current time.
311 var currentTime = new DateTime.now().millisecondsSinceEpoch;
312 var heapTime = _heap.first._wakeupTime;
313 var listTime = (_firstZeroTimer == null) ?
314 (currentTime + 1) : _firstZeroTimer._wakeupTime;
315
316 while ((heapTime <= currentTime) || (listTime <= currentTime)) {
317 var timer;
318 // Consume the timers in order by removing from heap or list based on
319 // their wakeup time and update the queue's time.
320 assert((heapTime != listTime) ||
321 ((_heap.first != null) && (_firstZeroTimer != null)));
322 if ((heapTime < listTime) ||
323 ((heapTime == listTime) &&
324 (_heap.first._id < _firstZeroTimer._id))) {
325 timer = _heap.removeFirst();
326 heapTime = _heap.isEmpty ?
327 (currentTime + 1) : _heap.first._wakeupTime;
328 } else {
329 timer = _firstZeroTimer;
330 assert(timer._milliSeconds == 0);
331 _firstZeroTimer = timer._indexOrNext;
332 if (_firstZeroTimer == null) {
333 _lastZeroTimer = null;
334 listTime = currentTime + 1;
335 } else {
336 // We want to drain all entries from the list as they should have
337 // been pending for 0 ms. To prevent issues with current time moving
338 // we ensure that the listTime does not go beyond current, unless
339 // the list is empty.
340 listTime = _firstZeroTimer._wakeupTime;
341 if (listTime > currentTime) {
342 listTime = currentTime;
343 }
344 }
345 }
346
347 // Append this timer to the pending timer list.
348 timer._indexOrNext = null; 349 timer._indexOrNext = null;
349 if (head == null) { 350 if (head == null) {
350 assert(tail == null); 351 assert(tail == null);
352 head = timer;
353 tail = timer;
354 } else {
355 tail._indexOrNext = timer;
356 tail = timer;
357 }
358 }
359 } else {
360 // Collect pending timers from the timer heap which have expired at this
361 // time.
362 var currentTime = new DateTime.now().millisecondsSinceEpoch;
363 var tail = null;
364 var timer;
365 while (!_heap.isEmpty && (_heap.first._wakeupTime <= currentTime)) {
366 timer = _heap.removeFirst();
367 timer._indexOrNext = null;
368 if (head == null) {
369 assert(tail == null);
351 head = timer; 370 head = timer;
352 tail = timer; 371 tail = timer;
353 } else { 372 } else {
354 tail._indexOrNext = timer; 373 tail._indexOrNext = timer;
355 tail = timer; 374 tail = timer;
356 } 375 }
357 } 376 }
358 } 377 }
378 return head;
379 }
359 380
360 // No timers queued: Early exit. 381 static void _runTimers(Timer head) {
382 // If there are no pending timers currently reset the id space before we
383 // have a chance to enqueue new timers.
384 if (_heap.isEmpty && (_firstZeroTimer == null)) {
385 _idCount = 0;
386 }
387
388 // Fast exit if no pending timers.
361 if (head == null) { 389 if (head == null) {
362 return; 390 return;
363 } 391 }
364 392
365 // If there are no pending timers currently reset the id space before we
366 // have a chance to enqueue new timers.
367 assert(_firstZeroTimer == null);
368 if (_heap.isEmpty) {
369 _idCount = 0;
370 }
371
372 // Trigger all of the pending timers. New timers added as part of the 393 // Trigger all of the pending timers. New timers added as part of the
373 // callbacks will be enqueued now and notified in the next spin at the 394 // callbacks will be enqueued now and notified in the next spin at the
374 // earliest. 395 // earliest.
375 _handlingCallbacks = true; 396 _handlingCallbacks = true;
376 try { 397 try {
377 while (head != null) { 398 while (head != null) {
378 // Dequeue the first candidate timer. 399 // Dequeue the first candidate timer.
379 var timer = head; 400 var timer = head;
380 head = timer._indexOrNext; 401 head = timer._indexOrNext;
381 timer._indexOrNext = null; 402 timer._indexOrNext = null;
382 403
383 // One of the timers in the pending_timers list can cancel 404 // One of the timers in the pending_timers list can cancel
384 // one of the later timers which will set the callback to 405 // one of the later timers which will set the callback to
385 // null. 406 // null.
386 if (timer._callback != null) { 407 if (timer._callback != null) {
387 var callback = timer._callback; 408 var callback = timer._callback;
388 if (!timer._repeating) { 409 if (!timer._repeating) {
389 // Mark timer as inactive. 410 // Mark timer as inactive.
390 timer._callback = null; 411 timer._callback = null;
391 } 412 }
392 callback(timer); 413 callback(timer);
393 // Re-insert repeating timer if not canceled. 414 // Re-insert repeating timer if not canceled.
394 if (timer._repeating && (timer._callback != null)) { 415 if (timer._repeating && (timer._callback != null)) {
395 timer._advanceWakeupTime(); 416 timer._advanceWakeupTime();
396 timer._addTimerToHeap(); 417 timer._enqueue();
397 } 418 }
398 // Execute pending micro tasks. 419 // Execute pending micro tasks.
399 _runPendingImmediateCallback(); 420 _runPendingImmediateCallback();
400 } 421 }
401 } 422 }
402 } finally { 423 } finally {
403 _handlingCallbacks = false; 424 _handlingCallbacks = false;
425 // Notify the event handler or shutdown the port if no more pending
426 // timers are present.
404 _notifyEventHandler(); 427 _notifyEventHandler();
405 } 428 }
406 } 429 }
407 430
408 // Creates a receive port and registers an empty handler on that port. Just 431 // Creates a receive port and registers an empty handler on that port. Just
409 // the triggering of the event loop will ensure that timers are executed. 432 // the triggering of the event loop will ensure that timers are executed.
410 static _ignoreMessage(_) { 433 static void _handleMessage(msg) {
411 _messagePending = false; 434 var timers;
435 if (msg == _ZERO_EVENT) {
436 timers = _queueFromZeroEvent();
437 } else {
438 assert(msg == _TIMEOUT_EVENT);
439 timers = _queueFromTimeoutEvent();
440 }
441 _runTimers(timers);
412 } 442 }
413 443
414 static void _createTimerHandler() { 444 static void _createTimerHandler() {
415 assert(_receivePort == null); 445 assert(_receivePort == null);
416 _receivePort = new RawReceivePort(_ignoreMessage); 446 assert(_sendPort == null);
447 _receivePort = new RawReceivePort(_handleMessage);
417 _sendPort = _receivePort.sendPort; 448 _sendPort = _receivePort.sendPort;
418 _scheduledWakeupTime = null; 449 _scheduledWakeupTime = null;
419 _messagePending = false;
420 } 450 }
421 451
422 static void _shutdownTimerHandler() { 452 static void _shutdownTimerHandler() {
423 _receivePort.close(); 453 _receivePort.close();
424 _receivePort = null; 454 _receivePort = null;
425 _sendPort = null; 455 _sendPort = null;
426 _scheduledWakeupTime = null; 456 _scheduledWakeupTime = null;
427 _messagePending = false;
428 } 457 }
429 458
430 // The Timer factory registered with the dart:async library by the embedder. 459 // The Timer factory registered with the dart:async library by the embedder.
431 static Timer _factory(int milliSeconds, 460 static Timer _factory(int milliSeconds,
432 void callback(Timer timer), 461 void callback(Timer timer),
433 bool repeating) { 462 bool repeating) {
434 if (repeating) { 463 if (repeating) {
435 return new _Timer.periodic(milliSeconds, callback); 464 return new _Timer.periodic(milliSeconds, callback);
436 } 465 }
437 return new _Timer(milliSeconds, callback); 466 return new _Timer(milliSeconds, callback);
438 } 467 }
439 } 468 }
440 469
441 _setupHooks() { 470 _setupHooks() {
442 VMLibraryHooks.timerFactory = _Timer._factory; 471 VMLibraryHooks.timerFactory = _Timer._factory;
443 } 472 }
OLDNEW
« no previous file with comments | « runtime/lib/isolate_patch.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698