Re: Sync API for workers

On 09/06/2012 05:03 AM, Jonas Sicking wrote:
> 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.


I think I prefer your approach over the proposals 1 and 2 because it makes it
clear that it is all about different communication channel so it should be
more obvious to the API user that data ordering can't be guaranteed.
So, I might be able to live with your proposal.
Although, merging SyncMessageChannel and proposal 2 might be good.
Since if we're giving up with the issues with data ordering, we could
give up with waitForMessage too and have something like
syncPort.postSyncMessage(); which would block the caller until reply is received.
However, that would effectively make the channel request-response only, not truly
bi-directional. But I think that is a good thing if MessagePortSyncSide doesn't
have any callbacks for handling incoming messages.



As a syntactic sugar, something like this could be nice
var c = new SyncMessageChannel();
// send ports to the right places

asyncPort.start({
   doSomething: function(message) {
     // do something with message.data
     message.reply("did something");
   },
   doSomethingElse: function(message) {
     // do something with message.data
     message.reply("did something else");
   }
})

var returnValue1 = syncPort.doSomething("data1");
var returnValue1 = syncPort.doSomethingElse("data1");


>
> 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 08:58:19 UTC