Dart Bindings: Breaking Change

32 views
Skip to first unread message

Zach Anderson

unread,
Jun 8, 2016, 5:15:25 PM6/8/16
to mojo...@chromium.org
If you don't use the Dart bindings you can stop reading now.

tl;dr: Methods returning their result as a Future now take a callback to be called when the result is available.

Following the discussion here [1] and here [2], after the CL here [3] lands and a new mojo package is published to pub, users of the Dart bindings will need to update their application code. In particular:

the Mojo interface

interface Foo {
  Bar() => (int32 a, int32 b, int32 c)
};

will be generated as the Dart interface:

abstract class Foo {
  void bar(void callback(int a, int b, int c));
}

This means that code that previously did the following:

var r = await foo.bar();
...

will need to be rewritten as:

foo.bar((int a, int b, int c) {
  ...
});

(caveat: The callbacks will always run in the root Zone.)

On the service implementation side, instead of returning the result of the responseFactory, implementations will call the callback on the results, and return no result. The callback sends the response.

Rationale:
0. The existing bindings weren't very idiomatic, so hopefully not much is lost by making them less idiomatic.
1. Mojo's multiple return values map better onto Dart's parameter lists than onto a Future return of an object with fields for the return values.
2. Better types for both proxies and stubs, so we'll get more help from the analyzer.
3. No more useless responseFactory parameter on the Proxy side.
4. Better performance due to avoiding an extra trip through the event loop.

Porting:

Supposing that a call has a single return value, there is a fairly direct translation from:

var r = await foo.bar();

to:

var c = new Completer();
foo.bar((int a) {
  c.complete(a);
});
var r = await c.future;

Ideally, Mojo interfaces should be wrapped by-hand in a more idiomatic Dart class. The right interface to expose to Dart programs is likely application dependent. If losing the Future-based API sounds like a big problem, let's talk.

I plan to land this change this week and push a new package to pub early next week. Please let me know here on the list or in person if you have any concerns.

Cheers,
Zach

Ian Hickson

unread,
Jun 8, 2016, 8:05:05 PM6/8/16
to Zach Anderson, mojo...@chromium.org
Is there a bug tracking the issue of the callbacks being called in the root zone? Is this resolvable? For Flutter we're using Zones more and more and it would be unfortunate if mojo callbacks escaped them.

--
You received this message because you are subscribed to the Google Groups "mojo-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mojo-dev+u...@chromium.org.

Zach Anderson

unread,
Jun 8, 2016, 11:21:37 PM6/8/16
to Ian Hickson, mojo...@chromium.org
The callbacks run in the handler for a RawRecievePort. The handlers for RawReceivePorts bypass Zones by design to avoid their runtime cost. We use a RawReceivePort rather than ReceivePort to avoid the cost of setting up a Stream, which really isn't a good abstraction for a Mojo connection anyway, AFAIU. You can make code run outside the root Zone on receiving a response by e.g. creating a completer and hooking up a .then() to the completer's future outside of the callback in the Zone of your choice:

var c = new Completer();
foo.bar((int a) {
    c.complete(a);
});
runZoned(() {
  c.future.then((int a) {
    ...
  });
});

If you need to do something more complicated, and code in the callback throws an exception that subclasses Error, e.g. ArgumentError, the exception will always be rethrown by the bindings library into the root Zone since it is likely a programming mistake.

What I can probably improve is the following: Right now, exceptions of types that don't subclass Error are swallowed unless there is a callback hooked up to e.g. foo.ctrl.onError. Instead these exceptions should be used to complete foo.ctrl.errorFuture to be consistent with other errors on a Proxy. This hasn't been an issue until now since the code running in the RawReceivePort handler was always library-internal.

HTH,
Zach

Adam Barth

unread,
Jun 8, 2016, 11:37:39 PM6/8/16
to Zach Anderson, Ian Hickson, mojo...@chromium.org
We use Zones for more than just error handling.  For example, we use Zones during testing to mock out time.  It would be really confusing if code running inside Mojo callbacks saw the real time instead of the mock time.

Would it be reasonable to remember the zone along with the callback and then call the callback in the proper zone in the handler for the RawRecievePort?

Adam

Ian Hickson

unread,
Jun 8, 2016, 11:37:50 PM6/8/16
to Zach Anderson, mojo...@chromium.org
The problem is that if someone is running in a Zone with an error handler, and makes a call to a mojo service, and then throws in their callback, the error handler won't be called.

Can you check what the current zone is when you receive a callback, and if it's not the default zone, use that zone to reinvoke the callback when you do so?

Florian Loitsch

unread,
Jun 9, 2016, 6:56:01 AM6/9/16
to Zach Anderson, Ian Hickson, mojo...@chromium.org
On Thu, Jun 9, 2016 at 5:21 AM 'Zach Anderson' via mojo-dev <mojo...@chromium.org> wrote:
The callbacks run in the handler for a RawRecievePort. The handlers for RawReceivePorts bypass Zones by design to avoid their runtime cost.
 
All other core-libraries do a fast check if the current zone is identical to the root-zone: if (identical(Zone.current, Zone.ROOT)) { <fast-path> }.
Can't the mojo implementation do the same?
It is considered a bug not to respect zones when doing asynchronous operations.

Zach Anderson

unread,
Jun 9, 2016, 10:53:14 AM6/9/16
to Ian Hickson, mojo...@chromium.org
Okay, I understand the problem now. I'll work on this in a follow-up CL.

Cheers,
Zach

Zach Anderson

unread,
Jun 13, 2016, 5:25:53 PM6/13/16
to Ian Hickson, mojo...@chromium.org
After https://github.com/domokit/mojo/commit/ee5b33cba0face525d425d6bd8682e5de038ad73, the callback will run in current Zone at the time of calling the method.

Cheers,
Zach

Ian Hickson

unread,
Jun 13, 2016, 5:31:22 PM6/13/16
to Zach Anderson, mojo...@chromium.org
yay!

Jeff Brown

unread,
Jun 13, 2016, 5:41:15 PM6/13/16
to Ian Hickson, Zach Anderson, mojo...@chromium.org
Are we concerned about the amount of boilerplate these features are introducing into the generated code?  What can we do to optimize code footprint?

Zach Anderson

unread,
Jun 13, 2016, 6:39:47 PM6/13/16
to Ian Hickson, mojo...@chromium.org
I have published new pub packages with these changes.

Cheers,
Zach

Zach Anderson

unread,
Jun 13, 2016, 6:55:37 PM6/13/16
to Jeff Brown, Ian Hickson, mojo...@chromium.org
There are certainly a couple of places where it should be straightforward to tighten up the generated Dart code, but I think footprint is really something that should be tracked across all the pieces in aggregate, not just the text of the generated Dart bindings.

Cheers,
Zach

Jeff Brown

unread,
Jun 14, 2016, 1:21:55 AM6/14/16
to Zach Anderson, Ian Hickson, mojo...@chromium.org

I agree.  It's something we should benchmark.

I mention this due to prior experiences with protobufs on Android.  Code footprint (especially method count) was a major cause of us officially discouraging use of protobufs in Android which was inconvenient to many people.  Even with lighter weight code generators, new tools were needed to work around limits in the VM.

The economics may be different for Dart, but in general, the more code, the longer it takes to download a program and get it running.

For example, I suggested to Vardhan that several common Mojo encoding operations could be efficiently implemented using tables while reducing footprint.

Anyhow, stuff to think about.

Jeff.

Reply all
Reply to author
Forward
0 new messages