Re: Sync API for workers

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.  I picture this being methods on
DedicatedWorkerGlobalScope and SharedWorkerGlobalScope.
(SharedWorkerGlobalScope's version would only get the zero-timeout polling
version.)

Also, the last "Otherwise, the "blocking permitted" flag of both
> "port" and its entangled port are cleared." has to apply any time when
> sending a port through a generic port rather than through a dedicated
> worker parent/child? When communicating with a generic port we never
> have any idea what is on the receiving end. And what is on the
> receiving end can change between the time when a message is sent, and
> when it is received.
>

Well, you can simplify the algorithm by always clearing the flag when
posting through anything but a dedicated worker's port.  That's
straightforward since you always know at send time what the relationship to
the receiver is--if it's through worker.postMessage it's to a child, and if
it's through postMessage on DedicatedWorkerGlobalScope it's to the parent.
It would be nice to do this in the more generic way, but this would be
enough for a lot of cases.

I suspect there's a way to make the general-case version work, though.  For
example, when a worker is transferred to another thread, include the thread
ID sending the port, as part of the metadata of the transfer.  The receiver
then knows where the port came from, and it knows itself, so it can see
where it lies relative to the sender to determine whether it was an "up",
"down" or a transfer that always invalidates both sides (sent to a worker
that is neither an ancestor nor a descendant).  If it determines that the
transfer invalidated the other side, then it sends a message across the
pipe saying "whoever you are, you need to clear your blocking-permitted
flag".  This would apply even if the other side changes hands in the
meantime, since once that flag is set, it's set permanently.

All that aside, do any implementations actually put dedicated workers in a
different process than their creator (if so, I'm curious as to why)?  This
should all be very simple if you don't do that, and I can't think of any
reason to--and good reasons not to, eg. fast ArrayBuffer transfers become
much harder.  Shared workers may cross processes, but dedicated workers?

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.

It seems much simpler to me to have only one kind of MessagePort, each
representing only one message channel.  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.  As a simple example, you can have a
worker thread whose protocol is simply:

- Send a message to the worker's port with a word.
- The worker sends a message to its parent with the word's definition.
(The mechanism of this lookup is black-boxed--it might be IndexedDB, or a
network request, or a complex combination.)

This means that the caller can use this worker's API synchronously or
asynchronously, without needing to define two interfaces and without the
child knowing the difference.  You can use it synchronously (if you're in a
worker yourself):

var worker = createDictionaryWorker();
worker.postMessage("elephant");
var definition = getMessage(worker); // wait for the answer

or asynchronously (from a worker or the UI thread):

var worker = createDictionaryWorker();
worker.postMessage("elephant");
worker.onmessage = function(e) { var definition = e.message; }

The worker doesn't care:

onmessage = function(e) {
    if(e.message == "elephant")
        postMessage("a large animal");
    else
        postMessage("don't know"); // worst dictionary ever
}

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.

-- 
Glenn Maynard

Received on Tuesday, 4 September 2012 03:55:58 UTC