blob: 5e88aaf97ff0ee5f6775ce3399103105212fd7f1 [file]
// 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 "dart:async";
typedef void _AsyncCallback();
class _AsyncCallbackEntry {
final _AsyncCallback callback;
_AsyncCallbackEntry? next;
_AsyncCallbackEntry(this.callback);
}
/// Head of single linked list of pending callbacks.
_AsyncCallbackEntry? _nextCallback;
/// Tail of single linked list of pending callbacks.
_AsyncCallbackEntry? _lastCallback;
/// Tail of priority callbacks added by the currently executing callback.
///
/// Priority callbacks are put at the beginning of the
/// callback queue, so that if one callback schedules more than one
/// priority callback, they are still enqueued in scheduling order.
_AsyncCallbackEntry? _lastPriorityCallback;
/// Whether we are currently inside the callback loop.
///
/// If we are inside the loop, we never need to schedule the loop,
/// even if adding a first element.
bool _isInCallbackLoop = false;
void _microtaskLoop() {
for (var entry = _nextCallback; entry != null; entry = _nextCallback) {
_lastPriorityCallback = null;
var next = entry.next;
_nextCallback = next;
if (next == null) _lastCallback = null;
_microtaskEntryCallback(entry)();
}
}
void _startMicrotaskLoop() {
_isInCallbackLoop = true;
try {
// Moved to separate function because try-finally prevents
// good optimization.
_microtaskLoop();
} finally {
_lastPriorityCallback = null;
_isInCallbackLoop = false;
if (_nextCallback != null) {
_AsyncRun._scheduleImmediate(_startMicrotaskLoop);
}
}
}
/// Schedules a callback to be called as a microtask.
///
/// The microtask is called after all other currently scheduled
/// microtasks, but as part of the current system event.
void _scheduleAsyncCallback(_AsyncCallback callback) {
_AsyncCallbackEntry newEntry = _AsyncCallbackEntry(callback);
_beforeScheduleMicrotaskCallback();
_AsyncCallbackEntry? lastCallback = _lastCallback;
if (lastCallback == null) {
_nextCallback = _lastCallback = newEntry;
if (!_isInCallbackLoop) {
_AsyncRun._scheduleImmediate(_startMicrotaskLoop);
}
} else {
lastCallback.next = newEntry;
_lastCallback = newEntry;
}
}
/// Schedules a callback to be called before all other currently scheduled ones.
///
/// This callback takes priority over existing scheduled callbacks.
/// It is only used internally to give higher priority to error reporting.
///
/// Is always run in the root zone.
void _schedulePriorityAsyncCallback(_AsyncCallback callback) {
if (_nextCallback == null) {
_scheduleAsyncCallback(callback);
_lastPriorityCallback = _lastCallback;
return;
}
_AsyncCallbackEntry entry = _AsyncCallbackEntry(callback);
_beforeSchedulePriorityCallback();
_AsyncCallbackEntry? lastPriorityCallback = _lastPriorityCallback;
if (lastPriorityCallback == null) {
entry.next = _nextCallback;
_nextCallback = _lastPriorityCallback = entry;
} else {
var next = lastPriorityCallback.next;
entry.next = next;
lastPriorityCallback.next = entry;
_lastPriorityCallback = entry;
if (next == null) {
_lastCallback = entry;
}
}
}
// Overridden by VM.
@pragma("dart2js:prefer-inline")
@pragma("dart2wasm:prefer-inline")
void _beforeSchedulePriorityCallback() {}
@pragma("dart2js:prefer-inline")
@pragma("dart2wasm:prefer-inline")
void _beforeScheduleMicrotaskCallback() {}
@pragma("dart2js:prefer-inline")
@pragma("dart2wasm:prefer-inline")
void Function() _microtaskEntryCallback(_AsyncCallbackEntry entry) =>
entry.callback;
/// Runs a function asynchronously.
///
/// Callbacks registered through this function are always executed in order and
/// are guaranteed to run before other asynchronous events (like [Timer] events,
/// or DOM events).
///
/// **Warning:** it is possible to starve the DOM by registering asynchronous
/// callbacks through this method. For example the following program runs
/// the callbacks without ever giving the Timer callback a chance to execute:
/// ```dart
/// main() {
/// Timer.run(() { print("executed"); }); // Will never be executed.
/// foo() {
/// scheduleMicrotask(foo); // Schedules [foo] in front of other events.
/// }
/// foo();
/// }
/// ```
/// ## Other resources
///
/// * [The Event Loop and Dart](https://dart.dev/articles/event-loop/):
/// Learn how Dart handles the event queue and microtask queue, so you can write
/// better asynchronous code with fewer surprises.
@pragma('vm:entry-point', 'call')
void scheduleMicrotask(void Function() callback) {
Zone currentZone = Zone._current;
if (identical(_rootZone, currentZone)) {
// We know register has no effect, run just calls the function,
// and `_rootScheduleMicrotask` will run its code in the root zone.
// No need to do anything extra,
_rootScheduleMicrotask(_rootZone, callback);
return;
}
if (currentZone._scheduleMicrotaskFunction == null &&
currentZone._handleUncaughtErrorFunction == null) {
// No overrides of schedule microtask and not in other error zone.
// Just register.
_rootScheduleMicrotask(
currentZone,
currentZone._registerCallbackZoned(currentZone, callback),
);
return;
}
// SHOULD NOT BE BINDING!
// The binding should only happen in `_rootScheduleMicrotask`,
// as an implementation detail. The wrapped function should not
// be passed to intermediate `scheduleMicrotask` handlers.
currentZone._scheduleMicrotaskZoned(
currentZone,
currentZone.bindCallbackGuarded(callback),
);
}
class _AsyncRun {
/// Schedule the given callback before any other event in the event-loop.
external static void _scheduleImmediate(void Function() callback);
}