Chromium Code Reviews| Index: sdk/lib/async/zone.dart |
| diff --git a/sdk/lib/async/zone.dart b/sdk/lib/async/zone.dart |
| index 9f015255d5b5547da939c5516f1656d5b3e8a63a..f0394d66683135687b457c4a57d77a7f55fbd06b 100644 |
| --- a/sdk/lib/async/zone.dart |
| +++ b/sdk/lib/async/zone.dart |
| @@ -7,6 +7,7 @@ part of dart.async; |
| typedef R ZoneCallback<R>(); |
| typedef R ZoneUnaryCallback<R, T>(T arg); |
| typedef R ZoneBinaryCallback<R, T1, T2>(T1 arg1, T2 arg2); |
| + |
| typedef T TaskCreate<T, S extends TaskSpecification>( |
| S specification, Zone zone); |
| typedef void TaskRun<T, A>(T task, A arg); |
| @@ -259,14 +260,26 @@ class _ZoneSpecification implements ZoneSpecification { |
| } |
| /** |
| - * This class wraps zones for delegation. |
| + * An adapted view of 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. |
| * |
| - * 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. |
| + * 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 way, since the implementation knows how |
|
Lasse Reichstein Nielsen
2016/06/30 14:37:47
delete "way".
floitsch
2016/07/01 04:03:40
Done.
|
| + * to skip zones that would just delegate to their parents. |
| */ |
| abstract class ZoneDelegate { |
| /*=R*/ handleUncaughtError/*<R>*/( |
| @@ -296,39 +309,133 @@ 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, and which is responsible for handling uncaught asynchronous errors, |
| + * or operations such as [print] and [scheduleMicrotask]. |
| + * |
| + * 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. |
| + * |
| + * Code is always executed inside a zone. When a program is started, the |
| + * default zone ([Zone.ROOT]) is installed. Users can provide |
| + * shadowing, nested zones. |
| + * |
| + * The [Zone] class is not subclassable, but users can provide custom zones by |
| + * forking an existing zone (usually [Zone.current]) with a [ZoneSpecification]. |
| + * Zone specifications contain intercepting functions that are invoked when the |
| + * zone members are invoked. As such, they provide the same functionality as |
| + * subclassing (but allow for a more efficient implementation). |
| + * |
| + * Asynchronous callbacks always return in the zone in which they have been |
| + * scheduled. This happens in two steps: |
| + * - the callback is registered using one of [registerCallback], |
| + * [registerUnaryCallback], or [registerBinaryCallback]. |
| + * - the asynchronous operation (such as [Future.then] or [Stream.listen]) |
| + * remember the current zone. Either, they store the zone in a data structure |
| + * (as is done for [Future]s), or they wrap the callback to capture the |
| + * current zone. A convenience function [bindCallback] (and the corresponding |
| + * [bindUnaryCallback] or [bindBindaryCallback]) perform the registration and |
| + * wrapping at the same time. |
| + * |
| + * Note that all asynchronous primitives (like [Timer.run]) have to be |
| + * implemented by the embedder, and that users generally don't need to worry |
| + * about keeping track of zones. However, new embedders (or native extensions) |
| + * need to ensure that new asynchronous primitives (like for example |
| + * `requestAnimationFrame` in the HTML library) respect this contract. |
| */ |
| 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 that is implicitly created. |
| + * |
| + * The root zone implements the default behavior of all zone operations. |
| + * Many methods, like [registerCallback] don't do anything, others, like |
| + * [scheduleMicrotask] interact with the embedder 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. |
| + * |
| + * 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, in the root zone, uncaught asynchronous errors are treated |
| + * like synchronous uncaught exceptions (although the root zone defers the |
| + * reporting by a microtask, to give other microtasks the opportunity to run |
| + * one last time). |
| + */ |
| /*=R*/ handleUncaughtError/*<R>*/(error, StackTrace stackTrace); |
| /** |
| * Returns the parent zone. |
| * |
| * Returns `null` if `this` is the [ROOT] zone. |
| + * |
| + * Zones are created by [fork] (or [runZoned] which forks the [current] zone) |
| + * on an existing zone. The new zone keeps the forking zone as [parent] zone. |
| */ |
| 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 of zones with different |
| + * error-zones. |
| + * |
| + * 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` is never reached, because the custom zone |
| + * // that is created by the call to `runZoned` provides an error handler. |
| + * future.catchError((e) { throw "is never reached"; }); |
| + * } |
| + * ``` |
| + * |
| + * Note that errors are not entering zones with different error handlers |
| + * 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; |
| @@ -336,62 +443,87 @@ abstract class Zone { |
| * 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]. |
| + * [errorZone] is the same. |
| */ |
| 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` |
| + * are automatically delegated to the parent zone (`this`). |
| * |
| - * The new zone will have the same stored values (accessed through |
| + * The new zone has 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``. |
| + * `zoneValues`, the new zone uses the value from `zoneValues`. |
| + * |
| + * Note that the fork operation is interceptible. A zone can thus replace |
| + * the zone specification (or zone value), giving the parent 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. |
| + * |
| + * By default (as implemented in the [ROOT] zone, this updates the [current] |
| + * zone to this zone, and executes [f]. |
| + * |
| + * Since the root zone is the only zone that can modify the [current] getter, |
| + * custom zones have to delegate to their parent zone if they wish to run |
| + * in their zone (which is generally the recommended behavior). |
| */ |
| /*=R*/ run/*<R>*/(/*=R*/ f()); |
| /** |
| * Executes the given callback [f] with argument [arg] in this zone. |
| + * |
| + * See [run]. |
| */ |
| /*=R*/ runUnary/*<R, T>*/(/*=R*/ f(/*=T*/ arg), /*=T*/ arg); |
| /** |
| * Executes the given callback [f] with argument [arg1] and [arg2] in this |
| * zone. |
| + * |
| + * See [run]. |
| */ |
| /*=R*/ runBinary/*<R, T1, T2>*/( |
| /*=R*/ f(/*=T1*/ arg1, /*=T2*/ arg2), /*=T1*/ arg1, /*=T2*/ arg2); |
| /** |
| - * Executes the given function [f] in this zone. |
| + * Executes the given function [f] in this zone and catches synchronous |
| + * errors. |
| + * |
| + * This function is equivalent to: |
| + * ``` |
| + * try { |
| + * return run(f); |
| + * } catch (e, s) { |
| + * return handleUncaughtError(e, s); |
| + * } |
| + * ``` |
| * |
| - * Same as [run] but catches uncaught errors and gives them to |
| - * [handleUncaughtError]. |
| + * See [run]. |
| */ |
| /*=R*/ runGuarded/*<R>*/(/*=R*/ f()); |
| /** |
| - * Executes the given callback [f] in this zone. |
| + * Executes the given callback [f] with argument [arg] 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); |
| /** |
| - * Executes the given callback [f] in this zone. |
| + * Executes the given callback [f] with arguments [arg1] and [arg2] in this |
| + * zone. |
| * |
| - * 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); |
| @@ -407,6 +539,8 @@ abstract class Zone { |
| * |
| * Returns a potentially new callback that should be used in place of the |
| * given [callback]. |
| + * |
| + * Custom zones may intercept this operation. |
| */ |
| ZoneCallback/*<R>*/ registerCallback/*<R>*/(/*=R*/ callback()); |
| @@ -460,45 +594,80 @@ abstract class Zone { |
| /*=R*/ f(/*=T1*/ arg1, /*=T2*/ arg2), { bool runGuarded: true }); |
| /** |
| - * Intercepts errors when added programmatically to a `Future` or `Stream`. |
| + * Intercepts errors when added programatically to a `Future` or `Stream`. |
| * |
| - * When caling [Completer.completeError], [Stream.addError], |
| + * When calling [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. |
| * |
| - * When other libraries use intermediate controllers or completers, such |
| - * calls may contain errors that have already been processed. |
| + * 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. |
| * |
| - * 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]. |
| + * 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]. |
| + * |
| + * Custom zones may intercept this operation. |
| */ |
| AsyncError errorCallback(Object error, StackTrace stackTrace); |
| /** |
| * Runs [f] asynchronously in this zone. |
| + * |
| + * The global `scheduleMicrotask` delegates to the current zone's |
| + * [scheduleMicrotask]. The root zone's implementation interacts with the |
| + * embedder to schedule the given callback as microtask. |
| + * |
| + * Custom zones may intercept this operation (for example to wrap the given |
| + * callback [f]). |
| */ |
| void scheduleMicrotask(void f()); |
| /** |
| * Creates a task, given a [create] function and a [specification]. |
| * |
| - * The [create] function is invoked with the [specification] as argument. It |
| - * returns a task object which is used for all future interactions with the |
| - * zone. |
| - * |
| - * Custom zones may replace the [specification] with a different one, thus |
| - * modifying the task parameters. |
| + * By default, in the root zone, the [create] function is invoked with the |
| + * [specification] as argument. It returns a task object which is used for all |
| + * future interactions with the zone. Generally, the object is a unique |
| + * instance that is also returned to whoever initiated the action. |
| + * For example, the HTML library uses the returned [StreamSubscription] as |
| + * task object, when users register an event listener. |
| * |
| - * Tasks are created when the program is starting an operation that returns |
| - * through the event loop. For example, a timer or an http request both |
| + * Tasks are created when the program starts an operation that returns |
| + * through the event loop. For example, a timer or an HTTP request both |
| * return through the event loop and are therefore tasks. |
| * |
| * If the [create] function is not invoked (because a custom zone has |
| * replaced or intercepted it), then the operation is *not* started. This |
| - * means that a custom zone can intercept tasks, like http requests. |
| + * means that a custom zone can intercept tasks, like HTTP requests. |
| + * |
| + * A task goes through the following steps: |
| + * - a user invokes a library function that should eventually return through |
| + * the event loop (and not just as a microtask). |
| + * - the library function creates a [TaskSpecification] that contains the |
| + * necessary information to start the operation, and invokes |
| + * `Zone.current.createTask` with the specification and a [create] closure. |
| + * The closure, when invoked, uses the specification to start the operation |
| + * (usually by interacting with the embedder, ar as a native extension), |
| + * and returns a task object that identifies the running task. |
| + * - custom zones handle the request and (unless completely intercepted and |
| + * aborted), end up calling the root zone's [createTask] which runs the |
| + * provided `create` closure (which may have been replaced at this point). |
| + * - later, the asynchronous operation returns through the event loop. |
| + * It invokes [Zone.runTask] on the zone the task should run on (which had |
| + * been given to the create function). The [runTask] function receives the |
| + * task object, a `run` function and an argument. As before, custom zones |
| + * may intercept this call. Eventually (unless aborted), the `run` function |
| + * is invoked, running Dart code that has been registered to run when the |
| + * task returns. This last step may happen multiple times for tasks that are |
| + * not oneshot tasks (see [ZoneSpecification.isOneShot]. |
| + * |
| + * Custom zones may replace the [specification] with a different one, thus |
| + * modifying the task parameters. |
| */ |
| Object/*=T*/ createTask/*<T, S>*/( |
| TaskCreate/*<T, S>*/ create, TaskSpecification/*=S*/ specification); |
| @@ -517,6 +686,8 @@ abstract class Zone { |
| * It is good practice, if task operations provide a meaningful [arg], so |
| * that custom zones can deal with it. They might want to log it, or |
| * replace it. |
| + * |
| + * See [createTask]. |
| */ |
| void runTask/*<T, A>*/( |
| TaskRun/*<T, A>*/ run, Object/*=T*/ task, Object/*=A*/ arg); |
| @@ -535,6 +706,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 the print function. |
| + * |
| + * 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); |