Nov 14, 2008, в 3:59 AM, Ian Hickson написал(а):

For the sake of completeness, a connect/startConversation method on a
worker really should automatically open the receiving port - this is
what examples posted so far implied, and it would cause a lot of
aggravation if it didn't. I know I'm often forgetting to open the port
when writing my tests, and it's not a very easy mistake to spot.

What do you mean by "open the port"? Do you mean calling start()? If so, that should happen automatically when you set onmessage the first time,
per spec.


Oh, that's my mistake - I totally didn't expect that it could have such side effect. It seems weird that addEventListener("message", ...) does not have such effect, does it?

In an async processing model, there is simply no way for the receiver to
have a list of all objects that were posted to it - it's exactly the
reason for the existence of the queue that events are delivered
asynchronously and cannot be peeked before being delivered. For example,
in a multi-process implementation, these events may still be across
process boundary.

It actually doesn't really matter if there is something that has been
posted but not yet received, because that is indistinguishable (as far as I can tell) from the case of the worker having shut down a split second
before that object was posted.

I'm not sure what state you mean by "shut down" here - the spec does not define this, and shutting down a side of an async communication channel is complicated (see e.g. a TCP/IP state diagram). Anyway, the contents of "the worker's ports" is used for defining "active needed worker" and "suspendable worker" further on, which are concepts that are very important for worker lifetime definition. If the ports in event queue are not important, then the spec should not say that they are included in "the worker's ports". This would resolve the concurrency problem, but I don't think that the resulting behavior would be desirable.

It is not possible to have a symmetric relationship in an asynchronous messaging model - we need a multi-step entagling/unentangling protocol, so the relationship is necessarily asymmetric. One can't freeze another
process (or really, even another thread) to change something in it
synchronously.

The above is not a requirement, it's just a description of the concept. I don't think anything actually depends on it being symmetric; all the parts
that actually entangle ports have (or, are intended to have, maybe I
missed some) pretty well-defined synchronisation points.

OK, say there is a pair of entangled ports in different threads/ processes, portA and portB. We concurrently post both with postMessage, which causes the ports to be cloned. From the point of view of first thread, PortA is now unentangled, and portA' is entangled with portB. From the point of view of second thread, PortB is unentangled, and portB' is entangled with portA.

Next, threads send asynchronous notifications to each other, asking to update entangling information. First thread's notification asks portB to become entangled with portA'. So, portB will need to forward this notification to portB' (and possibly further, because portB' may have been posted and cloned again). This already is unduly complicated.

Now consider that all these ports need to have destroyed sooner or later, but not too soon. This basically means that we now have a many- to-many distributed GC system. It was bad enough when we had to garbage protect ports between threads, because this required modification of the JavaScript interpreter to support a certain case of distributed GC. But this example basically shows that we need a full-blown distributed GC system in order to implement port cloning.

For example, any method that entangles two ports blocks until both threads are synchronised
and entangled.

This will cause deadlocks - if portB' is sent to the first thread as portB'' in the above scheme, the lock will not let synchronization ever finish.

(The spec is somewhat implicit about this, but the intent is that workers
really be implemented either as two system threads, one doing
communication and one running the JS, or by one system thread that runs the JS in an interruptible fashion. In particular, doing something that synchronises with a worker isn't expected to have to wait for that worker
to finish running its current JS.)

The JS thread will need to be interrupted in any case - we certainly don't want it to read a half-written pointer from memory or something. Adding memory barriers around access to data that can be modified externally is not sufficient, because MessagePort algorithms are not designed in a lock-free fashion (lock-free algorithms that only rely on read/write atomicity do exist, but these aren't such). Locking around all MessagePort functions will cause deadlocks, as demonstrated above, and is generally against best practices. A middle ground may exist, but it may not, and it's definitely hard to find.

I don't think that pursuing a design that relies on locking is particularly promising - for the same reason that workers do not expose shared data to JS programmers, it is highly desirable to not rely on shared data in implementations, too (except for a few well understood constructs, such as an event queue). So, I think that the specs (Web Workers and HTML5 channel messaging) should be cleaned up from anything that mentions synchronous access to entangled port's data structures to really be verified for correctness. This is not straightforward, and may seriously affect the API - e.g., I doubt that passing MessagePorts around is implementable with reasonable complexity, and there is not a lot of use in MessagePorts if they cannot be passed around.

- WBR, Alexey Proskuryakov

Reply via email to