BREAKING CHANGE: asynchronous error handlers, stack traces, runAsync and StreamTransformers.

829 views
Skip to first unread message

Florian Loitsch

unread,
Oct 12, 2013, 12:17:45 PM10/12/13
to announce, General Dart Discussion
What changed?
Methods receiving error-handlers now accept callbacks of arity 1 or 2. For example Future.catchError can now be invoked with either handleError(error) or handleError(error, stackTrace).
We modified the constructor of StreamTransformer.
We removed StreamEventTransformer and EventTransformStream.
We have deprecated getAttachedStackTrace.
We have renamed runAsync to scheduleMicrotask.

Who is affected?
This is an immediately-breaking change for users of the StreamTransformer-constructor and the removed StreamEventTransform and EventTransformStream classes.
Other users should be ok for a few weeks (but at most until the end of October), but are encouraged to update their asynchronous error-handling code as soon as possible.

How do I update my code?
Error Handling:
All asynchronous error-handling function can now receive the stack trace as second argument. If you want to have a stack trace, change your current error-handler to accept a second argument.
For example:
    future.catchError((error) {
      print(error);
      print(getAttachedStackTrace(error));
    }
becomes
    future.catchError((e, stackTrace) {
      print(e);
      print(stackTrace);
    }

StreamTransformer constructor:
Switch to the StreamTransformer.fromHandlers, and add a `stackTrace` argument to your error-handler:

    new StreamTransformer(handleData: f1, handleError: (e, sink) => ..., handleDone: f3)
becomes:
    new StreamTransformer.fromHandlers(handleData: f1, handleError: (e, stackTrace, sink) => ..., handleDone: f3)

Also note that the EventSink now takes an optional stackTrace argument.

Users of StreamEventTransformer and EventTransformStream:
Ideally you should switch to the updated StreamTransformer constructor, or use the Stream.eventTransformed constructor. In most cases the StreamTransformers can be adapted in a relatively straight-forward way. You can also copy the code we removed into your project.
Feel free to ask for help on the mailing-list.

Why did this change happen?
Before this change asynchronous error handlers didn't receive stack traces with their errors. Instead the stack traces were attached to the objects and could be retrieved with `getAttachedStackTrace`. This always was a temporary solution (and the method was marked as "experimental" for this reason) since it comes with severe restrictions like not being able to get the stack trace of String errors.
For these reasons we decided to change the error-handling in dart:async: all error-handling callbacks (arguments to Future.catchError, Stream.handleError, ...) are now typed as "Function" and can take either one or two arguments. If the callback takes two arguments, it receives the stack trace (or `null` if there is none) when it is invoked.
This change is mostly non-breaking, except for Stream transformers: the `handleError` of the `StreamTransformer` constructor was typed as `handleError(error, EventSink sink)` and `StreamEventTransformer` had an `addError(Object error, EventSink<T> sink)` method. Since updating this method breaks user code we decided to go one step further and clean these areas of the library.
This involved removing `StreamEventTransformer` and `EventTransformStream`. In return we added the Stream.eventTransformed and StreamTransformer constructors.

The runAsync renaming was triggered by standardization efforts in Html5, W3C, which uses this name for similar callbacks. We considered renaming "Future" to "Promise" but, contrary to "runAsync", futures are used *much* more frequently and would break many more programs.

When will the change take effect?
The changes were committed a few minutes ago.
--
Give a man a fire and he's warm for the whole day,
but set fire to him and he's warm for the rest of his life. - Terry Pratchett

dangli...@gmail.com

unread,
Oct 13, 2013, 4:19:51 AM10/13/13
to mi...@dartlang.org, announce
I cannot understand your approach.
You say that you have deprecated "getAttachedStackTrace" but still use it in new created (returned back?) class called _AsynError.

Please explain me.
1. Why class _AsynError is private. How is this requirement is dictated?
2. If you just later rename (as you say "deprecates") and still continue internal usage "getAttachedStackTrace" that why this requirement is dictated?
3. Why "void _attachStackTrace(o, st)" is private? It is very usefull for low level system programming (i.e diagnostics but not only) because it compensates for the inferiority of  "implementation of the exceptions" in Dart.

dangli...@gmail.com

unread,
Oct 13, 2013, 4:52:43 AM10/13/13
to mi...@dartlang.org, announce
Also another question regarding simplifying process of getting of information about the exception stack trace.

Current SDK implementation.

abstract class Exception {
  factory Exception([var message]) => new _ExceptionImplementation(message);
}

class Error {
  /**/
  external StackTrace get stackTrace;
}

Why not bring it all to one? Why produce different and inconsistent implementations?
I am talk about  the "Exception" and "Error" implementations where "Error" is not implements "Exception" but presented as something incompatible with "Exception".
Is this approach simplify or improve something? I think this only increases the inconsistency. And thus complicates the implementation of the remaining dependent.

Why not in this way?

abstract class Exception {
  factory Exception([var message]) => new _ExceptionImplementation(message);
  StackTrace get stackTrace {
    return getAttachedStackTrace(this); // or something similar
  }
}

class Error implements Exception {
  /**/
}

Permanent alterations do not cause your headaches?

Lasse R.H. Nielsen

unread,
Oct 13, 2013, 1:46:07 PM10/13/13
to mi...@dartlang.org, announce
Personally, I think that if we should do anything, we should just remove the Exception class entirely.
All classes can be thrown in Dart. There is no need for an Exception class or exception type hierarchy.
Every application, library or framework can create their own exception objects to signal whatever they want.

The Error class gets stacktraces added to it, but a well functioning program should never throw an Error, so the overhead should be minimal. If you see an Error, it's time to start debugging.

/L


--
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

To unsubscribe from this group and stop receiving emails from it, send an email to misc+uns...@dartlang.org.



--
Lasse R.H. Nielsen - l...@google.com  
'Faith without judgement merely degrades the spirit divine'
Google Denmark ApS - Frederiksborggade 20B, 1 sal - 1360 København K - Denmark - CVR nr. 28 86 69 84

Kevin Moore

unread,
Oct 13, 2013, 7:58:04 PM10/13/13
to e...@dartlang.org, General Dart Discussion, announce
Relavant issue: https://code.google.com/p/dart/issues/detail?id=9377

It's critical to point out one person's Exception is anothers Error.

If something is thrown, I can't imagine a scenario where I wouldn't want a StackTrace.


--
For more news and information, visit http://news.dartlang.org/
 
To join the conversation, visit https://groups.google.com/a/dartlang.org/

To unsubscribe from this group and stop receiving emails from it, send an email to announce+u...@dartlang.org.

Florian Loitsch

unread,
Oct 14, 2013, 6:49:05 AM10/14/13
to e...@dartlang.org, General Dart Discussion, announce
On Mon, Oct 14, 2013 at 1:58 AM, Kevin Moore <kev...@j832.com> wrote:
Relavant issue: https://code.google.com/p/dart/issues/detail?id=9377

It's critical to point out one person's Exception is anothers Error.

If something is thrown, I can't imagine a scenario where I wouldn't want a StackTrace.
If an IO operation fails you don't care about the stack trace. You just catch it and deal with it. 
--
For general Dart discussion, consider posting to mi...@dartlang.org
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 Project Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to eng+uns...@dartlang.org.
For more options, visit https://groups.google.com/a/dartlang.org/groups/opt_out.

dangli...@gmail.com

unread,
Oct 14, 2013, 8:09:17 AM10/14/13
to mi...@dartlang.org, e...@dartlang.org, announce
>> If something is thrown, I can't imagine a scenario where I wouldn't want a StackTrace.
>> If an IO operation fails you don't care about the stack trace. You just catch it and deal with it.

I do not think that you are right making such statements.

If an IO operation fails then at least it fails for a reason.
And this situation may be trivial. It may wrong parameter, by example, wrong URL.
In this case IO operation fails but I think that stack trace not just useful but it is required.
Also an IO operation may fails due exceeding resource limit (memory, disk space, number of open files etc). In this case stack trace information also useful because your program consume these resources and information about the place where this occurs also necessary.
If you talk about that case when IO operation fails due to failure of hardware equipments it is the force majeure.  But this is a special case.

понедельник, 14 октября 2013 г., 16:49:05 UTC+6 пользователь Florian Loitsch написал:

dangli...@gmail.com

unread,
Oct 14, 2013, 8:18:13 AM10/14/13
to mi...@dartlang.org, e...@dartlang.org, announce
>> If an IO operation fails you don't care about the stack trace. You just catch it and deal with it.

@Florian Loitsch
Even if you are a developer, you will in any case should not speak for everyone. You can only answer for their actions, and partly for the work of his creation.


понедельник, 14 октября 2013 г., 16:49:05 UTC+6 пользователь Florian Loitsch написал:
On Mon, Oct 14, 2013 at 1:58 AM, Kevin Moore <kev...@j832.com> wrote:

William Hesse

unread,
Oct 14, 2013, 8:22:20 AM10/14/13
to e...@dartlang.org, General Dart Discussion, announce
The stack trace of an IO exception, when the exception is due to a bad parameter, out of memory, or a system failure, will not tell you where in the program the IO call came from.  There is no real stack trace, because the error usually happens when the internal IO subsystems are running code driven by an OS IO signal.

Instead, we have tried to add information about what IO call is being processed, by adding the path, in the case of file operations, or by adding the socket and port, in the case of network operations, to the error.  The error should also have an informative message about the cause of the error.

If you have a call to socket.listen, somewhere in your code, with an onData and an onError callback, then the onError callback will be called with an exception and no stack trace.  The stack, at the time socket.listen was called, is long gone.  If you want that stack, you would need to capture it, and capture it  in the onError callback closure at the time socket.listen is called.  But that is expensive, and would happen on every call to .listen.

William Hesse

dangli...@gmail.com

unread,
Oct 14, 2013, 8:44:32 AM10/14/13
to mi...@dartlang.org, e...@dartlang.org, announce
>> If you have a call to socket.listen, somewhere in your code, with an onData and an onError callback, then the onError callback will be called with an exception and no stack trace.  The stack, at the time socket.listen was called, is long gone.  If you want that stack, you would need to capture it, and capture it  in the onError callback closure at the time socket.listen is called.  But that is expensive, and would happen on every call to .listen.

This can be solved in non-expensive way. But this required another approach. Like in .NET awaitable.
Example, current implementation:

void onData(T item) {
  data.add(item);
}

stream.listen(onData);

Example, not expensive but throw exception with stack trace at right place:

void onData(T get()) {
  data.add(get()); // where "get" may throw exception if some error ALREADY occurs. But profit in that the "onData" in stack trace.
}

stream.listen(onData);

Internally this looks like this.
Instead of this.

callOnData(onData(item));

You use this approach.

T get() {
  if(exception != null) {
    throw exception;
  }

  return item;
}

callOnData(onData(get));

понедельник, 14 октября 2013 г., 18:22:20 UTC+6 пользователь William Hesse написал:
Reply all
Reply to author
Forward
0 new messages