Re: Sync API for workers

On Mon, Sep 3, 2012 at 8:55 PM, Glenn Maynard <glenn@zewt.org> wrote:
> On Mon, Sep 3, 2012 at 9:30 PM, Jonas Sicking <jonas@sicking.cc> wrote:
>>
>> We can't generically block on children since we can't let the main
>> window block on a child. That would effectively permit synchronous IO
>> from the main thread which is not something that we want to allow.
>
>
> The UI thread would never be allowed to block, of course.  The "getMessage"
> API itself would never even be exposed in the UI thread, regardless of the
> state of this flag.

The problem with a "Only allow blocking on children, except that
window can't block on its children" is that you can never block on a
computation which is implemented in the main thread. I think that cuts
out some major use cases since todays browsers have many APIs which
are only implemented in the main thread.

APIs only existing on the main thread is likely going to always be the
case, even once browsers get better at implementing APIs in the main
thread and worker threads at the same time, since there will always be
some APIs that are main-thread only, like the DOM.

>> Your proposal makes it possible for pages to avoid the problems
>> described in my email by setting up a separate channel used for
>> synchronous messages. But some of the problems still remain. As soon
>> as a message channel is used for both synchronous and asynchronous
>> messages you can easily get into trouble. If someone calls the
>> blocking waitForMessage() function and receive a message which was
>> intended to be delivered asynchronously there is no good recourse.
>> Basically any time that happens there are only bad options available,
>> many of which have subtle problems that only happen intermittently
>> like the ones I described in my initial email.
>>
>> Since that is the case, I think the best solution is to always force
>> separate channels to be used for synchronous and asynchronous
>> messages.
>
> If you have messages that must be received synchronously, and other messages
> that must be received asynchronously, then that's precisely a time when
> you'd want to use MessagePorts to separate them.  That's what they're for.
> It's the same as using separate MessagePorts when you have two unrelated
> libraries receiving their own messages, so each library only sees messages
> intended for it.
>
> I agree that APIs that encourage people to write brittle code should be
> squinted at carefully, and we should definitely examine all APIs for that
> problem, but really I don't think it's the case here.

You are more optimistic than I am.

The fact that all the examples that people have used while we have
been discussing synchronous messaging have spun event loops in
attempts to deal with messages that couldn't be handled by the
synchronous poller makes me very much think that so will web
developers.

And the fact that using the existing communication channel is much
simpler than manually setting up a new one using |new MessageChannel|
and passing transferring ports using postMessage further adds to that.

So I would say that there's a high degree of risk that people will get
this wrong.

> 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 also wonder if what you are describing doesn't make more sense when
communicating with a child worker and blocking on receiving a response
from it.

Another trait that this looses is the ability to terminate a worker as
soon as we know that a synchronous response can't be sent. I.e. in
proposal 1 and 2 the implementation can terminate the worker as soon
as the object with the .reply() function is GCed. Note that this
doesn't expose any GC behavior since a "forever blocked" worker
behaves exactly the same as a terminated worker. I.e. neither will
ever execute any code.

>> All in all this is a much more complicated setup though. I think it'd
>> be worth keeping the simpler API like the 1 or 2 proposals even if we
>> do introduce SyncMessageChannel since that likely covers the majority
>> of use cases.
>
>
> Those proposals seem much more complex to me.  You can't send a message that
> will be received synchronously unless the other side prompts you for one
> first; you have to care whether the other side is acting synchronously or
> asynchronously.  It's a bunch of new concepts ("synchronous messages",
> "message replies"), instead of a simple (to users, at least) addition to
> MessagePorts.

Fewer APIs isn't the same thing as a simpler API. On the contrary, I
think trying to fit too much functionality into the same set of
functions can easily result in more complexity.

I think this is fairly well illustrated by the set of rules that you
ended up having to set up in order to make the "blocking permitted"
flags work out correctly. And your algorithm produces weird edge
cases, such as that it matters if someone sets up a message "proxy"
which forwards all messages from one channel to another, rather than
just passes on one end of a channel. With such a "proxy" your ports
end up touching more threads and so are more likely to clear the
"blocking permitted" flag. In all other cases such a proxy is
transparent.

The rules I ended up with were much simpler, and all rules could all
be checked synchronously meaning that if there's a bug in the code, an
exception is immediately thrown, rather than thrown much later once
someone tries to use a channel for synchronous messages which had been
passed around one time too many.

/ Jonas

Received on Wednesday, 5 September 2012 07:50:41 UTC