mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
![Liam Appelbe](/assets/img/avatar_default.png)
This seems to fix the issue, but it's a flaky crash so it's hard to 100% verify. Bug: https://github.com/dart-lang/sdk/issues/33838 Bug: https://github.com/flutter/devtools/issues/337 Change-Id: Ic037a94cfb9fe7da319d7d4b20e3a3efd0c7289f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/97541 Reviewed-by: Ryan Macnak <rmacnak@google.com> Commit-Queue: Liam Appelbe <liama@google.com>
470 lines
15 KiB
Dart
470 lines
15 KiB
Dart
// 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.
|
|
|
|
// part of "isolate_patch.dart";
|
|
|
|
// 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<_Timer>(_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 _NO_TIMER = -1;
|
|
|
|
// We distinguish what kind of message arrived based on the value being sent.
|
|
static const _ZERO_EVENT = 1;
|
|
static const _TIMEOUT_EVENT = null;
|
|
|
|
// Timers are ordered by wakeup time. Timers with a timeout value of > 0 do
|
|
// end up on the TimerHeap. Timers with a timeout of 0 are queued in a list.
|
|
static final _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 const _ID_MASK = 0x1fffffff;
|
|
static int _idCount = 0;
|
|
|
|
static RawReceivePort _receivePort;
|
|
static SendPort _sendPort;
|
|
static int _scheduledWakeupTime;
|
|
|
|
static bool _handlingCallbacks = false;
|
|
|
|
Function _callback; // Closure to call when timer fires. null if canceled.
|
|
int _wakeupTime; // Expiration time.
|
|
final int _milliSeconds; // Duration specified at creation.
|
|
final 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.
|
|
|
|
int _tick = 0; // Backing for [tick],
|
|
|
|
// 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 = VMLibraryHooks.timerMillisecondClock();
|
|
int wakeupTime = (milliSeconds == 0) ? now : (now + 1 + milliSeconds);
|
|
|
|
_Timer timer =
|
|
new _Timer._internal(callback, wakeupTime, milliSeconds, repeating);
|
|
// Enqueue this newly created timer in the appropriate structure and
|
|
// notify if necessary.
|
|
timer._enqueue();
|
|
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;
|
|
|
|
int _compareTo(_Timer other) {
|
|
int c = _wakeupTime - other._wakeupTime;
|
|
if (c != 0) return c;
|
|
return _id - other._id;
|
|
}
|
|
|
|
bool get isActive => _callback != null;
|
|
|
|
int get tick => _tick;
|
|
|
|
// Cancels a set timer. The timer is removed from the timer heap if it is a
|
|
// non-zero timer. Zero timers are kept in the list as they need to consume
|
|
// the corresponding pending message.
|
|
void cancel() {
|
|
_callback = null;
|
|
// Only heap timers are really removed. Zero timers need to consume their
|
|
// corresponding wakeup message so they are left in the queue.
|
|
if (!_isInHeap) return;
|
|
bool update = _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 = VMLibraryHooks.timerMillisecondClock();
|
|
}
|
|
}
|
|
|
|
// Adds a timer to the heap or timer list. Timers with the same wakeup time
|
|
// are enqueued in order and notified in FIFO order.
|
|
void _enqueue() {
|
|
if (_milliSeconds == 0) {
|
|
if (_firstZeroTimer == null) {
|
|
_lastZeroTimer = this;
|
|
_firstZeroTimer = this;
|
|
} else {
|
|
_lastZeroTimer._indexOrNext = this;
|
|
_lastZeroTimer = this;
|
|
}
|
|
// Every zero timer gets its own event.
|
|
_notifyZeroHandler();
|
|
} else {
|
|
_heap.add(this);
|
|
if (_heap.isFirst(this)) {
|
|
_notifyEventHandler();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enqueue one message for each zero timer. To be able to distinguish from
|
|
// EventHandler messages we send a _ZERO_EVENT instead of a _TIMEOUT_EVENT.
|
|
static void _notifyZeroHandler() {
|
|
if (_sendPort == null) {
|
|
_createTimerHandler();
|
|
}
|
|
_sendPort.send(_ZERO_EVENT);
|
|
}
|
|
|
|
// Handle the notification of a zero timer. Make sure to also execute non-zero
|
|
// timers with a lower expiration time.
|
|
static List _queueFromZeroEvent() {
|
|
var pendingTimers = new List();
|
|
assert(_firstZeroTimer != null);
|
|
// Collect pending timers from the timer heap that have an expiration prior
|
|
// to the currently notified zero timer.
|
|
var timer;
|
|
while (!_heap.isEmpty && (_heap.first._compareTo(_firstZeroTimer) < 0)) {
|
|
timer = _heap.removeFirst();
|
|
pendingTimers.add(timer);
|
|
}
|
|
// Append the first zero timer to the pending timers.
|
|
timer = _firstZeroTimer;
|
|
_firstZeroTimer = timer._indexOrNext;
|
|
timer._indexOrNext = null;
|
|
pendingTimers.add(timer);
|
|
return pendingTimers;
|
|
}
|
|
|
|
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 there are no pending timers. Close down the receive port.
|
|
if ((_firstZeroTimer == null) && _heap.isEmpty) {
|
|
// No pending timers: Close the receive port and let the event handler
|
|
// know.
|
|
if (_sendPort != null) {
|
|
_cancelWakeup();
|
|
_shutdownTimerHandler();
|
|
}
|
|
return;
|
|
} else if (_heap.isEmpty) {
|
|
// Only zero timers are left. Cancel any scheduled wakeups.
|
|
_cancelWakeup();
|
|
return;
|
|
}
|
|
|
|
// Only send a message if the requested wakeup time differs from the
|
|
// already scheduled wakeup time.
|
|
var wakeupTime = _heap.first._wakeupTime;
|
|
if ((_scheduledWakeupTime == null) ||
|
|
(wakeupTime != _scheduledWakeupTime)) {
|
|
_scheduleWakeup(wakeupTime);
|
|
}
|
|
}
|
|
|
|
static List _queueFromTimeoutEvent() {
|
|
var pendingTimers = new List();
|
|
if (_firstZeroTimer != null) {
|
|
// Collect pending timers from the timer heap that have an expiration
|
|
// prior to the next zero timer.
|
|
// By definition the first zero timer has been scheduled before the
|
|
// current time, meaning all timers which are "less than" the first zero
|
|
// timer are expired. The first zero timer will be dispatched when its
|
|
// corresponding message is delivered.
|
|
var timer;
|
|
while (!_heap.isEmpty && (_heap.first._compareTo(_firstZeroTimer) < 0)) {
|
|
timer = _heap.removeFirst();
|
|
pendingTimers.add(timer);
|
|
}
|
|
} else {
|
|
// Collect pending timers from the timer heap which have expired at this
|
|
// time.
|
|
var currentTime = VMLibraryHooks.timerMillisecondClock();
|
|
var timer;
|
|
while (!_heap.isEmpty && (_heap.first._wakeupTime <= currentTime)) {
|
|
timer = _heap.removeFirst();
|
|
pendingTimers.add(timer);
|
|
}
|
|
}
|
|
return pendingTimers;
|
|
}
|
|
|
|
static void _runTimers(List pendingTimers) {
|
|
// If there are no pending timers currently reset the id space before we
|
|
// have a chance to enqueue new timers.
|
|
if (_heap.isEmpty && (_firstZeroTimer == null)) {
|
|
_idCount = 0;
|
|
}
|
|
|
|
// Fast exit if no pending timers.
|
|
if (pendingTimers.length == 0) {
|
|
return;
|
|
}
|
|
|
|
// 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;
|
|
var i = 0;
|
|
try {
|
|
for (; i < pendingTimers.length; i++) {
|
|
// Next pending timer.
|
|
var timer = pendingTimers[i];
|
|
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. Or the pending zero timer has been canceled earlier.
|
|
if (timer._callback != null) {
|
|
var callback = timer._callback;
|
|
if (!timer._repeating) {
|
|
// Mark timer as inactive.
|
|
timer._callback = null;
|
|
} else if (timer._milliSeconds > 0) {
|
|
var ms = timer._milliSeconds;
|
|
int overdue =
|
|
VMLibraryHooks.timerMillisecondClock() - timer._wakeupTime;
|
|
if (overdue > ms) {
|
|
int missedTicks = overdue ~/ ms;
|
|
timer._wakeupTime += missedTicks * ms;
|
|
timer._tick += missedTicks;
|
|
}
|
|
}
|
|
timer._tick += 1;
|
|
|
|
callback(timer);
|
|
// Re-insert repeating timer if not canceled.
|
|
if (timer._repeating && (timer._callback != null)) {
|
|
timer._advanceWakeupTime();
|
|
timer._enqueue();
|
|
}
|
|
// Execute pending micro tasks.
|
|
var immediateCallback = _removePendingImmediateCallback();
|
|
if (immediateCallback != null) {
|
|
immediateCallback();
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
_handlingCallbacks = false;
|
|
// Re-queue timers we didn't get to.
|
|
for (i++; i < pendingTimers.length; i++) {
|
|
var timer = pendingTimers[i];
|
|
timer._enqueue();
|
|
}
|
|
_notifyEventHandler();
|
|
}
|
|
}
|
|
|
|
static void _handleMessage(msg) {
|
|
var pendingTimers;
|
|
if (msg == _ZERO_EVENT) {
|
|
pendingTimers = _queueFromZeroEvent();
|
|
assert(pendingTimers.length > 0);
|
|
} else {
|
|
assert(msg == _TIMEOUT_EVENT);
|
|
_scheduledWakeupTime = null; // Consumed the last scheduled wakeup now.
|
|
pendingTimers = _queueFromTimeoutEvent();
|
|
}
|
|
_runTimers(pendingTimers);
|
|
// Notify the event handler or shutdown the port if no more pending
|
|
// timers are present.
|
|
_notifyEventHandler();
|
|
}
|
|
|
|
// Tell the event handler to wake this isolate at a specific time.
|
|
static void _scheduleWakeup(int wakeupTime) {
|
|
if (_sendPort == null) {
|
|
_createTimerHandler();
|
|
}
|
|
VMLibraryHooks.eventHandlerSendData(null, _sendPort, wakeupTime);
|
|
_scheduledWakeupTime = wakeupTime;
|
|
}
|
|
|
|
// Cancel pending wakeups in the event handler.
|
|
static void _cancelWakeup() {
|
|
if (_sendPort != null) {
|
|
VMLibraryHooks.eventHandlerSendData(null, _sendPort, _NO_TIMER);
|
|
_scheduledWakeupTime = null;
|
|
}
|
|
}
|
|
|
|
// Create a receive port and register a message handler for the timer
|
|
// events.
|
|
static void _createTimerHandler() {
|
|
assert(_receivePort == null);
|
|
assert(_sendPort == null);
|
|
_receivePort = new RawReceivePort(_handleMessage);
|
|
_sendPort = _receivePort.sendPort;
|
|
_scheduledWakeupTime = null;
|
|
}
|
|
|
|
static void _shutdownTimerHandler() {
|
|
_receivePort.close();
|
|
_receivePort = null;
|
|
_sendPort = null;
|
|
_scheduledWakeupTime = null;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
@pragma("vm:entry-point", "call")
|
|
_setupHooks() {
|
|
VMLibraryHooks.timerFactory = _Timer._factory;
|
|
}
|