Re: Sync API for workers

On Mon, Sep 3, 2012 at 4:32 PM, Jonas Sicking <jonas@sicking.cc> wrote:

> It seems hard to ensure that deadlocks can't happen if we try to allow
> blocking calls on generic MessagePorts, this is why we haven't been
> interested in doing that. I'm not saying it's impossible, but if
> someone wants to propose this, please keep in mind that we're not
> interested in proposals which allow deadlocks, so you'll need to prove
> that your proposal can't cause deadlocks.
>

(See below.)

Another problem you have is that the A, B and C events aren't run from
> the event loop like normal events. They are instead run from whatever
> callstack existed when someone decided to make synchronous call to the
> parent. This will give web developers exactly the same problem as
> we've had with Gecko code spinning the event loop. When doing
> something like that, you have to be absolutely sure that all code
> which exists up your call stack can deal with all of these messages
> getting dispatched. And all of those messages have to be able to deal
> with being dispatched under the existing callstack.
>

I think all of the problems you're describing only happen if there's just
one channel that you can post messages to, eg. if you can't block on
MessagePort but only the global port.  I think we can find a solution for
the MessagePort problem.  Once you can block on specific MessagePorts, you
no longer have the confusion of getMessage() returning messages meant for
other APIs.  (After all, isn't that what MessageChannels are for?)

Conceptually, I think this is possible.  You should only be able to perform
a blocking getMessage if the other side of the port is in a dedicated
worker who is a descendant of the current thread.  Here's an attempt:

- Add an internal flag to MessagePort, "blocking permitted", which is
initially set.
- When a MessagePort "port" is transferred from source to dest,
    - If source is an ancestor of dest, the "blocking permitted" flag of
"port" is cleared.  (This is a "down" transfer.)
    - Otherwise, if source is a descendent of dest, the "blocking
permitted" flag of "port"'s entangled port is cleared.  (This is an "up"
transfer.)
    - Otherwise, if source == dest, do nothing.
    - Otherwise, the "blocking permitted" flag of both "port" and its
entangled port are cleared.  (For example, a port was transferred to a
shared worker.)
- When the "blocking permitted" flag of any MessagePort is cleared, any
getMessage calls blocking on that port throw an exception.
- Calling getMessage on a port (with a nonzero timeout) whose "blocking
permitted" flag is cleared throws the same exception.
- Additionally, calling getMessage on a port (with a nonzero timeout) when
neither it nor its entangled port has ever been transferred to another
thread throws an exception.  (Blocking for data when the current thread
holds both sides of the port guarantees a deadlock.)

In other words, if a port is transferred "up" the thread tree, then it's
allowed to block downwards, but any port that's ever been transferred
"down" can not.  If you transfer a port down and then back up, then neither
side can ever block on the port (the flag has been cleared on both sides).
(The "clear the entangled port's flag" would presumably actually mean
sending a control message over the pipe, telling the other side to clear
the flag.)

This works for dedicated workers, where the ancestor/descendant concepts
make sense.  This wouldn't work for shared workers, which would never be
able to block.  (That's hard, since shared workers create cycles.  I don't
think any current proposal can support shared workers while also
disallowing deadlocks.)

Now, this approach can go one of two ways: we can either allow blocking up
the tree or down the tree, but we'd have to pick one or the other.  I'm
inclined to recommend blocking *down* the tree, since that allows use cases
like the ones you mentioned, eg. starting a thread to do IndexedDB calls,
which you (the parent) can then block on.

1.1 is nifty in that it allows us to use events while dealing with
> replies from multiple handlers. But it seems like it adds a feature
> that doesn't have any good use cases (at least I haven't heard any),
> solely for the purpose of giving us a good reason for using events.
> The result is both more code for us, and more API surface and syntax
> for developers.
>
> So I strongly prefer doing proposal 1 or 2 instead.
>

I believe what those proposals are effectively doing is creating a single
separate messaging channel for sync messages.  That's basically the same
effect as above, except in a way that introduces more APIs to the platform,
separates synchronous messaging from async messaging more than necessary,
and loses a lot of the flexibility of MessagePorts.

-- 
Glenn Maynard

Received on Monday, 3 September 2012 23:47:31 UTC