blob: 4df37cd87748313744415dd28ccdafa474872f30 [file] [log] [blame]
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Timer heap implemented as a array-based binary heap[0].
// This allows for O(1) `first`, O(log(n)) `remove`/`removeFirst` and O(log(n))
// `add`.
//
// To ensure the timers are ordered by insertion time, the _Timer class has a
// `_id` field set when added to the heap.
//
// [0] http://en.wikipedia.org/wiki/Binary_heap
class _TimerHeap {
List<_Timer> _list;
int _used = 0;
_TimerHeap([int initSize = 7])
: _list = new List<_Timer>(initSize);
bool get isEmpty => _used == 0;
_Timer get first => _list[0];
bool isFirst(_Timer timer) => timer._indexOrNext == 0;
void add(_Timer timer) {
if (_used == _list.length) {
_resize();
}
timer._indexOrNext = _used++;
_list[timer._indexOrNext] = timer;
_bubbleUp(timer);
}
_Timer removeFirst() {
var f = first;
remove(f);
return f;
}
void remove(_Timer timer) {
_used--;
if (isEmpty) {
_list[0] = null;
timer._indexOrNext = null;
return;
}
var last = _list[_used];
if (!identical(last, timer)) {
last._indexOrNext = timer._indexOrNext;
_list[last._indexOrNext] = last;
if (last._compareTo(timer) < 0) {
_bubbleUp(last);
} else {
_bubbleDown(last);
}
}
_list[_used] = null;
timer._indexOrNext = null;
}
void _resize() {
var newList = new List(_list.length * 2 + 1);
newList.setRange(0, _used, _list);
_list = newList;
}
void _bubbleUp(_Timer timer) {
while (!isFirst(timer)) {
Timer parent = _parent(timer);
if (timer._compareTo(parent) < 0) {
_swap(timer, parent);
} else {
break;
}
}
}
void _bubbleDown(_Timer timer) {
while (true) {
int leftIndex = _leftChildIndex(timer._indexOrNext);
int rightIndex = _rightChildIndex(timer._indexOrNext);
_Timer newest = timer;
if (leftIndex < _used && _list[leftIndex]._compareTo(newest) < 0) {
newest = _list[leftIndex];
}
if (rightIndex < _used && _list[rightIndex]._compareTo(newest) < 0) {
newest = _list[rightIndex];
}
if (identical(newest, timer)) {
// We are where we should be, break.
break;
}
_swap(newest, timer);
}
}
void _swap(_Timer first, _Timer second) {
int tmp = first._indexOrNext;
first._indexOrNext = second._indexOrNext;
second._indexOrNext = tmp;
_list[first._indexOrNext] = first;
_list[second._indexOrNext] = second;
}
Timer _parent(_Timer timer) => _list[_parentIndex(timer._indexOrNext)];
Timer _leftChild(_Timer timer) => _list[_leftChildIndex(timer._indexOrNext)];
Timer _rightChild(_Timer timer) =>
_list[_rightChildIndex(timer._indexOrNext)];
static int _parentIndex(int index) => (index - 1) ~/ 2;
static int _leftChildIndex(int index) => 2 * index + 1;
static int _rightChildIndex(int index) => 2 * index + 2;
}
class _Timer implements 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;
// Keep track whether at least one message is pending in the event loop. This
// way we do not have to notify for every pending _firstZeroTimer.
static var _messagePending = false;
static bool _handlingCallbacks = false;
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) {
// Negative timeouts are treated as if 0 timeout.
if (milliSeconds < 0) {
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 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();
}
return timer;
}
factory _Timer(int milliSeconds, void callback(Timer timer)) {
return _createTimer(callback, milliSeconds, false);
}
factory _Timer.periodic(int milliSeconds, void callback(Timer timer)) {
return _createTimer(callback, milliSeconds, true);
}
bool get _isInHeap => _indexOrNext is int;
void _clear() {
_callback = null;
}
int _compareTo(_Timer other) {
int c = _wakeupTime - other._wakeupTime;
if (c != 0) return c;
return _id - other._id;
}
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 event handler is notified.
void cancel() {
_clear();
if (!_isInHeap) return;
// Only heap timers are really removed. Others are just dropped on
// notification.
bool update = (_firstZeroTimer == null) && _heap.isFirst(this);
_heap.remove(this);
if (update) {
_notifyEventHandler();
}
}
void _advanceWakeupTime() {
// 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 heap or timer list. Timers with the same wakeup time
// are enqueued in order and notified in FIFO order.
bool _addTimerToHeap() {
if (_milliSeconds == 0) {
if (_firstZeroTimer == null) {
_lastZeroTimer = this;
_firstZeroTimer = this;
return true;
} else {
_lastZeroTimer._indexOrNext = this;
_lastZeroTimer = this;
return false;
}
} else {
_heap.add(this);
return _firstZeroTimer == null && _heap.isFirst(this);
}
}
static void _notifyEventHandler() {
if (_handlingCallbacks) {
// While we are already handling callbacks we will not notify the event
// handler. _handleTimeout will call _notifyEventHandler once all pending
// timers are processed.
return;
}
if ((_firstZeroTimer == null) && _heap.isEmpty) {
// No pending timers: Close the receive port and let the event handler
// know.
if (_receivePort != null) {
VMLibraryHooks.eventHandlerSendData(null, _sendPort, _NO_TIMER);
_shutdownTimerHandler();
}
} else {
if (_receivePort == null) {
// Create a receive port and register a message handler for the timer
// events.
_createTimerHandler();
}
if (_firstZeroTimer != null) {
if (!_messagePending) {
_sendPort.send(null);
_messagePending = true; // Reset when the port receives a message.
}
} else {
var wakeupTime = _heap.first._wakeupTime;
if ((_scheduledWakeupTime == null) ||
(wakeupTime != _scheduledWakeupTime)) {
VMLibraryHooks.eventHandlerSendData(null, _sendPort, wakeupTime);
_scheduledWakeupTime = wakeupTime;
}
}
}
}
static void _handleTimeout() {
// Fast exit if no timers have been scheduled.
if (_heap.isEmpty && (_firstZeroTimer == null)) {
assert(_receivePort == null);
return;
}
// Collect all pending timers.
var head = null;
var tail = null;
if (_heap.isEmpty) {
// Only immediate timers are scheduled. Take over the whole list as is.
assert(_firstZeroTimer != null);
assert(_lastZeroTimer != null);
head = _firstZeroTimer;
tail = _lastZeroTimer;
_firstZeroTimer = null;
_lastZeroTimer = null;
} else {
assert(!_heap.isEmpty);
// 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 currentTime = new DateTime.now().millisecondsSinceEpoch;
var heapTime = _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 {
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 (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.
if (timer._callback != null) {
var callback = timer._callback;
if (!timer._repeating) {
// Mark timer as inactive.
timer._callback = null;
}
callback(timer);
// Re-insert repeating timer if not canceled.
if (timer._repeating && (timer._callback != null)) {
timer._advanceWakeupTime();
timer._addTimerToHeap();
}
// Execute pending micro tasks.
_runPendingImmediateCallback();
}
}
} finally {
_handlingCallbacks = false;
_notifyEventHandler();
}
}
// 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(_) {
_messagePending = false;
}
static void _createTimerHandler() {
assert(_receivePort == null);
_receivePort = new RawReceivePort(_ignoreMessage);
_sendPort = _receivePort.sendPort;
_scheduledWakeupTime = null;
_messagePending = false;
}
static void _shutdownTimerHandler() {
_receivePort.close();
_receivePort = null;
_sendPort = null;
_scheduledWakeupTime = null;
_messagePending = false;
}
// 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);
}
}
_setupHooks() {
VMLibraryHooks.timerFactory = _Timer._factory;
}