blob: bea3fc24b1ef6f245b169b59bc1eed7ffa415881 [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.
part of dart.async;
/**
* A Zone represents the asynchronous version of a dynamic extent. Asynchronous
* callbacks are executed in the zone they have been queued in. For example,
* the callback of a `future.then` is executed in the same zone as the one where
* the `then` was invoked.
*/
abstract class _Zone {
/// The currently running zone.
static _Zone _current = new _DefaultZone();
static _Zone get current => _current;
void handleUncaughtError(error);
/**
* Returns true if `this` and [otherZone] are in the same error zone.
*/
bool inSameErrorZone(_Zone otherZone);
/**
* Returns a zone for reentry in the zone.
*
* The returned zone is equivalent to `this` (and frequently is indeed
* `this`).
*
* The main purpose of this method is to allow `this` to attach debugging
* information to the returned zone.
*/
_Zone fork();
/**
* Tells the zone that it needs to wait for one more callback before it is
* done.
*
* Use [executeCallback] or [cancelCallbackExpectation] when the callback is
* executed (or canceled).
*/
void expectCallback();
/**
* Tells the zone not to wait for a callback anymore.
*
* Prefer calling [executeCallback], instead. This method is mostly useful
* for repeated callbacks (for example with [Timer.periodic]). In this case
* one should should call [expectCallback] when the repeated callback is
* initiated, and [cancelCallbackExpectation] when the [Timer] is canceled.
*/
void cancelCallbackExpectation();
/**
* Executes the given callback [f] in this zone.
*
* Decrements the number of callbacks this zone is waiting for (see
* [expectCallback]).
*/
void executeCallback(void f());
/**
* Same as [executeCallback] but catches uncaught errors and gives them to
* [handleUncaughtError].
*/
void executeCallbackGuarded(void f());
/**
* Same as [executeCallback] but does not decrement the number of
* callbacks this zone is waiting for (see [expectCallback]).
*/
void executePeriodicCallback(void f());
/**
* Same as [executePeriodicCallback] but catches uncaught errors and gives
* them to [handleUncaughtError].
*/
void executePeriodicCallbackGuarded(void f());
/**
* Executes [f] in `this` zone.
*
* The behavior of this method should be the same as
* [executePeriodicCallback] except that it can have a return value.
*
* Returns the result of the invocation.
*/
runFromChildZone(f());
/**
* Same as [runFromChildZone] but catches uncaught errors and gives them to
* [handleUncaughtError].
*/
runFromChildZoneGuarded(f());
/**
* Runs [f] asynchronously in [zone].
*/
void runAsync(void f(), _Zone zone);
/**
* Creates a Timer where the callback is executed in this zone.
*/
Timer createTimer(Duration duration, void callback());
/**
* Creates a periodic Timer where the callback is executed in this zone.
*/
Timer createPeriodicTimer(Duration duration, void callback(Timer timer));
/**
* The error zone is the one that is responsible for dealing with uncaught
* errors. Errors are not allowed to cross zones with different error-zones.
*/
_Zone get _errorZone;
/**
* Adds [child] as a child of `this`.
*
* This usually means that the [child] is in the asynchronous dynamic extent
* of `this`.
*/
void _addChild(_Zone child);
/**
* Removes [child] from `this`' children.
*
* This usually means that the [child] has finished executing and is done.
*/
void _removeChild(_Zone child);
}
/**
* Basic implementation of a [_Zone]. This class is intended for subclassing.
*/
class _ZoneBase implements _Zone {
/// The parent zone. [null] if `this` is the default zone.
final _Zone _parentZone;
/// The children of this zone. A child's [_parentZone] is `this`.
// TODO(floitsch): this should be a double-linked list.
final List<_Zone> _children = <_Zone>[];
/// The number of outstanding (asynchronous) callbacks. As long as the
/// number is greater than 0 it means that the zone is not done yet.
int _openCallbacks = 0;
bool _isExecutingCallback = false;
_ZoneBase(this._parentZone) {
_parentZone._addChild(this);
}
_ZoneBase._defaultZone() : _parentZone = null {
assert(this is _DefaultZone);
}
_Zone get _errorZone => _parentZone._errorZone;
void handleUncaughtError(error) {
_parentZone.handleUncaughtError(error);
}
bool inSameErrorZone(_Zone otherZone) => _errorZone == otherZone._errorZone;
_Zone fork() => this;
expectCallback() => _openCallbacks++;
cancelCallbackExpectation() {
_openCallbacks--;
_checkIfDone();
}
/**
* Cleans up this zone when it is done.
*
* This releases internal memore structures that are no longer necessary.
*
* A zone is done when its dynamic extent has finished executing and
* there are no outstanding asynchronous callbacks.
*/
_dispose() {
if (_parentZone != null) {
_parentZone._removeChild(this);
}
}
/**
* Checks if the zone is done and doesn't have any outstanding callbacks
* anymore.
*
* This method is called when an operation has decremented the
* outstanding-callback count, or when a child has been removed.
*/
void _checkIfDone() {
if (!_isExecutingCallback && _openCallbacks == 0 && _children.isEmpty) {
_dispose();
}
}
void executeCallback(void f()) {
_openCallbacks--;
this._runUnguarded(f);
}
void executeCallbackGuarded(void f()) {
_openCallbacks--;
this._runGuarded(f);
}
void executePeriodicCallback(void f()) {
this._runUnguarded(f);
}
void executePeriodicCallbackGuarded(void f()) {
this._runGuarded(f);
}
runFromChildZone(f()) => this._runUnguarded(f);
runFromChildZoneGuarded(f()) => this._runGuarded(f);
_runInZone(f(), bool handleUncaught) {
if (identical(_Zone._current, this)
&& !handleUncaught
&& _isExecutingCallback) {
// No need to go through a try/catch.
return f();
}
_Zone oldZone = _Zone._current;
_Zone._current = this;
// While we are executing the function we don't want to have other
// synchronous calls to think that they closed the zone. By incrementing
// the _openCallbacks count we make sure that their test will fail.
// As a side effect it will make nested calls faster since they are
// (probably) in the same zone and have an _openCallbacks > 0.
bool oldIsExecuting = _isExecutingCallback;
_isExecutingCallback = true;
// TODO(430): remove second try when VM bug is fixed.
try {
try {
return f();
} catch(e, s) {
if (handleUncaught) {
handleUncaughtError(_asyncError(e, s));
} else {
rethrow;
}
}
} finally {
_isExecutingCallback = oldIsExecuting;
_Zone._current = oldZone;
_checkIfDone();
}
}
/**
* Runs the function and catches uncaught errors.
*
* Uncaught errors are given to [handleUncaughtError].
*/
_runGuarded(void f()) {
return _runInZone(f, true);
}
/**
* Runs the function but doesn't catch uncaught errors.
*/
_runUnguarded(void f()) {
return _runInZone(f, false);
}
runAsync(void f(), _Zone zone) => _parentZone.runAsync(f, zone);
// TODO(floitsch): the zone should just forward to the parent zone. The
// default zone should then create the _ZoneTimer.
Timer createTimer(Duration duration, void callback()) {
return new _ZoneTimer(this, duration, callback);
}
// TODO(floitsch): the zone should just forward to the parent zone. The
// default zone should then create the _ZoneTimer.
Timer createPeriodicTimer(Duration duration, void callback(Timer timer)) {
return new _PeriodicZoneTimer(this, duration, callback);
}
void _addChild(_Zone child) {
// TODO(floitsch): the zone should just increment a counter, but not keep
// a reference to the child.
_children.add(child);
}
void _removeChild(_Zone child) {
assert(!_children.isEmpty);
// Children are usually added and removed fifo or filo.
if (identical(_children.last, child)) {
_children.length--;
_checkIfDone();
return;
}
for (int i = 0; i < _children.length; i++) {
if (identical(_children[i], child)) {
_children[i] = _children[_children.length - 1];
_children.length--;
// No need to check for done, as otherwise _children.last above would
// have triggered.
assert(!_children.isEmpty);
return;
}
}
throw new ArgumentError(child);
}
}
/**
* The default-zone that conceptually surrounds the `main` function.
*/
class _DefaultZone extends _ZoneBase {
_DefaultZone() : super._defaultZone();
_Zone get _errorZone => this;
handleUncaughtError(error) {
_scheduleAsyncCallback(() {
print("Uncaught Error: ${error}");
var trace = getAttachedStackTrace(error);
_attachStackTrace(error, null);
if (trace != null) {
print("Stack Trace:\n$trace\n");
}
throw error;
});
}
void runAsync(void f(), _Zone zone) {
if (identical(this, zone)) {
// No need to go through the zone when it's the default zone anyways.
_scheduleAsyncCallback(f);
return;
}
zone.expectCallback();
_scheduleAsyncCallback(() {
zone.executeCallbackGuarded(f);
});
}
}
typedef void _CompletionCallback();
/**
* A zone that executes a callback when the zone is dead.
*/
class _WaitForCompletionZone extends _ZoneBase {
final _CompletionCallback _onDone;
_WaitForCompletionZone(_Zone parentZone, this._onDone) : super(parentZone);
/**
* Runs the given function asynchronously. Executes the [_onDone] callback
* when the zone is done.
*/
runWaitForCompletion(void f()) {
return this._runUnguarded(f);
}
_dispose() {
super._dispose();
_onDone();
}
String toString() => "WaitForCompletion ${super.toString()}";
}
typedef void _HandleErrorCallback(error);
/**
* A zone that collects all uncaught errors and provides them in a stream.
* The stream is closed when the zone is done.
*/
class _CatchErrorsZone extends _WaitForCompletionZone {
final _HandleErrorCallback _handleError;
_CatchErrorsZone(_Zone parentZone, this._handleError, void onDone())
: super(parentZone, onDone);
_Zone get _errorZone => this;
handleUncaughtError(error) {
try {
_handleError(error);
} catch(e, s) {
if (identical(e, s)) {
_parentZone.handleUncaughtError(error);
} else {
_parentZone.handleUncaughtError(_asyncError(e, s));
}
}
}
/**
* Runs the given function asynchronously. Executes the [_onDone] callback
* when the zone is done.
*/
runWaitForCompletion(void f()) {
return this._runGuarded(f);
}
String toString() => "CatchErrors ${super.toString()}";
}
typedef void _RunAsyncInterceptor(void callback());
class _RunAsyncZone extends _ZoneBase {
final _RunAsyncInterceptor _runAsyncInterceptor;
_RunAsyncZone(_Zone parentZone, this._runAsyncInterceptor)
: super(parentZone);
void runAsync(void callback(), _Zone zone) {
zone.expectCallback();
_parentZone.runFromChildZone(() {
_runAsyncInterceptor(() => zone.executeCallbackGuarded(callback));
});
}
}
typedef void _TimerCallback();
/**
* A [Timer] class that takes zones into account.
*/
class _ZoneTimer implements Timer {
final _Zone _zone;
final _TimerCallback _callback;
Timer _timer;
_ZoneTimer(this._zone, Duration duration, this._callback) {
_zone.expectCallback();
_timer = _createTimer(duration, this._run);
}
void _run() {
_zone.executeCallbackGuarded(_callback);
}
void cancel() {
if (_timer.isActive) _zone.cancelCallbackExpectation();
_timer.cancel();
}
bool get isActive => _timer.isActive;
}
typedef void _PeriodicTimerCallback(Timer timer);
/**
* A [Timer] class for periodic callbacks that takes zones into account.
*/
class _PeriodicZoneTimer implements Timer {
final _Zone _zone;
final _PeriodicTimerCallback _callback;
Timer _timer;
_PeriodicZoneTimer(this._zone, Duration duration, this._callback) {
_zone.expectCallback();
_timer = _createPeriodicTimer(duration, this._run);
}
void _run(Timer timer) {
assert(identical(_timer, timer));
_zone.executePeriodicCallbackGuarded(() { _callback(this); });
}
void cancel() {
if (_timer.isActive) _zone.cancelCallbackExpectation();
_timer.cancel();
}
bool get isActive => _timer.isActive;
}
/**
* Runs [body] in its own zone.
*
* If [onError] is non-null the zone is considered an error zone. All uncaught
* errors, synchronous or asynchronous, in the zone are caught and handled
* by the callback.
*
* The [onDone] handler (if non-null) is invoked when the zone has no more
* outstanding callbacks.
*
* The [onRunAsync] handler (if non-null) is invoked when the [body] executes
* [runAsync]. The handler is invoked in the outer zone and can therefore
* execute [runAsync] without recursing. The given callback must be
* executed eventually. Otherwise the nested zone will not complete. It must be
* executed only once.
*
* Examples:
*
* runZonedExperimental(() {
* new Future(() { throw "asynchronous error"; });
* }, onError: print); // Will print "asynchronous error".
*
* The following example prints "1", "2", "3", "4" in this order.
*
* runZonedExperimental(() {
* print(1);
* new Future.value(3).then(print);
* }, onDone: () { print(4); });
* print(2);
*
* Errors may never cross error-zone boundaries. This is intuitive for leaving
* a zone, but it also applies for errors that would enter an error-zone.
* Errors that try to cross error-zone boundaries are considered uncaught.
*
* var future = new Future.value(499);
* runZonedExperimental(() {
* future = future.then((_) { throw "error in first error-zone"; });
* runZonedExperimental(() {
* future = future.catchError((e) { print("Never reached!"); });
* }, onError: (e) { print("unused error handler"); });
* }, onError: (e) { print("catches error of first error-zone."); });
*
* The following example prints the stack trace whenever a callback is
* registered using [runAsync] (which is also used by [Completer]s and
* [StreamController]s.
*
* printStackTrace() { try { throw 0; } catch(e, s) { print(s); } }
* runZonedExperimental(body, onRunAsync: (callback) {
* printStackTrace();
* runAsync(callback);
* });
*/
runZonedExperimental(body(),
{ void onRunAsync(void callback()),
void onError(error),
void onDone() }) {
if (onRunAsync != null) {
_RunAsyncZone zone = new _RunAsyncZone(_Zone._current, onRunAsync);
return zone._runUnguarded(() {
return runZonedExperimental(body, onError: onError, onDone: onDone);
});
}
// TODO(floitsch): we probably still want to install a new Zone.
if (onError == null && onDone == null) return body();
if (onError == null) {
_WaitForCompletionZone zone =
new _WaitForCompletionZone(_Zone._current, onDone);
return zone.runWaitForCompletion(body);
}
if (onDone == null) onDone = _nullDoneHandler;
_CatchErrorsZone zone = new _CatchErrorsZone(_Zone._current, onError, onDone);
return zone.runWaitForCompletion(body);
}