'Florian Loitsch' via Dart Announcements
2017-09-20 17:17:22 UTC
With *Dart 2.0-dev.1.0 (the release after 2017-09-20 <20%2017%2009%2020>)*
Dart's zones are finally becoming strong-mode clean. Unfortunately, this
update introduces some breaking changes to the Zone's API.
This mail discusses the changes to the API and how users can adapt their
code to make it work.
The corresponding CL is https://dart-review.googlesource.com/c/sdk/+/4942
It was committed as
https://github.com/dart-lang/sdk/commit/38bf70d7ac4ea695adc65ae3d7d0fa083b4dcd46
TL;DR
There were breaking changes to the Zone API of dart:async. If you implement
or use zones, please update your code (if necessary). See the "How to Fix"
section below for more details.
Zone API
In Dart, zones from dart:async are used to provide an environment that
stays stable across asynchronous calls. Zones can furthermore wrap methods
that are registered as asynchronous event handlers. This allows users to
execute actions when an asynchronous operation returns from the event loop.
For example, the stack_trace package uses this functionality to collect
stracktraces that survive event-loop interactions.
Users can provide "hooks" to zones, by providing callbacks that are invoked
when specific actions happen. These actions are operations like
registerCallback, run, or even print. In all these cases, core library code
calls into the current zone. For example, instead of emitting characters to
the screen, the print function invokes Zone.current.print with the correct
arguments.
Many interceptable zone operations deal with functions. For example,
registerCallback is invoked when a closure is about to be used as an
asynchronous callback, and provides the zone the opportunity to wrap the
function. There are three different registerX functions that deal with
different arities, but the exact function type varies. In Dart 1.x this
wasn't a problem, since dynamic could be used. A registerX function could
just wrap the original function and return a new function that was taking
and returning dynamic instead.
The new strong-mode compliant zone API fixes this, by introducing generic
arguments for these callbacks. For instance, the registerCallback function,
now takes one generic argument T which defines the return type: T
Function() registerCallback<T>(T Function() f). Adding some generic
arguments to the Zone functions is non-breaking and is similar to many
other strong-mode cleanups.
A Zone.registerCallback may invoke a user-provided callback that should do
the work instead. (It's a bit more complicated with ZoneDelegates and other
involved classes, but conceptually that's what's happening). This callback
needs to be able to do the full work of registerCallback. This means, that
users have to provide functions that satisfy the `registerCallback`'s
signature:
typedef RegisterCallbackHandler = T Function() Function<T>(T Function() f);
As can be seen, the function type is generic. This was the reason this
change could not be done at the same time as all the other strong-mode
cleanups: generic closure types were not supported at that time.
Most of the Zone changes consist of adding the missing generic type
informations to the API. Users of the Zone API need to update their hooks
so that they are generic as well.
Breaking Changes
Most of the breakages are introduced because of the additional typing
information. Almost all users need to update their code to use the generic
arguments for their hooks.
However, during the cleanup we also discovered some functions that didn't
work well in a fully typed system: handleUncaughtError, runZoned, runGuarded
and bindCallback.
The handleUncaughtError function is invoked when an error is not handled
and should be reported. (It is also invoked when an error tries to cross an
error-boundary).
It was typed as R handleUncaughtError<R>(error, StackTrace trace), but it
became obvious pretty fast, that functions are generally unable to satisfy
the return-type requirement except by returning null. As such, the function
was changed to be a void function instead.
This change impacts all functions that are supposed to return a value when
an error occurs: runZoned, runGuarded and bindCallback (when its optional
argument runGuarded is true). All of these catch synchronous errors and
invoke the error handler when this happens. Since the error handler doesn't
provide any value anymore, their functionality needed to be reworked.
If runZoned is invoked with an error-handler, than all errors (synchronous
or not) are given to the provided handler. In the synchronous case, the
return value of the onError handler would be used as the result of runZoned.
This is not the case anymore: since onError is a void function now, it
simply returns null when there is a synchronous error.
The runGuarded function basically puts a try/catch around the provided
closure and invokes it. In case of a synchronous error, it invokes the
zone's handleUncaughtError. Since this one can't return a value of the
correct type anymore, runGuarded was changed to only take void functions,
and to have a void return value.
Similarly, bindCallback was a combination of registerCallback and runGuarded
if the named argument runGuarded was true (its default value). For the same
reasons as above, this didn't make any sense anymore. The bindCallback
functions were thus split into two:
-
bindCallback which doesn't run guarded.
-
bindCallbackGuarded which only takes void functions and invokes the
bound callback in a guarded way.
How To Fix
This change affects users in different ways:
1.
Type issues
2.
API changes
The typing issues have to be resolved the same way as every other
strong-mode change. Generally it involves adding new generic types. For
example, the CL <https://codereview.chromium.org//2888623007> for the
`observe` package consists of simply adding the necessary generic types
(slightly trimmed):
--- a/lib/src/dirty_check.dart
+++ b/lib/src/dirty_check.dart
@@ -107,7 +107,8 @@ ZoneSpecification dirtyCheckZoneSpec() {
- Func0 wrapCallback(Zone self, ZoneDelegate parent, Zone zone, f()) {
+ ZoneCallback<R> wrapCallback<R>(
+ Zone self, ZoneDelegate parent, Zone zone, R f()) {
if (f == null) return f;
@@ -116,7 +117,8 @@ ZoneSpecification dirtyCheckZoneSpec() {
- Func1 wrapUnaryCallback(Zone self, ZoneDelegate parent, Zone zone, f(x))
{
+ ZoneUnaryCallback<R, T> wrapUnaryCallback<R, T>(
+ Zone self, ZoneDelegate parent, Zone zone, R f(T x)) {
These changes are backwards-compatible.
The API changes may require more work, and may not be backwards-compatible.
Package authors should either update their SDK constraint to ">=2.0.0-dev.1.0",
or test their packages with the 1.24 and the 2.0-dev releases.
runZoned
Most of the time, the runZoned is invoked for code that is not supposed to
throw synchronously. This means, that code that uses runZoned is most often
unaffected.
If the onError handler visibly returns something that is supposed to be
used synchronously, the code has to be rewritten. For example, as follows:
// Before:
int count = runZoned(() {
startAsyncOperation();
return 5 ~/ maybeZeroVariable;
}, onError: (e, s) {
print("caught error: $e");
return 0;
});
// After:
bool wasError = false;
int count = runZoned(() {
startAsyncOperation();
return 5 ~/ maybeZeroVariable;
}, onError: (e, s) {
print("caught error: $e");
wasError = true;
});
if (wasError) count = 0;
Note that this transformation is safe to use for the old API as well.
bindCallback
The bindCallback functions (bindCallback, bindUnaryCallback,
bindBinaryCallback) were significantly changed. They don't take any
optional named argument anymore, and the default behavior for not providing
an argument changed.
The easiest case is, when users passed the runGuarded flag and set it to
`false`. In this case, the transition to the new API consists of simple
removing the named argument:
// Before:
var g = zone.bindCallback(f, runGuarded: false);
// After:
var g = zone.bindCallback(f);
// Alternative After:
var capturedZone = zone;
var registered = capturedZone.registerCallback(f);
var g = () => capturedZone.run(registered);
The alternative "after" is a bit longer, but has the advantage that it
works for the old and the new API in the same way.
If the code didn't set runGuarded to `false` then the code needs to make
sure that the provided function is a `void` function and transform as
follows:
// Before:
var f = zone.bindCallback(f);
// After:
var f = zone.bindCallbackGuarded(f);
// Alternative After:
var capturedZone = zone;
var registered = capturedZone.registerCallback(f);
var g = () { capturedZone.runGuarded(registered); };
Again the "alternative" option has the advantage that it works the same way
before and after the change.
If the provided function is *not* a void function, then the transition is
dependent on the context. Feel free to contact me (floitsch@) for advice.
runGuarded
The runGuarded function (not the same-named flag to bindCallback) runs code
synchronously and forwards errors to the zone's error handler. The API
change consists of only taking `void` functions. This is very similar to
`runZoned` and requires similar fixes.
Example:
// Before. The value of `runGuarded` is used.
var x = zone.runGuarded(() => 499);
// After: `runGuarded` is a `void` function.
var x;
zone.runGuarded(() { x = 499; });
if (x == null) {
// Something happened synchronously. Was probably not dealt with before
either.
// Often this case is not even possible.
}
Note that this transformation works for both the old and new API.
--
For more news and information, visit https://plus.google.com/+dartlang
To join the conversation, visit https://groups.google.com/a/dartlang.org/
Dart's zones are finally becoming strong-mode clean. Unfortunately, this
update introduces some breaking changes to the Zone's API.
This mail discusses the changes to the API and how users can adapt their
code to make it work.
The corresponding CL is https://dart-review.googlesource.com/c/sdk/+/4942
It was committed as
https://github.com/dart-lang/sdk/commit/38bf70d7ac4ea695adc65ae3d7d0fa083b4dcd46
TL;DR
There were breaking changes to the Zone API of dart:async. If you implement
or use zones, please update your code (if necessary). See the "How to Fix"
section below for more details.
Zone API
In Dart, zones from dart:async are used to provide an environment that
stays stable across asynchronous calls. Zones can furthermore wrap methods
that are registered as asynchronous event handlers. This allows users to
execute actions when an asynchronous operation returns from the event loop.
For example, the stack_trace package uses this functionality to collect
stracktraces that survive event-loop interactions.
Users can provide "hooks" to zones, by providing callbacks that are invoked
when specific actions happen. These actions are operations like
registerCallback, run, or even print. In all these cases, core library code
calls into the current zone. For example, instead of emitting characters to
the screen, the print function invokes Zone.current.print with the correct
arguments.
Many interceptable zone operations deal with functions. For example,
registerCallback is invoked when a closure is about to be used as an
asynchronous callback, and provides the zone the opportunity to wrap the
function. There are three different registerX functions that deal with
different arities, but the exact function type varies. In Dart 1.x this
wasn't a problem, since dynamic could be used. A registerX function could
just wrap the original function and return a new function that was taking
and returning dynamic instead.
The new strong-mode compliant zone API fixes this, by introducing generic
arguments for these callbacks. For instance, the registerCallback function,
now takes one generic argument T which defines the return type: T
Function() registerCallback<T>(T Function() f). Adding some generic
arguments to the Zone functions is non-breaking and is similar to many
other strong-mode cleanups.
A Zone.registerCallback may invoke a user-provided callback that should do
the work instead. (It's a bit more complicated with ZoneDelegates and other
involved classes, but conceptually that's what's happening). This callback
needs to be able to do the full work of registerCallback. This means, that
users have to provide functions that satisfy the `registerCallback`'s
signature:
typedef RegisterCallbackHandler = T Function() Function<T>(T Function() f);
As can be seen, the function type is generic. This was the reason this
change could not be done at the same time as all the other strong-mode
cleanups: generic closure types were not supported at that time.
Most of the Zone changes consist of adding the missing generic type
informations to the API. Users of the Zone API need to update their hooks
so that they are generic as well.
Breaking Changes
Most of the breakages are introduced because of the additional typing
information. Almost all users need to update their code to use the generic
arguments for their hooks.
However, during the cleanup we also discovered some functions that didn't
work well in a fully typed system: handleUncaughtError, runZoned, runGuarded
and bindCallback.
The handleUncaughtError function is invoked when an error is not handled
and should be reported. (It is also invoked when an error tries to cross an
error-boundary).
It was typed as R handleUncaughtError<R>(error, StackTrace trace), but it
became obvious pretty fast, that functions are generally unable to satisfy
the return-type requirement except by returning null. As such, the function
was changed to be a void function instead.
This change impacts all functions that are supposed to return a value when
an error occurs: runZoned, runGuarded and bindCallback (when its optional
argument runGuarded is true). All of these catch synchronous errors and
invoke the error handler when this happens. Since the error handler doesn't
provide any value anymore, their functionality needed to be reworked.
If runZoned is invoked with an error-handler, than all errors (synchronous
or not) are given to the provided handler. In the synchronous case, the
return value of the onError handler would be used as the result of runZoned.
This is not the case anymore: since onError is a void function now, it
simply returns null when there is a synchronous error.
The runGuarded function basically puts a try/catch around the provided
closure and invokes it. In case of a synchronous error, it invokes the
zone's handleUncaughtError. Since this one can't return a value of the
correct type anymore, runGuarded was changed to only take void functions,
and to have a void return value.
Similarly, bindCallback was a combination of registerCallback and runGuarded
if the named argument runGuarded was true (its default value). For the same
reasons as above, this didn't make any sense anymore. The bindCallback
functions were thus split into two:
-
bindCallback which doesn't run guarded.
-
bindCallbackGuarded which only takes void functions and invokes the
bound callback in a guarded way.
How To Fix
This change affects users in different ways:
1.
Type issues
2.
API changes
The typing issues have to be resolved the same way as every other
strong-mode change. Generally it involves adding new generic types. For
example, the CL <https://codereview.chromium.org//2888623007> for the
`observe` package consists of simply adding the necessary generic types
(slightly trimmed):
--- a/lib/src/dirty_check.dart
+++ b/lib/src/dirty_check.dart
@@ -107,7 +107,8 @@ ZoneSpecification dirtyCheckZoneSpec() {
- Func0 wrapCallback(Zone self, ZoneDelegate parent, Zone zone, f()) {
+ ZoneCallback<R> wrapCallback<R>(
+ Zone self, ZoneDelegate parent, Zone zone, R f()) {
if (f == null) return f;
@@ -116,7 +117,8 @@ ZoneSpecification dirtyCheckZoneSpec() {
- Func1 wrapUnaryCallback(Zone self, ZoneDelegate parent, Zone zone, f(x))
{
+ ZoneUnaryCallback<R, T> wrapUnaryCallback<R, T>(
+ Zone self, ZoneDelegate parent, Zone zone, R f(T x)) {
These changes are backwards-compatible.
The API changes may require more work, and may not be backwards-compatible.
Package authors should either update their SDK constraint to ">=2.0.0-dev.1.0",
or test their packages with the 1.24 and the 2.0-dev releases.
runZoned
Most of the time, the runZoned is invoked for code that is not supposed to
throw synchronously. This means, that code that uses runZoned is most often
unaffected.
If the onError handler visibly returns something that is supposed to be
used synchronously, the code has to be rewritten. For example, as follows:
// Before:
int count = runZoned(() {
startAsyncOperation();
return 5 ~/ maybeZeroVariable;
}, onError: (e, s) {
print("caught error: $e");
return 0;
});
// After:
bool wasError = false;
int count = runZoned(() {
startAsyncOperation();
return 5 ~/ maybeZeroVariable;
}, onError: (e, s) {
print("caught error: $e");
wasError = true;
});
if (wasError) count = 0;
Note that this transformation is safe to use for the old API as well.
bindCallback
The bindCallback functions (bindCallback, bindUnaryCallback,
bindBinaryCallback) were significantly changed. They don't take any
optional named argument anymore, and the default behavior for not providing
an argument changed.
The easiest case is, when users passed the runGuarded flag and set it to
`false`. In this case, the transition to the new API consists of simple
removing the named argument:
// Before:
var g = zone.bindCallback(f, runGuarded: false);
// After:
var g = zone.bindCallback(f);
// Alternative After:
var capturedZone = zone;
var registered = capturedZone.registerCallback(f);
var g = () => capturedZone.run(registered);
The alternative "after" is a bit longer, but has the advantage that it
works for the old and the new API in the same way.
If the code didn't set runGuarded to `false` then the code needs to make
sure that the provided function is a `void` function and transform as
follows:
// Before:
var f = zone.bindCallback(f);
// After:
var f = zone.bindCallbackGuarded(f);
// Alternative After:
var capturedZone = zone;
var registered = capturedZone.registerCallback(f);
var g = () { capturedZone.runGuarded(registered); };
Again the "alternative" option has the advantage that it works the same way
before and after the change.
If the provided function is *not* a void function, then the transition is
dependent on the context. Feel free to contact me (floitsch@) for advice.
runGuarded
The runGuarded function (not the same-named flag to bindCallback) runs code
synchronously and forwards errors to the zone's error handler. The API
change consists of only taking `void` functions. This is very similar to
`runZoned` and requires similar fixes.
Example:
// Before. The value of `runGuarded` is used.
var x = zone.runGuarded(() => 499);
// After: `runGuarded` is a `void` function.
var x;
zone.runGuarded(() { x = 499; });
if (x == null) {
// Something happened synchronously. Was probably not dealt with before
either.
// Often this case is not even possible.
}
Note that this transformation works for both the old and new API.
--
For more news and information, visit https://plus.google.com/+dartlang
To join the conversation, visit https://groups.google.com/a/dartlang.org/
--
For other discussions, see https://groups.google.com/a/dartlang.org/
For HOWTO questions, visit http://stackoverflow.com/tags/dart
To file a bug report or feature request, go to http://www.dartbug.com/new
---
You received this message because you are subscribed to the Google Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it, send an email to misc+***@dartlang.org.
For other discussions, see https://groups.google.com/a/dartlang.org/
For HOWTO questions, visit http://stackoverflow.com/tags/dart
To file a bug report or feature request, go to http://www.dartbug.com/new
---
You received this message because you are subscribed to the Google Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it, send an email to misc+***@dartlang.org.