Index: sdk/lib/async/zone.dart |
diff --git a/sdk/lib/async/zone.dart b/sdk/lib/async/zone.dart |
index 24f83f8bd8ba69a5ce7468aa031005d93a9d2266..b81a7fef8114f329909871fb71eb346286003140 100644 |
--- a/sdk/lib/async/zone.dart |
+++ b/sdk/lib/async/zone.dart |
@@ -194,14 +194,26 @@ class _ZoneSpecification implements ZoneSpecification { |
} |
/** |
- * This class wraps zones for delegation. |
+ * An adapted view of the parent zone. |
* |
- * When forwarding to parent zones one can't just invoke the parent zone's |
- * exposed functions (like [Zone.run]), but one needs to provide more |
- * information (like the zone the `run` was initiated). Zone callbacks thus |
- * receive more information including this [ZoneDelegate] class. When delegating |
- * to the parent zone one should go through the given instance instead of |
- * directly invoking the parent zone. |
+ * This class allows the implementation of a zone method to invoke methods on |
+ * the parent zone while retaining knowledge of the originating zone. |
+ * |
+ * Custom zones (created through [Zone.fork] or [runZoned]) can provide |
+ * implementations of most methods of zones. This is similar to overriding |
+ * methods on [Zone], except that this mechanism doesn't require subclassing. |
+ * |
+ * A custom zone function (provided through a [ZoneSpecification]) typically |
+ * records or wraps its parameters and then delegates the operation to its |
+ * parent zone using the provided [ZoneDelegate]. |
+ * |
+ * While zones have access to their parent zone (through [Zone.parent]) it is |
+ * recommended to call the methods on the provided parent delegate for two |
+ * reasons: |
+ * 1. the delegate methods take an additional `zone` argument which is the |
+ * zone the action has been initiated in. |
+ * 2. delegate calls are more efficient, since the implementation knows how |
+ * to skip zones that would just delegate to their parents. |
*/ |
abstract class ZoneDelegate { |
/*=R*/ handleUncaughtError/*<R>*/( |
@@ -224,117 +236,266 @@ abstract class ZoneDelegate { |
} |
/** |
- * 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. |
+ * A zone represents an environment that remains stable across asynchronous |
+ * calls. |
+ * |
+ * Code is always executed in the context of a zone, available as |
+ * [Zone.current]. The initial `main` function runs in the context of the |
+ * default zone ([Zone.ROOT]). Code can be run in a different zone using either |
+ * [runZoned], to create a new zone, or [Zone.run] to run code in the context of |
+ * an existing zone likely created using [Zone.fork]. |
+ * |
+ * Developers can create a new zone that overrides some of the functionality of |
+ * an existing zone. For example, custom zones can replace of modify the |
+ * behavior of `print`, timers, microtasks or how uncaught errors are handled. |
+ * |
+ * The [Zone] class is not subclassable, but users can provide custom zones by |
+ * forking an existing zone (usually [Zone.current]) with a [ZoneSpecification]. |
+ * This is similar to creating a new class that extends the base `Zone` class |
+ * and that overrides some methods, except without actually creating a new |
+ * class. Instead the overriding methods are provided as functions that |
+ * explicitly take the equivalent of their own class, the "super" class and the |
+ * `this` object as parameters. |
+ * |
+ * Asynchronous callbacks always run in the context of the zone where they were |
+ * scheduled. This is implemented using two steps: |
+ * 1. the callback is first registered using one of [registerCallback], |
+ * [registerUnaryCallback], or [registerBinaryCallback]. This allows the zone |
+ * to record that a callback exists and potentially modify it (by returning a |
+ * different callback). The code doing the registration (e.g., `Future.then`) |
+ * also remembers the current zone so that it can later run the callback in |
+ * that zone. |
+ * 2. At a later point the registered callback is run in the remembered zone. |
+ * |
+ * This is all handled internally by the platform code and most users don't need |
+ * to worry about it. However, developers of new asynchronous operations, |
+ * provided by the underlying system or through native extensions, must follow |
+ * the protocol to be zone compatible. |
+ * |
+ * For convenience, zones provide [bindCallback] (and the corresponding |
+ * [bindUnaryCallback] or [bindBinaryCallback]) to make it easier to respect the |
+ * zone contract: these functions first invoke the corresponding `register` |
+ * functions and then wrap the returned function so that it runs in the current |
+ * zone when it is later asynchronously invoked. |
*/ |
abstract class Zone { |
// Private constructor so that it is not possible instantiate a Zone class. |
Zone._(); |
- /** The root zone that is implicitly created. */ |
+ /** |
+ * The root zone. |
+ * |
+ * All isolate entry functions (`main` or spawned functions) start running in |
+ * the root zone (that is, [Zone.current] is identical to [Zone.ROOT] when the |
+ * entry function is called). If no custom zone is created, the rest of the |
+ * program always runs in the root zone. |
+ * |
+ * The root zone implements the default behavior of all zone operations. |
+ * Many methods, like [registerCallback] do the bare minimum required of the |
+ * function, and are only provided as a hook for custom zones. Others, like |
+ * [scheduleMicrotask], interact with the underlying system to implement the |
+ * desired behavior. |
+ */ |
static const Zone ROOT = _ROOT_ZONE; |
/** The currently running zone. */ |
static Zone _current = _ROOT_ZONE; |
+ /** The zone that is currently active. */ |
static Zone get current => _current; |
+ /** |
+ * Handles uncaught asynchronous errors. |
+ * |
+ * There are two kind of asynchronous errors that are handled by this |
+ * function: |
+ * 1. Uncaught errors that were thrown in asynchronous callbacks, for example, |
+ * a `throw` in the function passed to [Timer.run]. |
+ * 2. Asynchronous errors that are pushed through [Future] and [Stream] |
+ * chains, but for which no child registered an error handler. |
+ * Most asynchronous classes, like [Future] or [Stream] push errors to their |
+ * listeners. Errors are propagated this way until either a listener handles |
+ * the error (for example with [Future.catchError]), or no listener is |
+ * available anymore. In the latter case, futures and streams invoke the |
+ * zone's [handleUncaughtError]. |
+ * |
+ * By default, when handled by the root zone, uncaught asynchronous errors are |
+ * treated like uncaught synchronous exceptions. |
+ */ |
/*=R*/ handleUncaughtError/*<R>*/(error, StackTrace stackTrace); |
/** |
- * Returns the parent zone. |
+ * The parent zone of the this zone. |
* |
- * Returns `null` if `this` is the [ROOT] zone. |
+ * Is `null` if `this` is the [ROOT] zone. |
+ * |
+ * Zones are created by [fork] on an existing zone, or by [runZoned] which |
+ * forks the [current] zone. The new zone's parent zone is the zone it was |
+ * forked from. |
*/ |
Zone get parent; |
/** |
* The error zone is the one that is responsible for dealing with uncaught |
* errors. |
- * Errors are not allowed to cross between zones with different error-zones. |
* |
- * This is the closest parent or ancestor zone of this zone that has a custom |
+ * This is the closest parent zone of this zone that provides a |
* [handleUncaughtError] method. |
+ * |
+ * Asynchronous errors never cross zone boundaries between zones with |
+ * different error handlers. |
+ * |
+ * Example: |
+ * ``` |
+ * import 'dart:async'; |
+ * |
+ * main() { |
+ * var future; |
+ * runZoned(() { |
+ * // The asynchronous error is caught by the custom zone which prints |
+ * // 'asynchronous error'. |
+ * future = new Future.error("asynchronous error"); |
+ * }, onError: (e) { print(e); }); // Creates a zone with an error handler. |
+ * // The following `catchError` handler is never invoked, because the |
+ * // custom zone created by the call to `runZoned` provides an |
+ * // error handler. |
+ * future.catchError((e) { throw "is never reached"; }); |
+ * } |
+ * ``` |
+ * |
+ * Note that errors cannot enter a child zone with a different error handler |
+ * either: |
+ * ``` |
+ * import 'dart:async'; |
+ * |
+ * main() { |
+ * runZoned(() { |
+ * // The following asynchronous error is *not* caught by the `catchError` |
+ * // in the nested zone, since errors are not to cross zone boundaries |
+ * // with different error handlers. |
+ * // Instead the error is handled by the current error handler, |
+ * // printing "Caught by outer zone: asynchronous error". |
+ * var future = new Future.error("asynchronous error"); |
+ * runZoned(() { |
+ * future.catchError((e) { throw "is never reached"; }); |
+ * }, onError: (e) { throw "is never reached"; }); |
+ * }, onError: (e) { print("Caught by outer zone: $e"); }); |
+ * } |
+ * ``` |
*/ |
Zone get errorZone; |
/** |
* Returns true if `this` and [otherZone] are in the same error zone. |
* |
- * Two zones are in the same error zone if they inherit their |
- * [handleUncaughtError] callback from the same [errorZone]. |
+ * Two zones are in the same error zone if they have the same [errorZone]. |
*/ |
bool inSameErrorZone(Zone otherZone); |
/** |
* Creates a new zone as a child of `this`. |
* |
- * The new zone will have behavior like the current zone, except where |
- * overridden by functions in [specification]. |
+ * The new zone uses the closures in the given [specification] to override |
+ * the current's zone behavior. All specification entries that are `null` |
+ * inherit the behavior from the parent zone (`this`). |
+ * |
+ * The new zone inherits the stored values (accessed through [operator []]) |
+ * of this zone and updates them with values from [zoneValues], which either |
+ * adds new values or overrides existing ones. |
* |
- * The new zone will have the same stored values (accessed through |
- * `operator []`) as this zone, but updated with the keys and values |
- * in [zoneValues]. If a key is in both this zone's values and in |
- * `zoneValues`, the new zone will use the value from `zoneValues``. |
+ * Note that the fork operation is interceptible. A zone can thus change |
+ * the zone specification (or zone values), giving the forking zone full |
+ * control over the child zone. |
*/ |
- Zone fork({ ZoneSpecification specification, |
- Map zoneValues }); |
+ Zone fork({ZoneSpecification specification, |
+ Map zoneValues}); |
/** |
- * Executes the given function [f] in this zone. |
+ * Executes [action] in this zone. |
+ * |
+ * By default (as implemented in the [ROOT] zone), runs [action] |
+ * with [current] set to this zone. |
+ * |
+ * If [action] throws, the synchronous exception is not caught by the zone's |
+ * error handler. Use [runGuarded] to achieve that. |
+ * |
+ * Since the root zone is the only zone that can modify the value of |
+ * [current], custom zones intercepting run should always delegate to their |
+ * parent zone. They may take actions before and after the call. |
*/ |
- /*=R*/ run/*<R>*/(/*=R*/ f()); |
+ /*=R*/ run/*<R>*/(/*=R*/ action()); |
/** |
- * Executes the given callback [f] with argument [arg] in this zone. |
+ * Executes the given [action] with [argument] in this zone. |
+ * |
+ * As [run] except that [action] is called with one [argument] instead of |
+ * none. |
*/ |
- /*=R*/ runUnary/*<R, T>*/(/*=R*/ f(/*=T*/ arg), /*=T*/ arg); |
+ /*=R*/ runUnary/*<R, T>*/(/*=R*/ action(/*=T*/ argument), /*=T*/ argument); |
/** |
- * Executes the given callback [f] with argument [arg1] and [arg2] in this |
+ * Executes the given [action] with [argument1] and [argument2] in this |
* zone. |
+ * |
+ * As [run] except that [action] is called with two arguments instead of none. |
*/ |
/*=R*/ runBinary/*<R, T1, T2>*/( |
- /*=R*/ f(/*=T1*/ arg1, /*=T2*/ arg2), /*=T1*/ arg1, /*=T2*/ arg2); |
+ /*=R*/ action(/*=T1*/ argument1, /*=T2*/ argument2), /*=T1*/ argument1, |
+ /*=T2*/ argument2); |
/** |
- * Executes the given function [f] in this zone. |
+ * Executes the given [action] in this zone and catches synchronous |
+ * errors. |
* |
- * Same as [run] but catches uncaught errors and gives them to |
- * [handleUncaughtError]. |
+ * This function is equivalent to: |
+ * ``` |
+ * try { |
+ * return this.run(action); |
+ * } catch (e, s) { |
+ * return this.handleUncaughtError(e, s); |
+ * } |
+ * ``` |
+ * |
+ * See [run]. |
*/ |
- /*=R*/ runGuarded/*<R>*/(/*=R*/ f()); |
+ /*=R*/ runGuarded/*<R>*/(/*=R*/ action()); |
/** |
- * Executes the given callback [f] in this zone. |
+ * Executes the given [action] with [argument] in this zone and |
+ * catches synchronous errors. |
* |
- * Same as [runUnary] but catches uncaught errors and gives them to |
- * [handleUncaughtError]. |
+ * See [runGuarded]. |
*/ |
- /*=R*/ runUnaryGuarded/*<R, T>*/(/*=R*/ f(/*=T*/ arg), /*=T*/ arg); |
+ /*=R*/ runUnaryGuarded/*<R, T>*/(/*=R*/ action(/*=T*/ argument), |
+ /*=T*/ argument); |
/** |
- * Executes the given callback [f] in this zone. |
+ * Executes the given [action] with [argument1] and [argument2] in this |
+ * zone and catches synchronous errors. |
* |
- * Same as [runBinary] but catches uncaught errors and gives them to |
- * [handleUncaughtError]. |
+ * See [runGuarded]. |
*/ |
/*=R*/ runBinaryGuarded/*<R, T1, T2>*/( |
- /*=R*/ f(/*=T1*/ arg1, /*=T2*/ arg2), /*=T1*/ arg1, /*=T2*/ arg2); |
+ /*=R*/ action(/*=T1*/ argument1, /*=T2*/ argument2), /*=T1*/ argument1, |
+ /*=T2*/ argument2); |
/** |
* Registers the given callback in this zone. |
* |
- * It is good practice to register asynchronous or delayed callbacks before |
- * invoking [run]. This gives the zone a chance to wrap the callback and |
- * to store information with the callback. For example, a zone may decide |
+ * When implementing an asynchronous primitive that uses callbacks, the |
+ * callback must be registered using [registerCallback] at the point where the |
+ * user provides the callback. This allows zones to record other information |
+ * that they need at the same time, perhaps even wrapping the callback, so |
+ * that the callback is prepared when it is later run in the same zones |
+ * (using [run]). For example, a zone may decide |
* to store the stack trace (at the time of the registration) with the |
* callback. |
* |
- * Returns a potentially new callback that should be used in place of the |
- * given [callback]. |
+ * Returns the callback that should be used in place of the provided |
+ * [callback]. Frequently zones simply return the original callback. |
+ * |
+ * Custom zones may intercept this operation. The default implementation in |
+ * [Zone.ROOT] returns the original callback unchanged. |
*/ |
ZoneCallback/*<R>*/ registerCallback/*<R>*/(/*=R*/ callback()); |
@@ -357,58 +518,79 @@ abstract class Zone { |
/** |
* Equivalent to: |
* |
- * ZoneCallback registered = registerCallback(f); |
+ * ZoneCallback registered = this.registerCallback(action); |
* if (runGuarded) return () => this.runGuarded(registered); |
* return () => this.run(registered); |
* |
*/ |
ZoneCallback/*<R>*/ bindCallback/*<R>*/( |
- /*=R*/ f(), { bool runGuarded: true }); |
+ /*=R*/ action(), { bool runGuarded: true }); |
/** |
* Equivalent to: |
* |
- * ZoneCallback registered = registerUnaryCallback(f); |
+ * ZoneCallback registered = this.registerUnaryCallback(action); |
* if (runGuarded) return (arg) => this.runUnaryGuarded(registered, arg); |
* return (arg) => thin.runUnary(registered, arg); |
*/ |
ZoneUnaryCallback/*<R, T>*/ bindUnaryCallback/*<R, T>*/( |
- /*=R*/ f(/*=T*/ arg), { bool runGuarded: true }); |
+ /*=R*/ action(/*=T*/ argument), { bool runGuarded: true }); |
/** |
* Equivalent to: |
* |
- * ZoneCallback registered = registerBinaryCallback(f); |
+ * ZoneCallback registered = registerBinaryCallback(action); |
* if (runGuarded) { |
* return (arg1, arg2) => this.runBinaryGuarded(registered, arg); |
* } |
* return (arg1, arg2) => thin.runBinary(registered, arg1, arg2); |
*/ |
ZoneBinaryCallback/*<R, T1, T2>*/ bindBinaryCallback/*<R, T1, T2>*/( |
- /*=R*/ f(/*=T1*/ arg1, /*=T2*/ arg2), { bool runGuarded: true }); |
+ /*=R*/ action(/*=T1*/ argument1, /*=T2*/ argument2), |
+ { bool runGuarded: true }); |
/** |
- * Intercepts errors when added programmatically to a `Future` or `Stream`. |
+ * Intercepts errors when added programatically to a `Future` or `Stream`. |
+ * |
+ * When calling [Completer.completeError], [Stream.addError], |
+ * or some [Future] constructors, the current zone is allowed to intercept |
+ * and replace the error. |
+ * |
+ * Future constructors invoke this function when the error is received |
+ * directly, for example with [Future.error], or when the error is caught |
+ * synchronously, for example with [Future.sync]. |
+ * |
+ * There is no guarantee that an error is only sent through [errorCallback] |
+ * once. Libraries that use intermediate controllers or completers might |
+ * end up invoking [errorCallback] multiple times. |
+ * |
+ * Returns `null` if no replacement is desired. Otherwise returns an instance |
+ * of [AsyncError] holding the new pair of error and stack trace. |
* |
- * When caling [Completer.completeError], [Stream.addError], |
- * or [Future] constructors that take an error or a callback that may throw, |
- * the current zone is allowed to intercept and replace the error. |
+ * Although not recommended, the returned instance may have its `error` member |
+ * ([AsyncError.error]) be equal to `null` in which case the error should be |
+ * replaced by a [NullThrownError]. |
* |
- * When other libraries use intermediate controllers or completers, such |
- * calls may contain errors that have already been processed. |
+ * Custom zones may intercept this operation. |
* |
- * Return `null` if no replacement is desired. |
- * The original error is used unchanged in that case. |
- * Otherwise return an instance of [AsyncError] holding |
- * the new pair of error and stack trace. |
- * If the [AsyncError.error] is `null`, it is replaced by a [NullThrownError]. |
+ * Implementations of a new asynchronous primitive that converts synchronous |
+ * errors to asynchronous errors rarely need to invoke [errorCallback], since |
+ * errors are usually reported through future completers or stream |
+ * controllers. |
*/ |
AsyncError errorCallback(Object error, StackTrace stackTrace); |
/** |
- * Runs [f] asynchronously in this zone. |
+ * Runs [action] asynchronously in this zone. |
+ * |
+ * The global `scheduleMicrotask` delegates to the current zone's |
+ * [scheduleMicrotask]. The root zone's implementation interacts with the |
+ * underlying system to schedule the given callback as a microtask. |
+ * |
+ * Custom zones may intercept this operation (for example to wrap the given |
+ * callback [action]). |
*/ |
- void scheduleMicrotask(void f()); |
+ void scheduleMicrotask(void action()); |
/** |
* Creates a Timer where the callback is executed in this zone. |
@@ -422,6 +604,24 @@ abstract class Zone { |
/** |
* Prints the given [line]. |
+ * |
+ * The global `print` function delegates to the current zone's [print] |
+ * function which makes it possible to intercept printing. |
+ * |
+ * Example: |
+ * ``` |
+ * import 'dart:async'; |
+ * |
+ * main() { |
+ * runZoned(() { |
+ * // Ends up printing: "Intercepted: in zone". |
+ * print("in zone"); |
+ * }, zoneSpecification: new ZoneSpecification( |
+ * print: (Zone self, ZoneDelegate parent, Zone zone, String line) { |
+ * parent.print(zone, "Intercepted: $line"); |
+ * })); |
+ * } |
+ * ``` |
*/ |
void print(String line); |