Re: Sync API for workers

On Wed, Sep 5, 2012 at 12:49 AM, Jonas Sicking <jonas@sicking.cc> wrote:
>> Importantly, the sending side
>> doesn't have to know whether the receiving side is using a sync API to
>> receive it or not--in other words, that information doesn't have to be part
>> of the user's messaging protocol.
>
> I agree that this is desirable trait. But so far I think the risks in
> encouraging event loop spinning outweigh the benefits.

I did some more thinking about this and came to the following conclusions:

The part that I dislike about having single channel used for both sync
and async messaging is that you end up with one or more async
listeners which expect to get notified about all incoming messages,
but then you have an API which "steals" a message away from those
listeners. On top of that it has to do that stealing without any way
of ensuring ensuring that it actually steals the right message. It has
to simply rely on prior information and hope that no unexpected
messages sneak in where not expected.

Basically any time you use the sync API to pull out a message and get
one that wasn't exactly the one you wanted, you have lost. I think all
solutions for dealing with that situation are too brittle and all have
side effects which will behave very intermittently and result in
hard-to-find bugs.

However, I do like the idea of the async side of a channel not caring
about if the other side is synchronous or not. Though no matter what
we do, the async side can't be completely agnostic to if the other
side uses blocking reads or not since if the other side wants to use
blocking reads, the async side is limited in where it can pass its
port.

But being (mostly) agnostic to if the other side is using sync
messages or not doesn't mean that the other side uses both sync and
async messaging! So what we could do is to create a SyncMessageChannel
where the async port looks exactly like the port of a normal
MessageChannel. That way a worker (or main thread) which provides an
asynchronous API by receiving a port to communicate to, can with no
effort both accept the port of a MessageChannel as well as the async
side of a SyncMessageChannel.

Hence I think something like the following would work:

[Constructor]
interface MessageChannel {
  readonly attribute MessagePortSyncSide syncPort;
  readonly attribute MessagePortAsyncSide asyncPort;
};

interface MessagePortSyncSide {
  void postMessage(any message, optional sequence<Transferable> transfer);
  any waitForMessage();
  void close();
};
MessagePortSyncSide implements Transferable;

interface MessagePortAsyncSide : EventTarget {
  void postMessage(any message, optional sequence<Transferable> transfer);
  void start();
  void close();

  // event handlers
           attribute EventHandler onmessage;
};
MessagePortAsyncSide implements Transferable;


Where there's the additional limitation that MessagePortSyncSide can
only be transferred though MessagePortAsyncSide.postMessage() or
Worker.postMessage(), and MessagePortAsyncSide can only be transferred
though MessagePortSyncSide.postMessage() or
DedicatedWorkerGlobalScope.postMessage().

Note that even MessagePortAsyncSide is fully API compatible with the
normal MessagePort, but code can still differentiate between the two
by using the instanceof operator or Object.toString().

I'm still not convinced that this is a better API than the ones
proposed in Olli's proposal 1 and proposal 2, but I think it's an
option.

What I like about proposals 1 and 2 is that they optimize for the
common case of simply wanting to perform a function call in a
different thread and waiting for an answer. We could even add a
.throw() function in addition to .reply() which makes the caller throw
an exception, very similar to ES6's generator.throw().

/ Jonas

Received on Thursday, 6 September 2012 02:04:29 UTC