| Index: sdk/lib/io/timer_impl.dart
|
| ===================================================================
|
| --- sdk/lib/io/timer_impl.dart (revision 42617)
|
| +++ sdk/lib/io/timer_impl.dart (working copy)
|
| @@ -20,7 +20,6 @@
|
| : _list = new List<_Timer>(initSize);
|
|
|
| bool get isEmpty => _used == 0;
|
| - bool get isNotEmpty => _used > 0;
|
|
|
| _Timer get first => _list[0];
|
|
|
| @@ -43,7 +42,6 @@
|
|
|
| void remove(_Timer timer) {
|
| _used--;
|
| - timer._id = -1;
|
| if (isEmpty) {
|
| _list[0] = null;
|
| timer._indexOrNext = null;
|
| @@ -118,39 +116,63 @@
|
| }
|
|
|
| class _Timer implements Timer {
|
| - // Disables the timer.
|
| + // Cancels the timer in the event handler.
|
| static const int _NO_TIMER = -1;
|
|
|
| // Timers are ordered by wakeup time.
|
| static _TimerHeap _heap = new _TimerHeap();
|
| static _Timer _firstZeroTimer;
|
| static _Timer _lastZeroTimer;
|
| +
|
| + // We use an id to be able to sort timers with the same expiration time.
|
| + // ids are recycled after ID_MASK enqueues or when the timer queue is empty.
|
| + static int _ID_MASK = 0x1fffffff;
|
| static int _idCount = 0;
|
|
|
| static RawReceivePort _receivePort;
|
| static SendPort _sendPort;
|
| + static int _scheduledWakeupTime;
|
| static bool _handlingCallbacks = false;
|
|
|
| - Function _callback;
|
| - int _milliSeconds;
|
| - int _wakeupTime = 0;
|
| - var _indexOrNext;
|
| - int _id = -1;
|
| + Function _callback; // Closure to call when timer fires. null if canceled.
|
| + int _wakeupTime; // Expiration time.
|
| + int _milliSeconds; // Duration specified at creation.
|
| + bool _repeating; // Indicates periodic timers.
|
| + var _indexOrNext; // Index if part of the TimerHeap, link otherwise.
|
| + int _id; // Incrementing id to enable sorting of timers with same expiry.
|
|
|
| + // Get the next available id. We accept collisions and reordering when the
|
| + // _idCount overflows and the timers expire at the same millisecond.
|
| + static int _nextId() {
|
| + var result = _idCount;
|
| + _idCount = (_idCount + 1) & _ID_MASK;
|
| + return result;
|
| + }
|
| +
|
| + _Timer._internal(this._callback,
|
| + this._wakeupTime,
|
| + this._milliSeconds,
|
| + this._repeating) : _id = _nextId();
|
| +
|
| static Timer _createTimer(void callback(Timer timer),
|
| int milliSeconds,
|
| bool repeating) {
|
| - _Timer timer = new _Timer._internal();
|
| - timer._callback = callback;
|
| - if (milliSeconds > 0) {
|
| - // Add one because DateTime.now() is assumed to round down
|
| - // to nearest millisecond, not up, so that time + duration is before
|
| - // duration milliseconds from now. Using micosecond timers like
|
| - // Stopwatch allows detecting that the timer fires early.
|
| - timer._wakeupTime =
|
| - new DateTime.now().millisecondsSinceEpoch + 1 + milliSeconds;
|
| + // Negative timeouts are treated as if 0 timeout.
|
| + if (milliSeconds < 0) {
|
| + milliSeconds = 0;
|
| }
|
| - timer._milliSeconds = repeating ? milliSeconds : -1;
|
| + // Add one because DateTime.now() is assumed to round down
|
| + // to nearest millisecond, not up, so that time + duration is before
|
| + // duration milliseconds from now. Using microsecond timers like
|
| + // Stopwatch allows detecting that the timer fires early.
|
| + int now = new DateTime.now().millisecondsSinceEpoch;
|
| + int wakeupTime = (milliSeconds == 0) ? now : (now + 1 + milliSeconds);
|
| +
|
| + _Timer timer = new _Timer._internal(callback,
|
| + wakeupTime,
|
| + milliSeconds,
|
| + repeating);
|
| +
|
| if (timer._addTimerToHeap()) {
|
| // The new timer is the first in queue. Update event handler.
|
| _notifyEventHandler();
|
| @@ -166,10 +188,8 @@
|
| return _createTimer(callback, milliSeconds, true);
|
| }
|
|
|
| - _Timer._internal() {}
|
| + bool get _isInHeap => _indexOrNext is int;
|
|
|
| - bool get _isInHeap => _id >= 0;
|
| -
|
| void _clear() {
|
| _callback = null;
|
| }
|
| @@ -180,16 +200,15 @@
|
| return _id - other._id;
|
| }
|
|
|
| - bool get _repeating => _milliSeconds >= 0;
|
| -
|
| bool get isActive => _callback != null;
|
|
|
| // Cancels a set timer. The timer is removed from the timer list and if
|
| - // the given timer is the earliest timer the native timer is reset.
|
| + // the given timer is the earliest timer the event handler is notified.
|
| void cancel() {
|
| _clear();
|
| if (!_isInHeap) return;
|
| - assert(_wakeupTime != 0);
|
| + // Only heap timers are really removed. Others are just dropped on
|
| + // notification.
|
| bool update = (_firstZeroTimer == null) && _heap.isFirst(this);
|
| _heap.remove(this);
|
| if (update) {
|
| @@ -198,14 +217,20 @@
|
| }
|
|
|
| void _advanceWakeupTime() {
|
| - assert(_milliSeconds >= 0);
|
| - _wakeupTime += _milliSeconds;
|
| + // Recalculate the next wakeup time. For repeating timers with a 0 timeout
|
| + // the next wakeup time is now.
|
| + _id = _nextId();
|
| + if (_milliSeconds > 0) {
|
| + _wakeupTime += _milliSeconds;
|
| + } else {
|
| + _wakeupTime = new DateTime.now().millisecondsSinceEpoch;
|
| + }
|
| }
|
|
|
| - // Adds a timer to the timer list. Timers with the same wakeup time are
|
| - // enqueued in order and notified in FIFO order.
|
| + // Adds a timer to the heap or timer list. Timers with the same wakeup time
|
| + // are enqueued in order and notified in FIFO order.
|
| bool _addTimerToHeap() {
|
| - if (_wakeupTime == 0) {
|
| + if (_milliSeconds == 0) {
|
| if (_firstZeroTimer == null) {
|
| _lastZeroTimer = this;
|
| _firstZeroTimer = this;
|
| @@ -216,7 +241,6 @@
|
| return false;
|
| }
|
| } else {
|
| - _id = _idCount++;
|
| _heap.add(this);
|
| return _firstZeroTimer == null && _heap.isFirst(this);
|
| }
|
| @@ -247,39 +271,93 @@
|
| if (_firstZeroTimer != null) {
|
| _sendPort.send(null);
|
| } else {
|
| - _EventHandler._sendData(null,
|
| - _sendPort,
|
| - _heap.first._wakeupTime);
|
| + var wakeupTime = _heap.first._wakeupTime;
|
| + if ((_scheduledWakeupTime == null) ||
|
| + (wakeupTime != _scheduledWakeupTime)) {
|
| + _EventHandler._sendData(null, _sendPort, wakeupTime);
|
| + _scheduledWakeupTime = wakeupTime;
|
| + }
|
| }
|
| }
|
| }
|
|
|
| - static void _handleTimeout(_) {
|
| + static void _handleTimeout(pendingImmediateCallback) {
|
| int currentTime = new DateTime.now().millisecondsSinceEpoch;
|
| // Collect all pending timers.
|
| - var timer = _firstZeroTimer;
|
| - var nextTimer = _lastZeroTimer;
|
| - _firstZeroTimer = null;
|
| - _lastZeroTimer = null;
|
| - while (_heap.isNotEmpty && _heap.first._wakeupTime <= currentTime) {
|
| - var next = _heap.removeFirst();
|
| - if (timer == null) {
|
| - nextTimer = next;
|
| - timer = next;
|
| + var head = null;
|
| + var tail = null;
|
| + // Keep track of the lowest wakeup times for both the list and heap. If
|
| + // the respective queue is empty move its time beyond the current time.
|
| + var heapTime = _heap.isEmpty ?
|
| + (currentTime + 1) : _heap.first._wakeupTime;
|
| + var listTime = (_firstZeroTimer == null) ?
|
| + (currentTime + 1) : _firstZeroTimer._wakeupTime;
|
| +
|
| + while ((heapTime <= currentTime) || (listTime <= currentTime)) {
|
| + var timer;
|
| + // Consume the timers in order by removing from heap or list based on
|
| + // their wakeup time and update the queue's time.
|
| + assert((heapTime != listTime) ||
|
| + ((_heap.first != null) && (_firstZeroTimer != null)));
|
| + if ((heapTime < listTime) ||
|
| + ((heapTime == listTime) &&
|
| + (_heap.first._id < _firstZeroTimer._id))) {
|
| + timer = _heap.removeFirst();
|
| + heapTime = _heap.isEmpty ? (currentTime + 1) : _heap.first._wakeupTime;
|
| } else {
|
| - nextTimer._indexOrNext = next;
|
| - nextTimer = next;
|
| + timer = _firstZeroTimer;
|
| + assert(timer._milliSeconds == 0);
|
| + _firstZeroTimer = timer._indexOrNext;
|
| + if (_firstZeroTimer == null) {
|
| + _lastZeroTimer = null;
|
| + listTime = currentTime + 1;
|
| + } else {
|
| + // We want to drain all entries from the list as they should have
|
| + // been pending for 0 ms. To prevent issues with current time moving
|
| + // we ensure that the listTime does not go beyond current, unless the
|
| + // list is empty.
|
| + listTime = _firstZeroTimer._wakeupTime;
|
| + if (listTime > currentTime) {
|
| + listTime = currentTime;
|
| + }
|
| + }
|
| }
|
| +
|
| + // Append this timer to the pending timer list.
|
| + timer._indexOrNext = null;
|
| + if (head == null) {
|
| + assert(tail == null);
|
| + head = timer;
|
| + tail = timer;
|
| + } else {
|
| + tail._indexOrNext = timer;
|
| + tail = timer;
|
| + }
|
| }
|
|
|
| + // No timers queued: Early exit.
|
| + if (head == null) {
|
| + return;
|
| + }
|
| +
|
| + // If there are no pending timers currently reset the id space before we
|
| + // have a chance to enqueue new timers.
|
| + assert(_firstZeroTimer == null);
|
| + if (_heap.isEmpty) {
|
| + _idCount = 0;
|
| + }
|
| +
|
| // Trigger all of the pending timers. New timers added as part of the
|
| // callbacks will be enqueued now and notified in the next spin at the
|
| // earliest.
|
| _handlingCallbacks = true;
|
| try {
|
| - while (timer != null) {
|
| - var next = timer._indexOrNext;
|
| + while (head != null) {
|
| + // Dequeue the first candidate timer.
|
| + var timer = head;
|
| + head = timer._indexOrNext;
|
| timer._indexOrNext = null;
|
| +
|
| // One of the timers in the pending_timers list can cancel
|
| // one of the later timers which will set the callback to
|
| // null.
|
| @@ -291,12 +369,13 @@
|
| }
|
| callback(timer);
|
| // Re-insert repeating timer if not canceled.
|
| - if (timer._repeating && timer._callback != null) {
|
| + if (timer._repeating && (timer._callback != null)) {
|
| timer._advanceWakeupTime();
|
| timer._addTimerToHeap();
|
| }
|
| + // Execute pending micro tasks.
|
| + pendingImmediateCallback();
|
| }
|
| - timer = next;
|
| }
|
| } finally {
|
| _handlingCallbacks = false;
|
| @@ -304,31 +383,38 @@
|
| }
|
| }
|
|
|
| - // Creates a receive port and registers the timer handler on that
|
| - // receive port.
|
| + // Creates a receive port and registers an empty handler on that port. Just
|
| + // the triggering of the event loop will ensure that timers are executed.
|
| + static _ignoreMessage(_) => null;
|
| +
|
| static void _createTimerHandler() {
|
| - if(_receivePort == null) {
|
| - _receivePort = new RawReceivePort(_handleTimeout);
|
| - _sendPort = _receivePort.sendPort;
|
| - }
|
| + assert(_receivePort == null);
|
| + _receivePort = new RawReceivePort(_ignoreMessage);
|
| + _sendPort = _receivePort.sendPort;
|
| + _scheduledWakeupTime = null;
|
| }
|
|
|
| static void _shutdownTimerHandler() {
|
| _receivePort.close();
|
| _receivePort = null;
|
| _sendPort = null;
|
| + _scheduledWakeupTime = null;
|
| }
|
| -}
|
|
|
| -// Provide a closure which will allocate a Timer object to be able to hook
|
| -// up the Timer interface in dart:isolate with the implementation here.
|
| -_getTimerFactoryClosure() {
|
| - return (int milliSeconds, void callback(Timer timer), bool repeating) {
|
| + // The Timer factory registered with the dart:async library by the embedder.
|
| + static Timer _factory(int milliSeconds,
|
| + void callback(Timer timer),
|
| + bool repeating) {
|
| if (repeating) {
|
| return new _Timer.periodic(milliSeconds, callback);
|
| }
|
| return new _Timer(milliSeconds, callback);
|
| - };
|
| + }
|
| }
|
|
|
| -
|
| +// Provide a closure which will allocate a Timer object to be able to hook
|
| +// up the Timer interface in dart:isolate with the implementation here.
|
| +_getTimerFactoryClosure() {
|
| + runTimerClosure = _Timer._handleTimeout;
|
| + return _Timer._factory;
|
| +}
|
|
|