- From: Glenn Maynard <glenn@zewt.org>
- Date: Sun, 13 Oct 2013 20:11:04 -0500
- To: Jonas Sicking <jonas@sicking.cc>
- Cc: "piranna@gmail.com" <piranna@gmail.com>, James Greene <james.m.greene@gmail.com>, David Bruant <bruant.d@gmail.com>, public-webapps <public-webapps@w3.org>, Andrea Marchesini <amarchesini@mozilla.com>, David Rajchenbach-Teller <dteller@mozilla.com>
- Message-ID: <CABirCh_wd0Xp_+BC=DKT5fYpH5GWz9ATu=nRrw5ukRpFKWvbTg@mail.gmail.com>
What I really dislike about this is that the worker can't send the port directly to a UI thread if it's a nested worker; it has to send it to its parent, who has to forward it to its parent, and so on. That seems like it'll make it hard to implement libraries, since libraries needs to have its fingers in every one of your workers' main message port, or the author needs to invent a new message routing mechanism (which is something message ports are supposed to do for us). For example, the below example can't be easily modularized and made into a library. If regular ports could be used, you could wrap both sides in "initAlertHandling(messagePort)", handing it a port to communicate over. The deadlock prevention algorithm I proposed earlier attempted to address this, but it was too complex. It attempted to allow as close to arbitrary message passing as possible without allowing deadlocks; maybe there's a less permissive approach that would be simple enough. Here's an alternate proposal. It attempts to allow transferring these ports in any way, across regular MessagePorts, but it no longer tries to implement blocking with regular MessagePorts. It keeps the SyncMessageChannel, MessagePortSyncSide and MessagePortAsyncSide interfaces of your proposal to simplify things: - When a SyncMessageChannel is created, store an identifier for the current thread in both ports, called the port's initial thread. This property stays the same as the port is transferred around. - Add a property to both ports called "transferred first", initially null. If MessagePortSyncSide is transferred and its "transferred first" property is null, set its "transferred first" to true and the "transferred first" property of its corresponding MessagePortAsyncSide to false. The same is true in reverse. (This is possible because, the first time either port is transferred, they're obviously still in the same thread.) - If either type of port is transferred to an illegal thread, the recipient thread automatically calls close() on the port. - Descendants of a MessagePortSyncSide's initial thread are always legal threads. Additionally, if the port's transferred first value is true, the initial thread itself is also a legal thread. - Ancestors of a MessagePortSyncSide's initial thread are always legal threads. Additionally, if the port's transferred first value is false, the initial thread itself is also a legal thread. Like the previous proposal, this requires that the browser can get a view of the thread tree. Since all of the checking happens in the thread that receives the port, and not the sending port, there are no race conditions due to not knowing the target of a port in advance. The "transferred first" field allows creating a SyncMessageChannel, and then either 1: transferring the MessagePortSyncSide to a child worker, or transferring MessagePortAsyncSide upwards to a parent worker or the UI thread. You can't do both; once you transfer a port once, that property is immutable. Simple put, if you transfer either port across the boundary defined by the initial thread, the ports are shut down to prevent the possibility of deadlocks. > any waitForMessage(); With regular messaging you have an Event containing a .data property with the posted message. Here, you just have the message. That'll make adding metadata difficult. Are we sure that's what we want? (One thing we'd lose is the message's origin; I'm not sure on a quick reading if this is meant to be supported for message channels, but it looks like it is.) An aside: one thing you'll want to be able to do is block on multiple sources, possibly with a timeout, like select(). I think the way this API would allow that is for the user to create a "multiplexer" thread that does all of the listening asynchronously, and then a single blocking port is used between the mux thread and the real thread. On Sun, Oct 13, 2013 at 2:39 PM, Jonas Sicking <jonas@sicking.cc> wrote: > * yield only works within generators in JS. > And if that's like Python, yield only goes up one level, and then the caller has to yield too. Generators are useless for wrapping async APIs unless you structure your entire codebase around it. It's very useful (its incarnation in Python, at least), but has no relevance to the problems the API this thread is discussing. To give a the simplest example of what you can do with this API: implement confirm() in workers. --- worker --- ({ var channel = new SyncMessageChannel(); postMessage({ port: channel.asyncPort }); var _confirmPort = channel.syncPort; confirm = function(message) { _confirmPort.postMessage(message); return _confirmPort.waitForMessage().result; } })(); if(confirm("Delete everything?")) ... --- main thread var worker = createWorker(); // create the above worker worker.onmessage = function(e) { var confirmPort = event.data.port; confirmPort.onmessage = function(e) { var answer = confirm(e.data); confirmPort.postMessage(answer); } } --- This gives a blocking confirm() that works in workers, that can be used with no other special work in the worker. (Generators would not allow that.) -- Glenn Maynard
Received on Monday, 14 October 2013 01:11:34 UTC