Add tasks to zones.

R=lrn@google.com, misko@google.com

Review URL: https://codereview.chromium.org/1848933002 .
This commit is contained in:
Florian Loitsch 2016-07-01 19:42:22 -07:00
parent 2a2c3c9b33
commit 85cccde717
4 changed files with 1320 additions and 77 deletions

View file

@ -4,6 +4,84 @@
part of dart.async;
abstract class _TimerTask implements Timer {
final Zone _zone;
final Timer _nativeTimer;
_TimerTask(this._nativeTimer, this._zone);
void cancel() {
_nativeTimer.cancel();
}
bool get isActive => _nativeTimer.isActive;
}
class _SingleShotTimerTask extends _TimerTask {
// TODO(floitsch): the generic argument should be 'void'.
final ZoneCallback<dynamic> _callback;
_SingleShotTimerTask(Timer timer, this._callback, Zone zone)
: super(timer, zone);
}
class _PeriodicTimerTask extends _TimerTask {
// TODO(floitsch): the first generic argument should be 'void'.
final ZoneUnaryCallback<dynamic, Timer> _callback;
_PeriodicTimerTask(Timer timer, this._callback, Zone zone)
: super(timer, zone);
}
/**
* A task specification for a single-shot timer.
*
* *Experimental*. Might disappear without notice.
*/
class SingleShotTimerTaskSpecification implements TaskSpecification {
static const String specificationName = "dart.async.timer";
/** The duration after which the timer should invoke the [callback]. */
final Duration duration;
/** The callback that should be run when the timer triggers. */
// TODO(floitsch): the generic argument should be void.
final ZoneCallback<dynamic> callback;
SingleShotTimerTaskSpecification(this.duration, void this.callback());
@override
String get name => specificationName;
@override
bool get isOneShot => true;
}
/**
* A task specification for a periodic timer.
*
* *Experimental*. Might disappear without notice.
*/
class PeriodicTimerTaskSpecification implements TaskSpecification {
static const String specificationName = "dart.async.periodic-timer";
/** The interval at which the periodic timer should invoke the [callback]. */
final Duration duration;
/** The callback that should be run when the timer triggers. */
// TODO(floitsch): the first generic argument should be void.
final ZoneUnaryCallback<dynamic, Timer> callback;
PeriodicTimerTaskSpecification(
this.duration, void this.callback(Timer timer));
@override
String get name => specificationName;
@override
bool get isOneShot => false;
}
/**
* A count-down timer that can be configured to fire once or repeatedly.
*
@ -47,10 +125,15 @@ abstract class Timer {
if (Zone.current == Zone.ROOT) {
// No need to bind the callback. We know that the root's timer will
// be invoked in the root zone.
return Zone.current.createTimer(duration, callback);
return Timer._createTimer(duration, callback);
}
return Zone.current.createTimer(
duration, Zone.current.bindCallback(callback, runGuarded: true));
return Zone.current.createTimer(duration, callback);
}
factory Timer._task(Zone zone, Duration duration, void callback()) {
SingleShotTimerTaskSpecification specification =
new SingleShotTimerTaskSpecification(duration, callback);
return zone.createTask(_createSingleShotTimerTask, specification);
}
/**
@ -70,17 +153,65 @@ abstract class Timer {
* scheduled for - even if the actual callback was delayed.
*/
factory Timer.periodic(Duration duration,
void callback(Timer timer)) {
void callback(Timer timer)) {
if (Zone.current == Zone.ROOT) {
// No need to bind the callback. We know that the root's timer will
// be invoked in the root zone.
return Zone.current.createPeriodicTimer(duration, callback);
return Timer._createPeriodicTimer(duration, callback);
}
return Zone.current.createPeriodicTimer(duration, callback);
}
factory Timer._periodicTask(Zone zone, Duration duration,
void callback(Timer timer)) {
PeriodicTimerTaskSpecification specification =
new PeriodicTimerTaskSpecification(duration, callback);
return zone.createTask(_createPeriodicTimerTask, specification);
}
static Timer _createSingleShotTimerTask(
SingleShotTimerTaskSpecification specification, Zone zone) {
ZoneCallback registeredCallback = identical(_ROOT_ZONE, zone)
? specification.callback
: zone.registerCallback(specification.callback);
_TimerTask timerTask;
Timer nativeTimer = Timer._createTimer(specification.duration, () {
timerTask._zone.runTask(_runSingleShotCallback, timerTask, null);
});
timerTask = new _SingleShotTimerTask(nativeTimer, registeredCallback, zone);
return timerTask;
}
static void _runSingleShotCallback(_SingleShotTimerTask timerTask, Object _) {
timerTask._callback();
}
static Timer _createPeriodicTimerTask(
PeriodicTimerTaskSpecification specification, Zone zone) {
// TODO(floitsch): the return type should be 'void', and the type
// should be inferred.
var boundCallback = Zone.current.bindUnaryCallback/*<dynamic, Timer>*/(
callback, runGuarded: true);
return Zone.current.createPeriodicTimer(duration, boundCallback);
ZoneUnaryCallback<dynamic, Timer> registeredCallback =
identical(_ROOT_ZONE, zone)
? specification.callback
: zone.registerUnaryCallback/*<dynamic, Timer>*/(
specification.callback);
_TimerTask timerTask;
Timer nativeTimer =
Timer._createPeriodicTimer(specification.duration, (Timer _) {
timerTask._zone.runTask(_runPeriodicCallback, timerTask, null);
});
timerTask = new _PeriodicTimerTask(nativeTimer, registeredCallback, zone);
return timerTask;
}
static void _runPeriodicCallback(_PeriodicTimerTask timerTask, Object _) {
timerTask._callback(timerTask);
}
/**

View file

@ -8,6 +8,13 @@ typedef R ZoneCallback<R>();
typedef R ZoneUnaryCallback<R, T>(T arg);
typedef R ZoneBinaryCallback<R, T1, T2>(T1 arg1, T2 arg2);
/// *Experimental*. Might disappear without warning.
typedef T TaskCreate<T, S extends TaskSpecification>(
S specification, Zone zone);
/// *Experimental*. Might disappear without warning.
typedef void TaskRun<T, A>(T task, A arg);
// TODO(floitsch): we are abusing generic typedefs as typedefs for generic
// functions.
/*ABUSE*/
@ -33,19 +40,31 @@ typedef ZoneBinaryCallback<R, T1, T2> RegisterBinaryCallbackHandler<R, T1, T2>(
Zone self, ZoneDelegate parent, Zone zone, R f(T1 arg1, T2 arg2));
typedef AsyncError ErrorCallbackHandler(Zone self, ZoneDelegate parent,
Zone zone, Object error, StackTrace stackTrace);
/// *Experimental*. Might disappear without warning.
/*ABUSE*/
typedef T CreateTaskHandler<T, S extends TaskSpecification>(
Zone self, ZoneDelegate parent, Zone zone,
TaskCreate<T, S> create, S taskSpecification);
/// *Experimental*. Might disappear without warning.
/*ABUSE*/
typedef void RunTaskHandler<T, A>(Zone self, ZoneDelegate parent, Zone zone,
TaskRun<T, A> run, T task, A arg);
typedef void ScheduleMicrotaskHandler(
Zone self, ZoneDelegate parent, Zone zone, void f());
typedef Timer CreateTimerHandler(
Zone self, ZoneDelegate parent, Zone zone, Duration duration, void f());
typedef Timer CreatePeriodicTimerHandler(
Zone self, ZoneDelegate parent, Zone zone,
Duration period, void f(Timer timer));
typedef void PrintHandler(
Zone self, ZoneDelegate parent, Zone zone, String line);
typedef Zone ForkHandler(Zone self, ZoneDelegate parent, Zone zone,
ZoneSpecification specification,
Map zoneValues);
// The following typedef declarations are used by functionality which
// will be removed and replaced by tasksif the task experiment is successful.
typedef Timer CreateTimerHandler(
Zone self, ZoneDelegate parent, Zone zone, Duration duration, void f());
typedef Timer CreatePeriodicTimerHandler(
Zone self, ZoneDelegate parent, Zone zone,
Duration period, void f(Timer timer));
/** Pair of error and stack trace. Returned by [Zone.errorCallback]. */
class AsyncError implements Error {
final Object error;
@ -56,10 +75,41 @@ class AsyncError implements Error {
String toString() => '$error';
}
/**
* A task specification contains the necessary information to create a task.
*
* See [Zone.createTask] for how a specification is used to create a task.
*
* Task specifications should be public and it should be possible to create
* new instances as a user. That is, custom zones should be able to replace
* an existing specification with a modified one.
*
* *Experimental*. This class might disappear without warning.
*/
abstract class TaskSpecification {
/**
* Description of the task.
*
* This string is unused by the root-zone, but might be used for debugging,
* and testing. As such, it should be relatively unique in its category.
*
* As a general guideline we recommend: "package-name.library.action".
*/
String get name;
/**
* Whether the scheduled task triggers at most once.
*
* If the task is not a one-shot task, it may need to be canceled to prevent
* further iterations of the task.
*/
bool get isOneShot;
}
class _ZoneFunction<T extends Function> {
final _Zone zone;
final T function;
const _ZoneFunction(this.zone, this.function);
}
@ -85,6 +135,9 @@ class _ZoneFunction<T extends Function> {
abstract class ZoneSpecification {
/**
* Creates a specification with the provided handlers.
*
* The task-related parameters ([createTask] and [runTask]) are experimental
* and might be removed without warning.
*/
const factory ZoneSpecification({
HandleUncaughtErrorHandler handleUncaughtError,
@ -96,7 +149,11 @@ abstract class ZoneSpecification {
RegisterBinaryCallbackHandler registerBinaryCallback,
ErrorCallbackHandler errorCallback,
ScheduleMicrotaskHandler scheduleMicrotask,
CreateTaskHandler createTask,
RunTaskHandler runTask,
// TODO(floitsch): mark as deprecated once tasks are non-experimental.
CreateTimerHandler createTimer,
// TODO(floitsch): mark as deprecated once tasks are non-experimental.
CreatePeriodicTimerHandler createPeriodicTimer,
PrintHandler print,
ForkHandler fork
@ -105,6 +162,9 @@ abstract class ZoneSpecification {
/**
* Creates a specification from [other] with the provided handlers overriding
* the ones in [other].
*
* The task-related parameters ([createTask] and [runTask]) are experimental
* and might be removed without warning.
*/
factory ZoneSpecification.from(ZoneSpecification other, {
HandleUncaughtErrorHandler handleUncaughtError: null,
@ -116,7 +176,11 @@ abstract class ZoneSpecification {
RegisterBinaryCallbackHandler registerBinaryCallback: null,
ErrorCallbackHandler errorCallback: null,
ScheduleMicrotaskHandler scheduleMicrotask: null,
CreateTaskHandler createTask: null,
RunTaskHandler runTask: null,
// TODO(floitsch): mark as deprecated once tasks are non-experimental.
CreateTimerHandler createTimer: null,
// TODO(floitsch): mark as deprecated once tasks are non-experimental.
CreatePeriodicTimerHandler createPeriodicTimer: null,
PrintHandler print: null,
ForkHandler fork: null
@ -132,11 +196,14 @@ abstract class ZoneSpecification {
registerBinaryCallback: registerBinaryCallback ??
other.registerBinaryCallback,
errorCallback: errorCallback ?? other.errorCallback,
createTask: createTask ?? other.createTask,
runTask: runTask ?? other.runTask,
print : print ?? other.print,
fork: fork ?? other.fork,
scheduleMicrotask: scheduleMicrotask ?? other.scheduleMicrotask,
createTimer : createTimer ?? other.createTimer,
createPeriodicTimer: createPeriodicTimer ?? other.createPeriodicTimer,
print : print ?? other.print,
fork: fork ?? other.fork);
createPeriodicTimer: createPeriodicTimer ?? other.createPeriodicTimer);
}
HandleUncaughtErrorHandler get handleUncaughtError;
@ -148,10 +215,17 @@ abstract class ZoneSpecification {
RegisterBinaryCallbackHandler get registerBinaryCallback;
ErrorCallbackHandler get errorCallback;
ScheduleMicrotaskHandler get scheduleMicrotask;
CreateTimerHandler get createTimer;
CreatePeriodicTimerHandler get createPeriodicTimer;
/// *Experimental*. Might disappear without warning.
CreateTaskHandler get createTask;
/// *Experimental*. Might disappear without warning.
RunTaskHandler get runTask;
PrintHandler get print;
ForkHandler get fork;
// TODO(floitsch): deprecate once tasks are non-experimental.
CreateTimerHandler get createTimer;
// TODO(floitsch): deprecate once tasks are non-experimental.
CreatePeriodicTimerHandler get createPeriodicTimer;
}
/**
@ -172,10 +246,14 @@ class _ZoneSpecification implements ZoneSpecification {
this.registerBinaryCallback: null,
this.errorCallback: null,
this.scheduleMicrotask: null,
this.createTimer: null,
this.createPeriodicTimer: null,
this.createTask: null,
this.runTask: null,
this.print: null,
this.fork: null
this.fork: null,
// TODO(floitsch): deprecate once tasks are non-experimental.
this.createTimer: null,
// TODO(floitsch): deprecate once tasks are non-experimental.
this.createPeriodicTimer: null
});
final HandleUncaughtErrorHandler handleUncaughtError;
@ -187,10 +265,15 @@ class _ZoneSpecification implements ZoneSpecification {
final RegisterBinaryCallbackHandler registerBinaryCallback;
final ErrorCallbackHandler errorCallback;
final ScheduleMicrotaskHandler scheduleMicrotask;
final CreateTimerHandler createTimer;
final CreatePeriodicTimerHandler createPeriodicTimer;
final CreateTaskHandler createTask;
final RunTaskHandler runTask;
final PrintHandler print;
final ForkHandler fork;
// TODO(floitsch): deprecate once tasks are non-experimental.
final CreateTimerHandler createTimer;
// TODO(floitsch): deprecate once tasks are non-experimental.
final CreatePeriodicTimerHandler createPeriodicTimer;
}
/**
@ -217,10 +300,23 @@ abstract class ZoneDelegate {
Zone zone, /*=R*/ f(/*=T1*/ arg1, /*=T2*/ arg2));
AsyncError errorCallback(Zone zone, Object error, StackTrace stackTrace);
void scheduleMicrotask(Zone zone, void f());
Timer createTimer(Zone zone, Duration duration, void f());
Timer createPeriodicTimer(Zone zone, Duration period, void f(Timer timer));
/// *Experimental*. Might disappear without notice.
Object/*=T*/ createTask/*<T, S extends TaskSpecification>*/(
Zone zone, TaskCreate/*<T, S>*/ create,
TaskSpecification/*=S*/ specification);
/// *Experimental*. Might disappear without notice.
void runTask/*<T, A>*/(
Zone zone, TaskRun/*<T, A>*/ run, Object/*=T*/ task,
Object/*=A*/ argument);
void print(Zone zone, String line);
Zone fork(Zone zone, ZoneSpecification specification, Map zoneValues);
// TODO(floitsch): deprecate once tasks are non-experimental.
Timer createTimer(Zone zone, Duration duration, void f());
// TODO(floitsch): deprecate once tasks are non-experimental.
Timer createPeriodicTimer(Zone zone, Duration period, void f(Timer timer));
}
/**
@ -410,14 +506,101 @@ abstract class Zone {
*/
void scheduleMicrotask(void f());
/**
* Creates a task in the current zone.
*
* A task represents an asynchronous operation or process that reports back
* through the event loop.
*
* This function allows the zone to intercept the initialization of the
* task while the [runTask] function is invoked when the task reports back.
*
* 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 between the zone and the task. The object is
* a unique instance representing the task. It is generally returned to
* whoever initiated the task.
* For example, the HTML library uses the returned [StreamSubscription] as
* task object when users register an event listener.
*
* Tasks are created when the program starts an operation that reports back
* 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.
*
* A task goes through the following steps:
* - a user invokes a library function that should eventually return through
* the event loop.
* - 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 underlying system, or 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 in which the task should run
* (and which was originally passed to the `create` function by
* `createTask`). 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. 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. An operation that wishes to be an
* interceptable task must publicly specify the types that intercepting code
* sees:
* - The specification type (extending [TaskSpecification]) which holds the
* information available when intercepting the `createTask` call.
* - The task object type, returned by `createTask` and [create]. This object
* may simply be typed as [Object].
* - The argument type, if [runTask] takes a meaningful argument.
*
* *Experimental*. Might disappear without notice.
*/
Object/*=T*/ createTask/*<T, S extends TaskSpecification>*/(
/*=T*/ create(TaskSpecification/*=S*/ specification, Zone zone),
TaskSpecification/*=S*/ specification);
/**
* Runs a task callback.
*
* This function is invoked, when an operation, started through [createTask],
* generates an event.
*
* Generally, tasks schedule Dart code in the global event loop when the
* [createTask] function is invoked. Since the
* event loop does not expect any return value from the code it runs, the
* [runTask] function is a void function.
*
* The [task] object must be the same as the one created with [createTask].
*
* It is good practice that task operations provide a meaningful [argument],
* so that custom zones can interact with it. They might want to log or
* replace the argument before calling the [run] function.
*
* *Experimental*. Might disappear without notice.
*/
void runTask/*<T, A>*/(
/*=T*/ run(/*=T*/ task, /*=A*/ argument), Object/*=T*/ task,
Object/*=A*/ argument);
/**
* Creates a Timer where the callback is executed in this zone.
*/
// TODO(floitsch): deprecate once tasks are non-experimental.
Timer createTimer(Duration duration, void callback());
/**
* Creates a periodic Timer where the callback is executed in this zone.
*/
// TODO(floitsch): deprecate once tasks are non-experimental.
Timer createPeriodicTimer(Duration period, void callback(Timer timer));
/**
@ -523,7 +706,7 @@ class _ZoneDelegate implements ZoneDelegate {
// TODO(floitsch): make this a generic method call on '<R>' once it's
// supported. Remove the unnecessary cast.
return handler(implZone, _parentDelegate(implZone), zone, f)
as Object/*=ZoneCallback<R>*/;
as dynamic/*=ZoneCallback<R>*/;
}
ZoneUnaryCallback/*<R, T>*/ registerUnaryCallback/*<R, T>*/(
@ -534,7 +717,7 @@ class _ZoneDelegate implements ZoneDelegate {
// TODO(floitsch): make this a generic method call on '<R, T>' once it's
// supported. Remove the unnecessary cast.
return handler(implZone, _parentDelegate(implZone), zone, f)
as Object/*=ZoneUnaryCallback<R, T>*/;
as dynamic/*=ZoneUnaryCallback<R, T>*/;
}
ZoneBinaryCallback/*<R, T1, T2>*/ registerBinaryCallback/*<R, T1, T2>*/(
@ -545,7 +728,7 @@ class _ZoneDelegate implements ZoneDelegate {
// TODO(floitsch): make this a generic method call on '<R, T1, T2>' once
// it's supported. Remove the unnecessary cast.
return handler(implZone, _parentDelegate(implZone), zone, f)
as Object/*=ZoneBinaryCallback<R, T1, T2>*/;
as dynamic/*=ZoneBinaryCallback<R, T1, T2>*/;
}
AsyncError errorCallback(Zone zone, Object error, StackTrace stackTrace) {
@ -564,18 +747,25 @@ class _ZoneDelegate implements ZoneDelegate {
handler(implZone, _parentDelegate(implZone), zone, f);
}
Timer createTimer(Zone zone, Duration duration, void f()) {
var implementation = _delegationTarget._createTimer;
Object/*=T*/ createTask/*<T, S extends TaskSpecification>*/(
Zone zone, TaskCreate/*<T, S>*/ create, TaskSpecification/*=S*/ specification) {
var implementation = _delegationTarget._createTask;
_Zone implZone = implementation.zone;
CreateTimerHandler handler = implementation.function;
return handler(implZone, _parentDelegate(implZone), zone, duration, f);
// TODO(floitsch): make the handler call a generic method call on '<T, S>'
// once it's supported. Remove the unnecessary cast.
var handler =
implementation.function as CreateTaskHandler/*<T, S>*/;
return handler(
implZone, _parentDelegate(implZone), zone, create, specification);
}
Timer createPeriodicTimer(Zone zone, Duration period, void f(Timer timer)) {
var implementation = _delegationTarget._createPeriodicTimer;
void runTask/*<T, A>*/(Zone zone, TaskRun run, Object /*=T*/ task,
Object /*=A*/ argument) {
var implementation = _delegationTarget._runTask;
_Zone implZone = implementation.zone;
CreatePeriodicTimerHandler handler = implementation.function;
return handler(implZone, _parentDelegate(implZone), zone, period, f);
RunTaskHandler handler = implementation.function;
// TODO(floitsch): make this a generic call on '<T, A>'.
handler(implZone, _parentDelegate(implZone), zone, run, task, argument);
}
void print(Zone zone, String line) {
@ -593,6 +783,22 @@ class _ZoneDelegate implements ZoneDelegate {
return handler(
implZone, _parentDelegate(implZone), zone, specification, zoneValues);
}
// TODO(floitsch): deprecate once tasks are non-experimental.
Timer createTimer(Zone zone, Duration duration, void f()) {
var implementation = _delegationTarget._createTimer;
_Zone implZone = implementation.zone;
CreateTimerHandler handler = implementation.function;
return handler(implZone, _parentDelegate(implZone), zone, duration, f);
}
// TODO(floitsch): deprecate once tasks are non-experimental.
Timer createPeriodicTimer(Zone zone, Duration period, void f(Timer timer)) {
var implementation = _delegationTarget._createPeriodicTimer;
_Zone implZone = implementation.zone;
CreatePeriodicTimerHandler handler = implementation.function;
return handler(implZone, _parentDelegate(implZone), zone, period, f);
}
}
@ -610,11 +816,17 @@ abstract class _Zone implements Zone {
_ZoneFunction<RegisterBinaryCallbackHandler> get _registerBinaryCallback;
_ZoneFunction<ErrorCallbackHandler> get _errorCallback;
_ZoneFunction<ScheduleMicrotaskHandler> get _scheduleMicrotask;
_ZoneFunction<CreateTimerHandler> get _createTimer;
_ZoneFunction<CreatePeriodicTimerHandler> get _createPeriodicTimer;
_ZoneFunction<CreateTaskHandler> get _createTask;
_ZoneFunction<RunTaskHandler> get _runTask;
_ZoneFunction<PrintHandler> get _print;
_ZoneFunction<ForkHandler> get _fork;
_ZoneFunction<HandleUncaughtErrorHandler> get _handleUncaughtError;
// TODO(floitsch): deprecate once tasks are non-experimental.
_ZoneFunction<CreateTimerHandler> get _createTimer;
// TODO(floitsch): deprecate once tasks are non-experimental.
_ZoneFunction<CreatePeriodicTimerHandler> get _createPeriodicTimer;
_Zone get parent;
ZoneDelegate get _delegate;
Map get _map;
@ -636,12 +848,17 @@ class _CustomZone extends _Zone {
_ZoneFunction<RegisterBinaryCallbackHandler> _registerBinaryCallback;
_ZoneFunction<ErrorCallbackHandler> _errorCallback;
_ZoneFunction<ScheduleMicrotaskHandler> _scheduleMicrotask;
_ZoneFunction<CreateTimerHandler> _createTimer;
_ZoneFunction<CreatePeriodicTimerHandler> _createPeriodicTimer;
_ZoneFunction<CreateTaskHandler> _createTask;
_ZoneFunction<RunTaskHandler> _runTask;
_ZoneFunction<PrintHandler> _print;
_ZoneFunction<ForkHandler> _fork;
_ZoneFunction<HandleUncaughtErrorHandler> _handleUncaughtError;
// TODO(floitsch): deprecate once tasks are non-experimental.
_ZoneFunction<CreateTimerHandler> _createTimer;
// TODO(floitsch): deprecate once tasks are non-experimental.
_ZoneFunction<CreatePeriodicTimerHandler> _createPeriodicTimer;
// A cached delegate to this zone.
ZoneDelegate _delegateCache;
@ -692,13 +909,14 @@ class _CustomZone extends _Zone {
? new _ZoneFunction<ScheduleMicrotaskHandler>(
this, specification.scheduleMicrotask)
: parent._scheduleMicrotask;
_createTimer = (specification.createTimer != null)
? new _ZoneFunction<CreateTimerHandler>(this, specification.createTimer)
: parent._createTimer;
_createPeriodicTimer = (specification.createPeriodicTimer != null)
? new _ZoneFunction<CreatePeriodicTimerHandler>(
this, specification.createPeriodicTimer)
: parent._createPeriodicTimer;
_createTask = (specification.createTask != null)
? new _ZoneFunction<CreateTaskHandler>(
this, specification.createTask)
: parent._createTask;
_runTask = (specification.runTask != null)
? new _ZoneFunction<RunTaskHandler>(
this, specification.runTask)
: parent._runTask;
_print = (specification.print != null)
? new _ZoneFunction<PrintHandler>(this, specification.print)
: parent._print;
@ -709,6 +927,16 @@ class _CustomZone extends _Zone {
? new _ZoneFunction<HandleUncaughtErrorHandler>(
this, specification.handleUncaughtError)
: parent._handleUncaughtError;
// Deprecated fields, once tasks are non-experimental.
_createTimer = (specification.createTimer != null)
? new _ZoneFunction<CreateTimerHandler>(
this, specification.createTimer)
: parent._createTimer;
_createPeriodicTimer = (specification.createPeriodicTimer != null)
? new _ZoneFunction<CreatePeriodicTimerHandler>(
this, specification.createPeriodicTimer)
: parent._createPeriodicTimer;
}
/**
@ -859,7 +1087,7 @@ class _CustomZone extends _Zone {
// TODO(floitsch): make this a generic method call on '<R>' once it's
// supported. Remove the unnecessary cast.
return handler(implementation.zone, parentDelegate, this, callback)
as Object/*=ZoneCallback<R>*/;
as dynamic/*=ZoneCallback<R>*/;
}
ZoneUnaryCallback/*<R, T>*/ registerUnaryCallback/*<R, T>*/(
@ -871,7 +1099,7 @@ class _CustomZone extends _Zone {
// TODO(floitsch): make this a generic method call on '<R, T>' once it's
// supported. Remove the unnecessary cast.
return handler(implementation.zone, parentDelegate, this, callback)
as Object/*=ZoneUnaryCallback<R, T>*/;
as dynamic/*=ZoneUnaryCallback<R, T>*/;
}
ZoneBinaryCallback/*<R, T1, T2>*/ registerBinaryCallback/*<R, T1, T2>*/(
@ -883,7 +1111,7 @@ class _CustomZone extends _Zone {
// TODO(floitsch): make this a generic method call on '<R, T1, T2>' once
// it's supported. Remove the unnecessary cast.
return handler(implementation.zone, parentDelegate, this, callback)
as Object/*=ZoneBinaryCallback<R, T1, T2>*/;
as dynamic/*=ZoneBinaryCallback<R, T1, T2>*/;
}
AsyncError errorCallback(Object error, StackTrace stackTrace) {
@ -902,24 +1130,29 @@ class _CustomZone extends _Zone {
assert(implementation != null);
ZoneDelegate parentDelegate = _parentDelegate(implementation.zone);
ScheduleMicrotaskHandler handler = implementation.function;
return handler(implementation.zone, parentDelegate, this, f);
handler(implementation.zone, parentDelegate, this, f);
}
Timer createTimer(Duration duration, void f()) {
var implementation = this._createTimer;
assert(implementation != null);
Object/*=T*/ createTask/*<T, S extends TaskSpecification>*/(
TaskCreate/*<T, S>*/ create, TaskSpecification/*=S*/ specification) {
var implementation = this._createTask;
ZoneDelegate parentDelegate = _parentDelegate(implementation.zone);
CreateTimerHandler handler = implementation.function;
return handler(implementation.zone, parentDelegate, this, duration, f);
}
Timer createPeriodicTimer(Duration duration, void f(Timer timer)) {
var implementation = this._createPeriodicTimer;
assert(implementation != null);
ZoneDelegate parentDelegate = _parentDelegate(implementation.zone);
CreatePeriodicTimerHandler handler = implementation.function;
// TODO(floitsch): make the handler call a generic method call on '<T, S>'
// once it's supported. Remove the unnecessary cast.
var handler =
implementation.function as CreateTaskHandler/*<T, S>*/;
return handler(
implementation.zone, parentDelegate, this, duration, f);
implementation.zone, parentDelegate, this, create, specification);
}
void runTask/*<T, A>*/(
TaskRun/*<T, A>*/ run, Object/*=T*/ task, Object/*=A*/ arg1) {
var implementation = this._runTask;
ZoneDelegate parentDelegate = _parentDelegate(implementation.zone);
RunTaskHandler handler = implementation.function;
// TODO(floitsch): make this a generic method call on '<T, A>' once it's
// supported.
handler(implementation.zone, parentDelegate, this, run, task, arg1);
}
void print(String line) {
@ -929,6 +1162,25 @@ class _CustomZone extends _Zone {
PrintHandler handler = implementation.function;
return handler(implementation.zone, parentDelegate, this, line);
}
// TODO(floitsch): deprecate once tasks are non-experimental.
Timer createTimer(Duration duration, void f()) {
var implementation = this._createTimer;
assert(implementation != null);
ZoneDelegate parentDelegate = _parentDelegate(implementation.zone);
CreateTimerHandler handler = implementation.function;
return handler(implementation.zone, parentDelegate, this, duration, f);
}
// TODO(floitsch): deprecate once tasks are non-experimental.
Timer createPeriodicTimer(Duration duration, void f(Timer timer)) {
var implementation = this._createPeriodicTimer;
assert(implementation != null);
ZoneDelegate parentDelegate = _parentDelegate(implementation.zone);
CreatePeriodicTimerHandler handler = implementation.function;
return handler(
implementation.zone, parentDelegate, this, duration, f);
}
}
/*=R*/ _rootHandleUncaughtError/*<R>*/(
@ -1006,22 +1258,39 @@ void _rootScheduleMicrotask(Zone self, ZoneDelegate parent, Zone zone, f()) {
_scheduleAsyncCallback(f);
}
Object/*=T*/ _rootCreateTask/*<T, S extends TaskSpecification>*/(
Zone self, ZoneDelegate parent, Zone zone,
TaskCreate/*<T, S>*/ create, TaskSpecification/*=S*/ specification) {
return create(specification, zone);
}
void _rootRunTask/*<T, A>*/(
Zone self, ZoneDelegate parent, Zone zone, TaskRun run/*<T, A>*/,
Object/*=T*/ task, Object/*=A*/ arg) {
if (Zone._current == zone) {
run(task, arg);
return;
}
Zone old = Zone._enter(zone);
try {
run(task, arg);
} catch (e, s) {
zone.handleUncaughtError/*<dynamic>*/(e, s);
} finally {
Zone._leave(old);
}
}
Timer _rootCreateTimer(Zone self, ZoneDelegate parent, Zone zone,
Duration duration, void callback()) {
if (!identical(_ROOT_ZONE, zone)) {
callback = zone.bindCallback(callback);
}
return Timer._createTimer(duration, callback);
return new Timer._task(zone, duration, callback);
}
Timer _rootCreatePeriodicTimer(
Zone self, ZoneDelegate parent, Zone zone,
Duration duration, void callback(Timer timer)) {
if (!identical(_ROOT_ZONE, zone)) {
// TODO(floitsch): the return type should be 'void'.
callback = zone.bindUnaryCallback/*<dynamic, Timer>*/(callback);
}
return Timer._createPeriodicTimer(duration, callback);
return new Timer._periodicTask(zone, duration, callback);
}
void _rootPrint(Zone self, ZoneDelegate parent, Zone zone, String line) {
@ -1082,10 +1351,10 @@ class _RootZone extends _Zone {
_ZoneFunction<ScheduleMicrotaskHandler> get _scheduleMicrotask =>
const _ZoneFunction<ScheduleMicrotaskHandler>(
_ROOT_ZONE, _rootScheduleMicrotask);
_ZoneFunction<CreateTimerHandler> get _createTimer =>
const _ZoneFunction<CreateTimerHandler>(_ROOT_ZONE, _rootCreateTimer);
_ZoneFunction<CreatePeriodicTimerHandler> get _createPeriodicTimer =>
const _ZoneFunction<CreatePeriodicTimerHandler>(_ROOT_ZONE, _rootCreatePeriodicTimer);
_ZoneFunction<CreateTaskHandler> get _createTask =>
const _ZoneFunction<CreateTaskHandler>(_ROOT_ZONE, _rootCreateTask);
_ZoneFunction<RunTaskHandler> get _runTask =>
const _ZoneFunction<RunTaskHandler>(_ROOT_ZONE, _rootRunTask);
_ZoneFunction<PrintHandler> get _print =>
const _ZoneFunction<PrintHandler>(_ROOT_ZONE, _rootPrint);
_ZoneFunction<ForkHandler> get _fork =>
@ -1094,6 +1363,14 @@ class _RootZone extends _Zone {
const _ZoneFunction<HandleUncaughtErrorHandler>(
_ROOT_ZONE, _rootHandleUncaughtError);
// TODO(floitsch): deprecate once tasks are non-experimental.
_ZoneFunction<CreateTimerHandler> get _createTimer =>
const _ZoneFunction<CreateTimerHandler>(_ROOT_ZONE, _rootCreateTimer);
// TODO(floitsch): deprecate once tasks are non-experimental.
_ZoneFunction<CreatePeriodicTimerHandler> get _createPeriodicTimer =>
const _ZoneFunction<CreatePeriodicTimerHandler>(
_ROOT_ZONE, _rootCreatePeriodicTimer);
// The parent zone.
_Zone get parent => null;
@ -1225,6 +1502,16 @@ class _RootZone extends _Zone {
_rootScheduleMicrotask(null, null, this, f);
}
Object/*=T*/ createTask/*<T, S extends TaskSpecification>*/(
TaskCreate/*<T, S>*/ create, TaskSpecification/*=S*/ specification) {
return _rootCreateTask/*<T, S>*/(null, null, this, create, specification);
}
void runTask/*<T, A>*/(
TaskRun/*<T, A>*/ run, Object/*=T*/ task, Object/*=A*/ arg) {
_rootRunTask/*<T, A>*/(null, null, this, run, task, arg);
}
Timer createTimer(Duration duration, void f()) {
return Timer._createTimer(duration, f);
}

View file

@ -0,0 +1,310 @@
// Copyright (c) 2016, 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.
// Tests basic functionality of tasks in zones.
import 'package:expect/expect.dart';
import 'package:async_helper/async_helper.dart';
import 'dart:async';
List log = [];
class MySpecification extends TaskSpecification {
final Function callback;
final bool isOneShot;
final int value;
MySpecification(void this.callback(), this.isOneShot, this.value);
String get name => "test.specification-name";
}
class MyTask {
final Zone zone;
final Function callback;
final int id;
int invocationCount = 0;
bool shouldStop = false;
MyTask(this.zone, void this.callback(), this.id);
}
void runMyTask(MyTask task, int value) {
log.add("running "
"zone: ${Zone.current['name']} "
"task-id: ${task.id} "
"invocation-count: ${task.invocationCount} "
"value: $value");
task.callback();
task.invocationCount++;
}
MyTask createMyTask(MySpecification spec, Zone zone) {
var task = new MyTask(zone, spec.callback, spec.value);
log.add("creating task: ${spec.value} oneshot?: ${spec.isOneShot}");
if (spec.isOneShot) {
Timer.run(() {
zone.runTask(runMyTask, task, task.id);
});
} else {
new Timer.periodic(const Duration(milliseconds: 10), (Timer timer) {
zone.runTask(runMyTask, task, task.id);
if (task.shouldStop) {
timer.cancel();
}
});
}
return task;
}
MyTask startTask(f, bool oneShot, int value) {
var spec = new MySpecification(f, oneShot, value);
return Zone.current.createTask(createMyTask, spec);
}
/// Makes sure things are working in a simple setting.
/// No interceptions, changes, ...
Future testCustomTask() {
var testCompleter = new Completer();
asyncStart();
Object createTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
TaskCreate create, TaskSpecification specification) {
if (specification is MySpecification) {
log.add("create enter "
"zone: ${self['name']} "
"spec-value: ${specification.value} "
"spec-oneshot?: ${specification.isOneShot}");
MyTask result = parent.createTask(zone, create, specification);
log.add("create leave");
return result;
}
return parent.createTask(zone, create, specification);
}
void runTaskHandler(Zone self, ZoneDelegate parent, Zone zone, TaskRun run,
Object task, Object arg) {
if (task is MyTask) {
log.add("run enter "
"zone: ${self['name']} "
"task-id: ${task.id} "
"invocation-count: ${task.invocationCount} "
"arg: $arg");
parent.runTask(zone, run, task, arg);
log.add("run leave invocation-count: ${task.invocationCount}");
return;
}
parent.runTask(zone, run, task, arg);
}
runZoned(() async {
var completer0 = new Completer();
startTask(() {
completer0.complete("done");
}, true, 0);
await completer0.future;
Expect.listEquals([
'create enter zone: custom zone spec-value: 0 spec-oneshot?: true',
'creating task: 0 oneshot?: true',
'create leave',
'run enter zone: custom zone task-id: 0 invocation-count: 0 arg: 0',
'running zone: custom zone task-id: 0 invocation-count: 0 value: 0',
'run leave invocation-count: 1'
], log);
log.clear();
var completer1 = new Completer();
MyTask task1;
task1 = startTask(() {
if (task1.invocationCount == 1) {
task1.shouldStop = true;
completer1.complete("done");
}
}, false, 1);
await completer1.future;
Expect.listEquals([
'create enter zone: custom zone spec-value: 1 spec-oneshot?: false',
'creating task: 1 oneshot?: false',
'create leave',
'run enter zone: custom zone task-id: 1 invocation-count: 0 arg: 1',
'running zone: custom zone task-id: 1 invocation-count: 0 value: 1',
'run leave invocation-count: 1',
'run enter zone: custom zone task-id: 1 invocation-count: 1 arg: 1',
'running zone: custom zone task-id: 1 invocation-count: 1 value: 1',
'run leave invocation-count: 2',
], log);
log.clear();
testCompleter.complete("done");
asyncEnd();
},
zoneValues: {'name': 'custom zone'},
zoneSpecification: new ZoneSpecification(
createTask: createTaskHandler,
runTask: runTaskHandler));
return testCompleter.future;
}
/// More complicated zone, that intercepts...
Future testCustomTask2() {
var testCompleter = new Completer();
asyncStart();
Object createTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
TaskCreate create, TaskSpecification specification) {
if (specification is MySpecification) {
log.add("create enter "
"zone: ${self['name']} "
"spec-value: ${specification.value} "
"spec-oneshot?: ${specification.isOneShot}");
var replacement = new MySpecification(specification.callback,
specification.isOneShot, specification.value + 1);
MyTask result = parent.createTask(zone, create, replacement);
log.add("create leave");
return result;
}
return parent.createTask(zone, create, specification);
}
void runTaskHandler(Zone self, ZoneDelegate parent, Zone zone, TaskRun run,
Object task, Object arg) {
if (task is MyTask) {
log.add("run enter "
"zone: ${self['name']} "
"task-id: ${task.id} "
"invocation-count: ${task.invocationCount} "
"arg: $arg");
int value = arg;
parent.runTask(zone, run, task, value + 101);
log.add("run leave invocation-count: ${task.invocationCount}");
return;
}
parent.runTask(zone, run, task, arg);
}
runZoned(() async {
var completer0 = new Completer();
startTask(() {
completer0.complete("done");
}, true, 0);
await completer0.future;
Expect.listEquals([
'create enter zone: outer-zone spec-value: 0 spec-oneshot?: true',
'creating task: 1 oneshot?: true',
'create leave',
'run enter zone: outer-zone task-id: 1 invocation-count: 0 arg: 1',
'running zone: outer-zone task-id: 1 invocation-count: 0 value: 102',
'run leave invocation-count: 1'
], log);
log.clear();
var completer1 = new Completer();
MyTask task1;
task1 = startTask(() {
if (task1.invocationCount == 1) {
task1.shouldStop = true;
completer1.complete("done");
}
}, false, 1);
await completer1.future;
Expect.listEquals([
'create enter zone: outer-zone spec-value: 1 spec-oneshot?: false',
'creating task: 2 oneshot?: false',
'create leave',
'run enter zone: outer-zone task-id: 2 invocation-count: 0 arg: 2',
'running zone: outer-zone task-id: 2 invocation-count: 0 value: 103',
'run leave invocation-count: 1',
'run enter zone: outer-zone task-id: 2 invocation-count: 1 arg: 2',
'running zone: outer-zone task-id: 2 invocation-count: 1 value: 103',
'run leave invocation-count: 2',
], log);
log.clear();
var nestedCompleter = new Completer();
runZoned(() async {
var completer0 = new Completer();
startTask(() {
completer0.complete("done");
}, true, 0);
await completer0.future;
Expect.listEquals([
'create enter zone: inner-zone spec-value: 0 spec-oneshot?: true',
'create enter zone: outer-zone spec-value: 1 spec-oneshot?: true',
'creating task: 2 oneshot?: true',
'create leave',
'create leave',
'run enter zone: inner-zone task-id: 2 invocation-count: 0 arg: 2',
'run enter zone: outer-zone task-id: 2 invocation-count: 0 arg: 103',
'running zone: inner-zone task-id: 2 invocation-count: 0 value: 204',
'run leave invocation-count: 1',
'run leave invocation-count: 1'
], log);
log.clear();
var completer1 = new Completer();
MyTask task1;
task1 = startTask(() {
if (task1.invocationCount == 1) {
task1.shouldStop = true;
completer1.complete("done");
}
}, false, 1);
await completer1.future;
Expect.listEquals([
'create enter zone: inner-zone spec-value: 1 spec-oneshot?: false',
'create enter zone: outer-zone spec-value: 2 spec-oneshot?: false',
'creating task: 3 oneshot?: false',
'create leave',
'create leave',
'run enter zone: inner-zone task-id: 3 invocation-count: 0 arg: 3',
'run enter zone: outer-zone task-id: 3 invocation-count: 0 arg: 104',
'running zone: inner-zone task-id: 3 invocation-count: 0 value: 205',
'run leave invocation-count: 1',
'run leave invocation-count: 1',
'run enter zone: inner-zone task-id: 3 invocation-count: 1 arg: 3',
'run enter zone: outer-zone task-id: 3 invocation-count: 1 arg: 104',
'running zone: inner-zone task-id: 3 invocation-count: 1 value: 205',
'run leave invocation-count: 2',
'run leave invocation-count: 2',
], log);
log.clear();
nestedCompleter.complete("done");
},
zoneValues: {'name': 'inner-zone'},
zoneSpecification: new ZoneSpecification(
createTask: createTaskHandler,
runTask: runTaskHandler));
await nestedCompleter.future;
testCompleter.complete("done");
asyncEnd();
},
zoneValues: {'name': 'outer-zone'},
zoneSpecification: new ZoneSpecification(
createTask: createTaskHandler,
runTask: runTaskHandler));
return testCompleter.future;
}
runTests() async {
await testCustomTask();
await testCustomTask2();
}
main() {
asyncStart();
runTests().then((_) {
asyncEnd();
});
}

View file

@ -0,0 +1,515 @@
// Copyright (c) 2016, 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.
// Tests timer tasks.
import 'package:expect/expect.dart';
import 'package:async_helper/async_helper.dart';
import 'dart:async';
import 'dart:collection';
class MyTimerSpecification implements SingleShotTimerTaskSpecification {
final Function callback;
final Duration duration;
MyTimerSpecification(this.callback, this.duration);
bool get isOneShot => true;
String get name => "test.timer-override";
}
class MyPeriodicTimerSpecification implements PeriodicTimerTaskSpecification {
final Function callback;
final Duration duration;
MyPeriodicTimerSpecification(this.callback, this.duration);
bool get isOneShot => true;
String get name => "test.periodic-timer-override";
}
/// Makes sure things are working in a simple setting.
/// No interceptions, changes, ...
Future testTimerTask() {
List log = [];
var testCompleter = new Completer();
asyncStart();
int taskIdCounter = 0;
Object createTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
TaskCreate create, TaskSpecification specification) {
var taskMap = self['taskMap'];
var taskIdMap = self['taskIdMap'];
if (specification is SingleShotTimerTaskSpecification) {
log.add("create enter "
"zone: ${self['name']} "
"spec-duration: ${specification.duration} "
"spec-oneshot?: ${specification.isOneShot}");
var result = parent.createTask(zone, create, specification);
taskMap[result] = specification;
taskIdMap[specification] = taskIdCounter++;
log.add("create leave");
return result;
} else if (specification is PeriodicTimerTaskSpecification) {
log.add("create enter "
"zone: ${self['name']} "
"spec-duration: ${specification.duration} "
"spec-oneshot?: ${specification.isOneShot}");
var result = parent.createTask(zone, create, specification);
taskMap[result] = specification;
taskIdMap[specification] = taskIdCounter++;
log.add("create leave");
return result;
}
return parent.createTask(zone, create, specification);
}
void runTaskHandler(Zone self, ZoneDelegate parent, Zone zone, TaskRun run,
Object task, Object arg) {
var taskMap = self['taskMap'];
var taskIdMap = self['taskIdMap'];
if (taskMap.containsKey(task)) {
var spec = taskMap[task];
log.add("run enter "
"zone: ${self['name']} "
"task-id: ${taskIdMap[spec]} "
"arg: $arg");
parent.runTask(zone, run, task, arg);
log.add("run leave");
return;
}
parent.runTask(zone, run, task, arg);
}
runZoned(() async {
var completer0 = new Completer();
Timer.run(() {
completer0.complete("done");
});
await completer0.future;
Expect.listEquals([
'create enter zone: custom zone spec-duration: 0:00:00.000000 '
'spec-oneshot?: true',
'create leave',
'run enter zone: custom zone task-id: 0 arg: null',
'run leave'
], log);
log.clear();
var completer1 = new Completer();
var counter1 = 0;
new Timer.periodic(const Duration(milliseconds: 5), (Timer timer) {
if (counter1++ > 1) {
timer.cancel();
completer1.complete("done");
}
});
await completer1.future;
Expect.listEquals([
'create enter zone: custom zone spec-duration: 0:00:00.005000 '
'spec-oneshot?: false',
'create leave',
'run enter zone: custom zone task-id: 1 arg: null',
'run leave',
'run enter zone: custom zone task-id: 1 arg: null',
'run leave',
'run enter zone: custom zone task-id: 1 arg: null',
'run leave'
], log);
log.clear();
testCompleter.complete("done");
asyncEnd();
},
zoneValues: {'name': 'custom zone', 'taskMap': {}, 'taskIdMap': {}},
zoneSpecification: new ZoneSpecification(
createTask: createTaskHandler,
runTask: runTaskHandler));
return testCompleter.future;
}
/// More complicated zone, that intercepts...
Future testTimerTask2() {
List log = [];
var testCompleter = new Completer();
asyncStart();
int taskIdCounter = 0;
Object createTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
TaskCreate create, TaskSpecification specification) {
var taskMap = self['taskMap'];
var taskIdMap = self['taskIdMap'];
if (specification is SingleShotTimerTaskSpecification) {
log.add("create enter "
"zone: ${self['name']} "
"spec-duration: ${specification.duration} "
"spec-oneshot?: ${specification.isOneShot}");
var mySpec = new MyTimerSpecification(specification.callback,
specification.duration + const Duration(milliseconds: 2));
var result = parent.createTask(zone, create, mySpec);
taskMap[result] = specification;
taskIdMap[specification] = taskIdCounter++;
log.add("create leave");
return result;
} else if (specification is PeriodicTimerTaskSpecification) {
log.add("create enter "
"zone: ${self['name']} "
"spec-duration: ${specification.duration} "
"spec-oneshot?: ${specification.isOneShot}");
var mySpec = new MyPeriodicTimerSpecification(specification.callback,
specification.duration + const Duration(milliseconds: 2));
var result = parent.createTask(zone, create, specification);
taskMap[result] = specification;
taskIdMap[specification] = taskIdCounter++;
log.add("create leave");
return result;
}
return parent.createTask(zone, create, specification);
}
void runTaskHandler(Zone self, ZoneDelegate parent, Zone zone, TaskRun run,
Object task, Object arg) {
var taskMap = self['taskMap'];
var taskIdMap = self['taskIdMap'];
if (taskMap.containsKey(task)) {
var spec = taskMap[task];
log.add("run enter "
"zone: ${self['name']} "
"task-id: ${taskIdMap[spec]} "
"arg: $arg");
parent.runTask(zone, run, task, arg);
log.add("run leave");
return;
}
parent.runTask(zone, run, task, arg);
}
runZoned(() async {
var completer0 = new Completer();
Timer.run(() {
completer0.complete("done");
});
await completer0.future;
// No visible change (except for the zone name) in the log, compared to the
// simple invocations.
Expect.listEquals([
'create enter zone: outer-zone spec-duration: 0:00:00.000000 '
'spec-oneshot?: true',
'create leave',
'run enter zone: outer-zone task-id: 0 arg: null',
'run leave'
], log);
log.clear();
var completer1 = new Completer();
var counter1 = 0;
new Timer.periodic(const Duration(milliseconds: 5), (Timer timer) {
if (counter1++ > 1) {
timer.cancel();
completer1.complete("done");
}
});
await completer1.future;
// No visible change (except for the zone nome) in the log, compared to the
// simple invocations.
Expect.listEquals([
'create enter zone: outer-zone spec-duration: 0:00:00.005000 '
'spec-oneshot?: false',
'create leave',
'run enter zone: outer-zone task-id: 1 arg: null',
'run leave',
'run enter zone: outer-zone task-id: 1 arg: null',
'run leave',
'run enter zone: outer-zone task-id: 1 arg: null',
'run leave'
], log);
log.clear();
var nestedCompleter = new Completer();
runZoned(() async {
var completer0 = new Completer();
Timer.run(() {
completer0.complete("done");
});
await completer0.future;
// The outer zone sees the duration change of the inner zone.
Expect.listEquals([
'create enter zone: inner-zone spec-duration: 0:00:00.000000 '
'spec-oneshot?: true',
'create enter zone: outer-zone spec-duration: 0:00:00.002000 '
'spec-oneshot?: true',
'create leave',
'create leave',
'run enter zone: inner-zone task-id: 3 arg: null',
'run enter zone: outer-zone task-id: 2 arg: null',
'run leave',
'run leave'
], log);
log.clear();
var completer1 = new Completer();
var counter1 = 0;
new Timer.periodic(const Duration(milliseconds: 5), (Timer timer) {
if (counter1++ > 1) {
timer.cancel();
completer1.complete("done");
}
});
await completer1.future;
// The outer zone sees the duration change of the inner zone.
Expect.listEquals([
'create enter zone: inner-zone spec-duration: 0:00:00.005000 '
'spec-oneshot?: false',
'create enter zone: outer-zone spec-duration: 0:00:00.005000 '
'spec-oneshot?: false',
'create leave',
'create leave',
'run enter zone: inner-zone task-id: 5 arg: null',
'run enter zone: outer-zone task-id: 4 arg: null',
'run leave',
'run leave',
'run enter zone: inner-zone task-id: 5 arg: null',
'run enter zone: outer-zone task-id: 4 arg: null',
'run leave',
'run leave',
'run enter zone: inner-zone task-id: 5 arg: null',
'run enter zone: outer-zone task-id: 4 arg: null',
'run leave',
'run leave'
], log);
log.clear();
nestedCompleter.complete("done");
},
zoneValues: {'name': 'inner-zone', 'taskMap': {}, 'taskIdMap': {}},
zoneSpecification: new ZoneSpecification(
createTask: createTaskHandler,
runTask: runTaskHandler));
await nestedCompleter.future;
testCompleter.complete("done");
asyncEnd();
},
zoneValues: {'name': 'outer-zone', 'taskMap': {}, 'taskIdMap': {}},
zoneSpecification: new ZoneSpecification(
createTask: createTaskHandler,
runTask: runTaskHandler));
return testCompleter.future;
}
class TimerEntry {
final int time;
final SimulatedTimer timer;
TimerEntry(this.time, this.timer);
}
class SimulatedTimer implements Timer {
static int _idCounter = 0;
Zone _zone;
final int _id = _idCounter++;
final Duration _duration;
final Function _callback;
final bool _isPeriodic;
bool _isActive = true;
SimulatedTimer(this._zone, this._duration, this._callback, this._isPeriodic);
bool get isActive => _isActive;
void cancel() {
_isActive = false;
}
void _run() {
if (!isActive) return;
_zone.runTask(_runTimer, this, null);
}
static void _runTimer(SimulatedTimer timer, _) {
if (timer._isPeriodic) {
timer._callback(timer);
} else {
timer._callback();
}
}
}
testSimulatedTimer() {
List log = [];
var currentTime = 0;
// Using a simple list as queue. Not very efficient, but the test has only
// very few timers running at the same time.
var queue = new DoubleLinkedQueue<TimerEntry>();
// Schedules the given callback at now + duration.
void schedule(int scheduledTime, SimulatedTimer timer) {
log.add("scheduling timer ${timer._id} for $scheduledTime");
if (queue.isEmpty) {
queue.add(new TimerEntry(scheduledTime, timer));
} else {
DoubleLinkedQueueEntry current = queue.firstEntry();
while (current != null) {
if (current.element.time <= scheduledTime) {
current = current.nextEntry();
} else {
current.prepend(new TimerEntry(scheduledTime, timer));
break;
}
}
if (current == null) {
queue.add(new TimerEntry(scheduledTime, timer));
}
}
}
void runQueue() {
while (queue.isNotEmpty) {
var item = queue.removeFirst();
// If multiple callbacks were scheduled at the same time, increment the
// current time instead of staying at the same time.
currentTime = item.time > currentTime ? item.time : currentTime + 1;
SimulatedTimer timer = item.timer;
log.add("running timer ${timer._id} at $currentTime "
"(active?: ${timer.isActive})");
if (!timer.isActive) continue;
if (timer._isPeriodic) {
schedule(currentTime + timer._duration.inMilliseconds, timer);
}
item.timer._run();
}
}
SimulatedTimer createSimulatedOneShotTimer(
SingleShotTimerTaskSpecification spec, Zone zone) {
var timer = new SimulatedTimer(zone, spec.duration, spec.callback, false);
schedule(currentTime + spec.duration.inMilliseconds, timer);
return timer;
}
SimulatedTimer createSimulatedPeriodicTimer(
PeriodicTimerTaskSpecification spec, Zone zone) {
var timer = new SimulatedTimer(zone, spec.duration, spec.callback, true);
schedule(currentTime + spec.duration.inMilliseconds, timer);
return timer;
}
Object createSimulatedTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
TaskCreate create, TaskSpecification specification) {
var taskMap = self['taskMap'];
var taskIdMap = self['taskIdMap'];
if (specification is SingleShotTimerTaskSpecification) {
log.add("create enter "
"zone: ${self['name']} "
"spec-duration: ${specification.duration} "
"spec-oneshot?: ${specification.isOneShot}");
var result =
parent.createTask(zone, createSimulatedOneShotTimer, specification);
log.add("create leave");
return result;
}
if (specification is PeriodicTimerTaskSpecification) {
log.add("create enter "
"zone: ${self['name']} "
"spec-duration: ${specification.duration} "
"spec-oneshot?: ${specification.isOneShot}");
var result =
parent.createTask(zone, createSimulatedPeriodicTimer, specification);
log.add("create leave");
return result;
}
return parent.createTask(zone, create, specification);
}
runZoned(() {
Timer.run(() {
log.add("running Timer.run");
});
var timer0;
new Timer(const Duration(milliseconds: 10), () {
log.add("running Timer(10)");
timer0.cancel();
log.add("canceled timer0");
});
timer0 = new Timer(const Duration(milliseconds: 15), () {
log.add("running Timer(15)");
});
var counter1 = 0;
new Timer.periodic(const Duration(milliseconds: 5), (Timer timer) {
log.add("running periodic timer $counter1");
if (counter1++ > 1) {
timer.cancel();
}
});
},
zoneSpecification:
new ZoneSpecification(createTask: createSimulatedTaskHandler));
runQueue();
Expect.listEquals([
'create enter zone: null spec-duration: 0:00:00.000000 spec-oneshot?: true',
'scheduling timer 0 for 0',
'create leave',
'create enter zone: null spec-duration: 0:00:00.010000 spec-oneshot?: true',
'scheduling timer 1 for 10',
'create leave',
'create enter zone: null spec-duration: 0:00:00.015000 spec-oneshot?: true',
'scheduling timer 2 for 15',
'create leave',
'create enter zone: null spec-duration: 0:00:00.005000 '
'spec-oneshot?: false',
'scheduling timer 3 for 5',
'create leave',
'running timer 0 at 1 (active?: true)',
'running Timer.run',
'running timer 3 at 5 (active?: true)',
'scheduling timer 3 for 10',
'running periodic timer 0',
'running timer 1 at 10 (active?: true)',
'running Timer(10)',
'canceled timer0',
'running timer 3 at 11 (active?: true)',
'scheduling timer 3 for 16',
'running periodic timer 1',
'running timer 2 at 15 (active?: false)',
'running timer 3 at 16 (active?: true)',
'scheduling timer 3 for 21',
'running periodic timer 2',
'running timer 3 at 21 (active?: false)'
], log);
log.clear();
}
runTests() async {
await testTimerTask();
await testTimerTask2();
testSimulatedTimer();
}
main() {
asyncStart();
runTests().then((_) {
asyncEnd();
});
}