Chromium Code Reviews| 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..c6b70dcd8016ca37bae8a9b11a50878906694644 |
| --- /dev/null |
| +++ b/sdk/lib/async/zone.dart |
| @@ -0,0 +1,263 @@ |
| +// 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; |
| + |
| +get _stackTrace { |
| + try { |
| + throw "foo"; |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
throw 0;
No need to introduce an string constant t
floitsch
2013/05/21 18:08:32
agreed.
Removed together with dPrint below.
|
| + } catch (e, s) { |
| + return s; |
| + } |
| +} |
| + |
| +dPrint(str) {} //=> print(str); |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
Remove this?
Name is bad in any case.
floitsch
2013/05/21 18:08:32
Yes. was for debugging. Removed.
|
| + |
| +/** |
| + * 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. |
| + */ |
| +class _Zone { |
| + /// The currently running zone. |
| + static _Zone _current = new _DefaultZone(); |
| + |
| + /// 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 probably be a linked list. |
| + final List<_Zone> _children = <_Zone>[]; |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
If a zone can only be a member of one zone, then y
floitsch
2013/05/21 18:08:32
Kept TODO for now, but we should decide how we wan
|
| + |
| + /// 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; |
| + |
| + static _Zone get current => _current; |
| + |
| + _Zone(this._parentZone) { |
| + assert(_parentZone != null || this is _DefaultZone); |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
You could make _DefaultZone implement _Zone instea
floitsch
2013/05/21 18:08:32
There is a lot of code that is the same (dealing w
|
| + if (_parentZone != null) { |
| + _parentZone._children.add(this); |
| + } |
| + } |
| + |
| + /// 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 => _parentZone._errorZone; |
| + |
| + void handleUncaughtError(error) { |
| + _parentZone.handleUncaughtError(error); |
| + } |
| + |
| + /** |
| + * Returns true if `this` and [otherZone] are in the same error zone. |
| + */ |
| + bool inSameErrorZone(_Zone otherZone) { |
| + return _errorZone == otherZone._errorZone; |
| + } |
| + |
| + /** |
| + * 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() { |
| + // dPrint(_stackTrace); |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
Commented code.
floitsch
2013/05/21 18:08:32
Done.
|
| + return this; |
| + } |
| + |
| + /** |
| + * Increments the open-callback counter. |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
Increments the number of open callbacks.
As long
floitsch
2013/05/21 18:08:32
Done.
|
| + * |
| + * As long as the counter is not 0, the zone is considered to be running. |
| + */ |
| + incrementOpenCallbackCount() => _openCallbacks++; |
| + |
| + /** |
| + * Decrements the open-callback counter. |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
Decrements the number of open callbacks.
When the
floitsch
2013/05/21 18:08:32
reworded. not exactly as you proposed, though.
|
| + * |
| + * If the counter reaches 0, the zone is considered to be done and expects |
| + * no more code to be run in the zone. In most cases it is better and easier |
| + * to call [executeCallback] instead. |
| + */ |
| + decrementOpenCallbackCount() { |
| + _openCallbacks--; |
| + _checkIfDone(); |
| + } |
| + |
| + /** |
| + * Cleans up when the zone is done. |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
Cleans up this zone when it is done.
This release
floitsch
2013/05/21 18:08:32
Done.
|
| + * |
| + * A zone is done when its dynamic extent has finished executing and there |
| + * are no outstanding asynchronous callbacks. |
| + */ |
| + _onDone() { |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
Don't call it _onDone when it's not a generic over
floitsch
2013/05/21 18:08:32
Done.
|
| + if (_parentZone != null) { |
| + _parentZone._children.remove(this); |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
Ok, double-linked it is. And don't act on other ob
floitsch
2013/05/21 18:08:32
added _addChild and _removeChild.
Kept the list (a
|
| + } |
| + } |
| + |
| + void _checkIfDone() { |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
Document when this is called.
"After any operation
floitsch
2013/05/21 18:08:32
Done.
|
| + if (_openCallbacks == 0 && _children.isEmpty) { |
| + dPrint("*/- done $this"); |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
debug code.
floitsch
2013/05/21 18:08:32
Done.
|
| + _onDone(); |
| + } |
| + } |
| + |
| + /** |
| + * Executes the given callback in this zone. |
| + * |
| + * Decrements the open-callback counter and checks (after the call) if the |
| + * zone is done. |
| + */ |
| + executeCallback(void fun()) { |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
void return type.
floitsch
2013/05/21 18:08:32
Done.
|
| + _openCallbacks--; |
| + dPrint("callbacks: $_openCallbacks"); |
| + _runInZone(fun); |
| + } |
| + |
| + /** |
| + * Same as [executeCallback] but doesn't decrement the open-callback counter. |
| + */ |
| + executePeriodicCallback(void fun()) { |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
void return type. Check for more yourself.
floitsch
2013/05/21 18:08:32
Done.
|
| + _runInZone(fun); |
| + } |
| + |
| + _runInZone(void fun()) { |
| + if (_current == this && _openCallbacks != 0) return fun(); |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
Consider using identical instead of ==.
Just in ca
floitsch
2013/05/21 18:08:32
Done.
|
| + return _runGuarded(fun); |
| + } |
| + |
| + _runGuarded(void fun()) { |
| + _Zone oldZone = _current; |
| + _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--; |
| + _current = oldZone; |
| + _checkIfDone(); |
| + } |
| + } |
| + |
| + runAsync(void fun()) { |
| + _openCallbacks++; |
| + _scheduleAsyncCallback(() { |
| + _openCallbacks--; |
| + try { |
| + _runInZone(fun); |
| + } catch(e, s) { |
| + handleUncaughtError(_asyncError(e, s)); |
| + } |
| + }); |
| + } |
| + |
| + // TODO(floitsch): for debugging only. Should be removed before committing. |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
Reminder to do todo.
floitsch
2013/05/21 18:08:32
Done.
|
| + String toString() => "Zone $_id"; |
| + final int _id = _idCounter++; |
| + static int _idCounter = 0; |
| +} |
| + |
| +/** |
| + * The default-zone that conceptually surrounds the `main` function. |
| + */ |
| +class _DefaultZone extends _Zone { |
| + _DefaultZone() : super(null); |
| + |
| + _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 _Zone { |
| + 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 runWait(void fun()) { |
| + if (fun == null) dPrint(_stackTrace); |
| + _runInZone(() { |
| + try { |
| + fun(); |
| + } catch (e, s) { |
| + handleUncaughtError(_asyncError(e, s)); |
| + } |
| + }); |
| + return _doneCompleter.future; |
| + } |
| + |
| + _onDone() { |
| + super._onDone(); |
| + dPrint("-*- $this"); |
| + _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) { |
| + dPrint("WithError error: $error"); |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
debug code.
floitsch
2013/05/21 18:08:32
Done.
|
| + errorsController.add(error); |
| + } |
| + |
| + Future runWait(void fun()) { |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
Name is not very telling. I.e., I would have no id
floitsch
2013/05/21 18:08:32
Changed to runWaitforCompletion.
|
| + super.runWait(fun).whenComplete(() { |
| + dPrint("closing"); |
|
Lasse Reichstein Nielsen
2013/05/21 08:19:57
debug code.
floitsch
2013/05/21 18:08:32
Done.
|
| + errorsController.close(); |
| + }); |
| + dPrint("zone: ${_Zone._current}"); |
| + } |
| + |
| + String toString() => "WithErrors ${super.toString()}"; |
| +} |
| + |
| +Stream catchErrors(void body()) { |
| + _Zone catchErrorsZone = new _CatchErrorsZone(_Zone._current); |
| + catchErrorsZone.runWait(body); |
| + return catchErrorsZone.errors; |
| +} |
| + |
| +Future waitForCompletion(void body()) { |
| + _Zone zone = new _WaitForCompletionZone(_Zone._current); |
| + return zone.runWait(body); |
| +} |