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

Unified Diff: sdk/lib/async/zone.dart

Issue 15764003: Add Zone support for Timers. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Fix bug and add tests. Created 7 years, 7 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
Index: sdk/lib/async/zone.dart
diff --git a/sdk/lib/async/zone.dart b/sdk/lib/async/zone.dart
new file mode 100644
index 0000000000000000000000000000000000000000..d122417f978fef1e755eeb88282a2efcca092b8a
--- /dev/null
+++ b/sdk/lib/async/zone.dart
@@ -0,0 +1,436 @@
+// 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 [unexpectCallback] 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 [unexpectCallback] when the [Timer] is canceled.
+ */
+ void unexpectCallback();
+
+ /**
+ * Executes the given callback in this zone.
+ *
+ * Decrements the number of callbacks this zone is waiting for (see
+ * [expectCallback]).
+ */
+ void executeCallback(void fun());
+
+ /**
+ * Same as [executeCallback] but catches uncaught errors and gives them to
+ * [handleUncaughtError].
+ */
+ void executeGuardedCallback(void fun());
Lasse Reichstein Nielsen 2013/05/29 08:04:59 Name annoys me. It's not the callback which has th
floitsch 2013/05/29 15:21:29 changed to executeCallbackGuarded
+
+ /**
+ * Same as [executeCallback] but does not decrement the number of
+ * callbacks this zone is waiting for (see [expectCallback]).
+ */
+ void executePeriodicCallback(void fun());
Lasse Reichstein Nielsen 2013/05/29 08:04:59 execueCallbackPeriodically?
floitsch 2013/05/29 15:21:29 no. The callback is not executed periodically. it'
+
+ /**
+ * Same as [executePeriodicCallback] but catches uncaught errors and gives
+ * them to [handleUncaughtError].
+ */
+ void executeGuardedPeriodicCallback(void fun());
+
+ /**
+ * Runs [fun] asynchronously in this zone.
+ */
+ void runAsync(void fun());
+
+ /**
+ * 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;
+
+ _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++;
+
+ unexpectCallback() {
+ _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 (_openCallbacks == 0 && _children.isEmpty) {
+ _dispose();
+ }
+ }
+
+ /**
+ * Executes the given callback in this zone.
+ *
+ * Decrements the open-callback counter and checks (after the call) if the
+ * zone is done.
+ */
+ void executeCallback(void fun()) {
+ _openCallbacks--;
+ _runInZone(fun);
+ }
+
+ /**
+ * Same as [executeCallback] but catches uncaught errors and gives them to
+ * [handleUncaughtError].
+ */
+ void executeGuardedCallback(void fun()) {
+ _openCallbacks--;
+ _runGuarded(fun);
+ }
+
+ /**
+ * Same as [executeCallback] but doesn't decrement the open-callback counter.
+ */
+ void executePeriodicCallback(void fun()) {
+ _runInZone(fun);
+ }
+
+ /**
+ * Same as [executePeriodicCallback] but catches uncaught errors and gives
+ * them to [handleUncaughtError].
+ */
+ void executeGuardedPeriodicCallback(void fun()) {
+ _runGuarded(fun);
+ }
+
+ _runInZone(fun()) {
+ if (identical(_Zone._current, this) && _openCallbacks != 0) return fun();
+
+ _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.
+ _openCallbacks++;
+ try {
+ return fun();
+ } finally {
+ _openCallbacks--;
+ _Zone._current = oldZone;
+ _checkIfDone();
+ }
+ }
+
+ /**
+ * Runs the function and catches uncaught errors.
+ *
+ * Uncaught errors are given to [handleUncaughtError].
+ */
+ _runGuarded(void fun()) {
+ try {
+ _runInZone(fun);
+ } catch(e, s) {
+ handleUncaughtError(_asyncError(e, s));
+ }
+ }
+
+ runAsync(void fun()) {
+ _openCallbacks++;
+ _scheduleAsyncCallback(() {
+ _openCallbacks--;
+ _runGuarded(fun);
+ });
+ }
+
+ Timer createTimer(Duration duration, void callback()) {
+ return new _ZoneTimer(this, duration, callback);
+ }
+
+ Timer createPeriodicTimer(Duration duration, void callback(Timer timer)) {
+ return new _PeriodicZoneTimer(this, duration, callback);
+ }
+
+ void _addChild(_Zone 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) {
+ print("Uncaught Error: ${error}");
+ var trace = getAttachedStackTrace(error);
+ if (trace != null) {
+ print("Stack Trace:\n$trace\n");
+ }
+ throw error;
+ }
+}
+
+/**
+ * A zone that can execute a callback (through a future) when the zone is dead.
+ */
+class _WaitForCompletionZone extends _ZoneBase {
+ final Completer _doneCompleter = new Completer();
+
+ _WaitForCompletionZone(_Zone parentZone) : super(parentZone);
+
+ /**
+ * Runs the given function asynchronously and returns a future that is
+ * completed with `null` once the zone is done.
+ */
+ Future runWaitForCompletion(void fun()) {
+ _runInZone(() {
+ try {
+ fun();
+ } catch (e, s) {
+ handleUncaughtError(_asyncError(e, s));
+ }
+ });
+ return _doneCompleter.future;
+ }
+
+ _dispose() {
+ super._dispose();
+ _doneCompleter.complete();
+ }
+
+ String toString() => "WaitForCompletion ${super.toString()}";
+}
+
+/**
+ * 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 StreamController errorsController = new StreamController();
+
+ Stream get errors => errorsController.stream;
+
+ _CatchErrorsZone(_Zone parentZone) : super(parentZone);
+
+ _Zone get _errorZone => this;
+
+ handleUncaughtError(error) {
+ errorsController.add(error);
+ }
+
+ Future runWaitForCompletion(void fun()) {
+ super.runWaitForCompletion(fun).whenComplete(() {
+ errorsController.close();
+ });
+ }
+
+ String toString() => "WithErrors ${super.toString()}";
+}
+
+typedef void _TimerCallback();
+
+/**
+ * A [Timer] class that takes zones into account.
+ */
+class _ZoneTimer implements Timer {
+ final _Zone _zone;
+ final _TimerCallback _callback;
+ Timer _timer;
+ bool _isDone = false;
+
+ _ZoneTimer(this._zone, Duration duration, this._callback) {
+ _zone.expectCallback();
+ _timer = _createTimer(duration, this.run);
+ }
+
+ void run() {
+ _isDone = true;
+ _zone.executeGuardedCallback(_callback);
+ }
+
+ void cancel() {
+ if (!_isDone) _zone.unexpectCallback();
+ _isDone = true;
+ _timer.cancel();
+ }
+}
+
+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;
+ bool _isDone = false;
+
+ _PeriodicZoneTimer(this._zone, Duration duration, this._callback) {
+ _zone.expectCallback();
+ _timer = _createPeriodicTimer(duration, this.run);
+ }
+
+ void run(Timer timer) {
+ assert(identical(_timer, timer));
+ _zone.executeGuardedPeriodicCallback(() { _callback(this); });
+ }
+
+ void cancel() {
+ if (!_isDone) _zone.unexpectCallback();
+ _isDone = true;
+ _timer.cancel();
+ }
+}
+
+Stream catchErrors(void body()) {
+ _Zone catchErrorsZone = new _CatchErrorsZone(_Zone._current);
+ catchErrorsZone.runWaitForCompletion(body);
+ return catchErrorsZone.errors;
+}
+
+Future waitForCompletion(void body()) {
+ _Zone zone = new _WaitForCompletionZone(_Zone._current);
+ return zone.runWaitForCompletion(body);
+}

Powered by Google App Engine
This is Rietveld 408576698