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

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;
Søren Gjesse 2015/02/27 14:12:56 Additional question: Why are you using 1 and null,
Ivan Posva 2015/03/03 04:51:53 The external event handler is sending nulls for ti
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) {
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 List _queueFromZeroEvent() {
264 var pendingTimers = new List();
265 assert(_firstZeroTimer != null);
266 // Collect pending timers from the timer heap that have an expiration prior
267 // to the currently notified zero timer.
268 var timer;
269 while (!_heap.isEmpty && (_heap.first._compareTo(_firstZeroTimer) < 0)) {
270 timer = _heap.removeFirst();
271 pendingTimers.add(timer);
272 }
273 // Append the first zero timer to the pending timers.
274 timer = _firstZeroTimer;
275 _firstZeroTimer = timer._indexOrNext;
276 timer._indexOrNext = null;
277 pendingTimers.add(timer);
278 return pendingTimers;
279 }
280
281
252 static void _notifyEventHandler() { 282 static void _notifyEventHandler() {
253 if (_handlingCallbacks) { 283 if (_handlingCallbacks) {
254 // While we are already handling callbacks we will not notify the event 284 // While we are already handling callbacks we will not notify the event
255 // handler. _handleTimeout will call _notifyEventHandler once all pending 285 // handler. _handleTimeout will call _notifyEventHandler once all pending
256 // timers are processed. 286 // timers are processed.
257 return; 287 return;
258 } 288 }
259 289
290 // If there are no pending timers. Close down the receive port.
260 if ((_firstZeroTimer == null) && _heap.isEmpty) { 291 if ((_firstZeroTimer == null) && _heap.isEmpty) {
261 // No pending timers: Close the receive port and let the event handler 292 // No pending timers: Close the receive port and let the event handler
262 // know. 293 // know.
263 if (_receivePort != null) { 294 if (_sendPort != null) {
264 VMLibraryHooks.eventHandlerSendData(null, _sendPort, _NO_TIMER); 295 VMLibraryHooks.eventHandlerSendData(null, _sendPort, _NO_TIMER);
265 _shutdownTimerHandler(); 296 _shutdownTimerHandler();
266 } 297 }
267 } else { 298 return;
268 if (_receivePort == null) { 299 } else if (_heap.isEmpty) {
300 // Only zero timers are left. Cancel any scheduled wakeups.
301 VMLibraryHooks.eventHandlerSendData(null, _sendPort, _NO_TIMER);
302 return;
303 }
304
305 // Only send a message if the requested wakeup time differs from the
306 // already scheduled wakeup time.
307 var wakeupTime = _heap.first._wakeupTime;
308 if ((_scheduledWakeupTime == null) ||
309 (wakeupTime != _scheduledWakeupTime)) {
310 if (_sendPort == null) {
269 // Create a receive port and register a message handler for the timer 311 // Create a receive port and register a message handler for the timer
270 // events. 312 // events.
271 _createTimerHandler(); 313 _createTimerHandler();
272 } 314 }
273 if (_firstZeroTimer != null) { 315 VMLibraryHooks.eventHandlerSendData(null, _sendPort, wakeupTime);
274 if (!_messagePending) { 316 _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 } 317 }
287 } 318 }
288 319
289 static void _handleTimeout() { 320 static List _queueFromTimeoutEvent() {
290 // Fast exit if no timers have been scheduled. 321 var pendingTimers = new List();
322 if (_firstZeroTimer != null) {
323 // Collect pending timers from the timer heap that have an expiration
324 // prior to the next zero timer.
325 // By definition the first zero timer has been scheduled before the
326 // current time, meaning all timers which are "less than" the first zero
327 // timer are expired. The first zero timer will be dispatched when its
328 // corresponding message is delivered.
329 var timer;
330 while (!_heap.isEmpty && (_heap.first._compareTo(_firstZeroTimer) < 0)) {
331 timer = _heap.removeFirst();
332 pendingTimers.add(timer);
333 }
334 } else {
335 // Collect pending timers from the timer heap which have expired at this
336 // time.
337 var currentTime = new DateTime.now().millisecondsSinceEpoch;
338 var timer;
339 while (!_heap.isEmpty && (_heap.first._wakeupTime <= currentTime)) {
340 timer = _heap.removeFirst();
341 pendingTimers.add(timer);
342 }
343 }
344 return pendingTimers;
345 }
346
347 static void _runTimers(List pendingTimers) {
348 // If there are no pending timers currently reset the id space before we
349 // have a chance to enqueue new timers.
291 if (_heap.isEmpty && (_firstZeroTimer == null)) { 350 if (_heap.isEmpty && (_firstZeroTimer == null)) {
292 assert(_receivePort == null); 351 _idCount = 0;
293 return;
294 } 352 }
295 353
296 // Collect all pending timers. 354 // Fast exit if no pending timers.
297 var head = null; 355 if (pendingTimers.length == 0) {
298 var tail = null;
299 if (_heap.isEmpty) {
300 // Only immediate timers are scheduled. Take over the whole list as is.
301 assert(_firstZeroTimer != null);
302 assert(_lastZeroTimer != null);
303 head = _firstZeroTimer;
304 tail = _lastZeroTimer;
305 _firstZeroTimer = null;
306 _lastZeroTimer = null;
307 } else {
308 assert(!_heap.isEmpty);
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 if (head == null) {
350 assert(tail == null);
351 head = timer;
352 tail = timer;
353 } else {
354 tail._indexOrNext = timer;
355 tail = timer;
356 }
357 }
358 }
359
360 // No timers queued: Early exit.
361 if (head == null) {
362 return; 356 return;
363 } 357 }
364 358
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 359 // 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 360 // callbacks will be enqueued now and notified in the next spin at the
374 // earliest. 361 // earliest.
375 _handlingCallbacks = true; 362 _handlingCallbacks = true;
376 try { 363 try {
377 while (head != null) { 364 for (var i = 0; i < pendingTimers.length; i++) {
378 // Dequeue the first candidate timer. 365 // Next pending timer.
379 var timer = head; 366 var timer = pendingTimers[i];
380 head = timer._indexOrNext;
381 timer._indexOrNext = null; 367 timer._indexOrNext = null;
382 368
383 // One of the timers in the pending_timers list can cancel 369 // One of the timers in the pending_timers list can cancel
384 // one of the later timers which will set the callback to 370 // one of the later timers which will set the callback to
385 // null. 371 // null.
Søren Gjesse 2015/02/27 12:38:46 Extend this comment to say that all zero timers st
Ivan Posva 2015/03/03 04:51:53 Done.
386 if (timer._callback != null) { 372 if (timer._callback != null) {
387 var callback = timer._callback; 373 var callback = timer._callback;
388 if (!timer._repeating) { 374 if (!timer._repeating) {
389 // Mark timer as inactive. 375 // Mark timer as inactive.
390 timer._callback = null; 376 timer._callback = null;
391 } 377 }
392 callback(timer); 378 callback(timer);
393 // Re-insert repeating timer if not canceled. 379 // Re-insert repeating timer if not canceled.
394 if (timer._repeating && (timer._callback != null)) { 380 if (timer._repeating && (timer._callback != null)) {
395 timer._advanceWakeupTime(); 381 timer._advanceWakeupTime();
396 timer._addTimerToHeap(); 382 timer._enqueue();
397 } 383 }
398 // Execute pending micro tasks. 384 // Execute pending micro tasks.
399 _runPendingImmediateCallback(); 385 _runPendingImmediateCallback();
400 } 386 }
401 } 387 }
402 } finally { 388 } finally {
403 _handlingCallbacks = false; 389 _handlingCallbacks = false;
390 // Notify the event handler or shutdown the port if no more pending
391 // timers are present.
404 _notifyEventHandler(); 392 _notifyEventHandler();
405 } 393 }
406 } 394 }
407 395
408 // Creates a receive port and registers an empty handler on that port. Just 396 // 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. 397 // the triggering of the event loop will ensure that timers are executed.
410 static _ignoreMessage(_) { 398 static void _handleMessage(msg) {
411 _messagePending = false; 399 var pendingTimers;
400 if (msg == _ZERO_EVENT) {
401 pendingTimers = _queueFromZeroEvent();
402 assert(pendingTimers.length > 0);
403 } else {
404 assert(msg == _TIMEOUT_EVENT);
405 pendingTimers = _queueFromTimeoutEvent();
406 }
407 _runTimers(pendingTimers);
412 } 408 }
413 409
414 static void _createTimerHandler() { 410 static void _createTimerHandler() {
415 assert(_receivePort == null); 411 assert(_receivePort == null);
416 _receivePort = new RawReceivePort(_ignoreMessage); 412 assert(_sendPort == null);
413 _receivePort = new RawReceivePort(_handleMessage);
417 _sendPort = _receivePort.sendPort; 414 _sendPort = _receivePort.sendPort;
418 _scheduledWakeupTime = null; 415 _scheduledWakeupTime = null;
419 _messagePending = false;
420 } 416 }
421 417
422 static void _shutdownTimerHandler() { 418 static void _shutdownTimerHandler() {
423 _receivePort.close(); 419 _receivePort.close();
424 _receivePort = null; 420 _receivePort = null;
425 _sendPort = null; 421 _sendPort = null;
426 _scheduledWakeupTime = null; 422 _scheduledWakeupTime = null;
427 _messagePending = false;
428 } 423 }
429 424
430 // The Timer factory registered with the dart:async library by the embedder. 425 // The Timer factory registered with the dart:async library by the embedder.
431 static Timer _factory(int milliSeconds, 426 static Timer _factory(int milliSeconds,
432 void callback(Timer timer), 427 void callback(Timer timer),
433 bool repeating) { 428 bool repeating) {
434 if (repeating) { 429 if (repeating) {
435 return new _Timer.periodic(milliSeconds, callback); 430 return new _Timer.periodic(milliSeconds, callback);
436 } 431 }
437 return new _Timer(milliSeconds, callback); 432 return new _Timer(milliSeconds, callback);
438 } 433 }
439 } 434 }
440 435
441 _setupHooks() { 436 _setupHooks() {
442 VMLibraryHooks.timerFactory = _Timer._factory; 437 VMLibraryHooks.timerFactory = _Timer._factory;
443 } 438 }
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