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

Unified Diff: sdk/lib/io/timer_impl.dart

Issue 825403003: Fix http://dartbug.com/21834 (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 5 years, 11 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « runtime/lib/isolate_patch.dart ('k') | tests/lib/mirrors/invocation_fuzz_test.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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;
+}
« no previous file with comments | « runtime/lib/isolate_patch.dart ('k') | tests/lib/mirrors/invocation_fuzz_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698