Bug 13686 - Add example of calling start() on a port
Add example of calling start() on a port
Status: RESOLVED FIXED
Product: WebAppsWG
Classification: Unclassified
Component: Web Messaging (editor: Ian Hickson)
unspecified
Other other
: P3 normal
: ---
Assigned To: Ian 'Hixie' Hickson
public-webapps-bugzilla
http://www.whatwg.org/specs/web-apps/...
:
Depends on:
Blocks:
  Show dependency treegraph
 
Reported: 2011-08-05 15:50 UTC by contributor
Modified: 2012-07-10 21:19 UTC (History)
10 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description contributor 2011-08-05 15:50:03 UTC
Specification: http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html
Multipage: http://www.whatwg.org/C#handler-messageport-onmessage
Complete: http://www.whatwg.org/c#handler-messageport-onmessage

Comment:
Either remove the special case from onmessage (to call start()) or add it also
to addEventListener listeners

Posted from: 91.154.41.96
User agent: Mozilla/5.0 (X11; Linux x86_64; rv:8.0a1) Gecko/20110804 Firefox/8.0a1
Comment 1 Anne 2011-08-05 16:10:49 UTC
If we change addEventListener that should be done by having a special hook in DOM Core. I do not think that is a good idea though.
Comment 2 Olli Pettay 2011-08-05 16:45:26 UTC
(In reply to comment #1)
> If we change addEventListener that should be done by having a special hook in
> DOM Core.
Why?
Comment 3 Ian 'Hixie' Hickson 2011-08-06 06:08:37 UTC
I don't think changing addEventListener() makes sense. It's one thing to have an attribute with magic, it's quite another to have an API that is magical only in certain circumstances.

Anyway, it doesn't make sense to make addEventListener() magical here. If you're using it, you almost certainly are doing something more complicated than just hooking a listener and using the port — you're probably hooking a series of listeners, possibly not all in the same task, each to look for one particular type of message. IIRC there's an example of this in the spec. For that use case, it doesn't make sense to start the port quickly.
Comment 4 Anne 2011-08-06 07:55:57 UTC
(In reply to comment #2)
> Why?

Because otherwise it is not explicit that addEventListener() has special behavior for certain event types.
Comment 5 Olli Pettay 2011-08-06 09:42:39 UTC
(In reply to comment #3)
> Anyway, it doesn't make sense to make addEventListener() magical here.
Why does it make sense to have onmessage magical then.
onmessage is the only magical onfoo listener there is, and we shouldn't 
design inconsistent APIs.

Atm there are two inconsistencies. onmessage vs addEventListener and
onmessage vs other onfoo listeners.
What I'd really like to see is to removal of both inconsistencies, but
making just the MessagePort API internally somewhat consistent by
having onmessage and addEventListener working the same way could be enough.
Comment 6 Ian 'Hixie' Hickson 2011-08-08 21:26:24 UTC
It makes sense to have onmessage be magical because you can only ever hook one event listener using an event handler attribute, and if you are hooking one event listener, requiring you to start() the queue immediately as well is just extra boilerplate. The common case should be simple.
Comment 7 Olli Pettay 2011-08-09 10:08:36 UTC
We shouldn't add strange special cases to web apis.

onfoo listeners add an event listener to bubble phase, nothing else.
Comment 8 Anne 2011-08-13 11:45:35 UTC
That is not true for e.g. onerror or events implementing BeforeUnloadEvent. Event handlers have a couple of special cases already.
Comment 9 Olli Pettay 2011-08-14 14:08:45 UTC
Setting onerror doesn't have anything special.
(The event handling does have, but that is a different thing)
Comment 10 Ian 'Hixie' Hickson 2011-08-14 19:14:07 UTC
(In reply to comment #7)
> We shouldn't add strange special cases to web apis.

I agree. This isn't a strange special case.


> onfoo listeners add an event listener to bubble phase, nothing else.

If we did this we would be complicating the API in a strange way with no benefit to anyone except theoretical purity.
Comment 11 Olli Pettay 2011-08-14 20:12:31 UTC
This is a similar inconsistency as load event handling
(doesn't propagate from Document to Window); each documentation 
describing the generic onfoo handling needs to say that in one special
case onfoo does something else than usually, like each documentation describing
event propagation needs to special case load event.

I tried to fix load event handling five years ago, but that broke way too
many web sites. In this case the spec breaks consistency without no reason - 
let's not do that.
Comment 12 Ian 'Hixie' Hickson 2011-08-15 05:06:17 UTC
EDITOR'S RESPONSE: This is an Editor's Response to your comment. If you are satisfied with this response, please change the state of this bug to CLOSED. If you have additional information and would like the editor to reconsider, please reopen this bug. If you would like to escalate the issue to the full HTML Working Group, please add the TrackerRequest keyword to this bug, and suggest title and text for the tracker issue; or you may create a tracker issue yourself, if you are able to do so. For more details, see this document:
   http://dev.w3.org/html5/decision-policy/decision-policy.html

Status: Rejected
Change Description: no spec change
Rationale: I disagree. I think that if we _don't_ have this in the spec, tutorials will be forced to tell people over and over "don't forget to call start(), man, isn't it lame that it isn't called automatically". They don't have to say anything at all at the moment. I don't think anyone will even realise anything magical is happening here unless they are advanced authors registering multiple event listeners, and then they'll be happy that it doesn't also happen in that case.
Comment 13 Simon Pieters 2011-08-15 17:13:48 UTC
For the record, I agree with Ian here.
Comment 14 Jacob Rossi [MSFT] 2011-09-07 20:20:04 UTC
(In reply to comment #12)
> EDITOR'S RESPONSE: This is an Editor's Response to your comment. If you are
> satisfied with this response, please change the state of this bug to CLOSED. If
> you have additional information and would like the editor to reconsider, please
> reopen this bug. If you would like to escalate the issue to the full HTML
> Working Group, please add the TrackerRequest keyword to this bug, and suggest
> title and text for the tracker issue; or you may create a tracker issue
> yourself, if you are able to do so. For more details, see this document:
>    http://dev.w3.org/html5/decision-policy/decision-policy.html
> 
> Status: Rejected
> Change Description: no spec change
> Rationale: I disagree. I think that if we _don't_ have this in the spec,
> tutorials will be forced to tell people over and over "don't forget to call
> start(), man, isn't it lame that it isn't called automatically". They don't
> have to say anything at all at the moment. I don't think anyone will even
> realise anything magical is happening here unless they are advanced authors
> registering multiple event listeners, and then they'll be happy that it doesn't
> also happen in that case.

>> you almost certainly are doing something more complicated than
>> just hooking a listener and using the port

I do not believe that the use of addEventListener necessarily indicates advanced developers--it's what we're encouraging developers use as *the best* way to subscribe to events. 

You make the argument that not automatically starting the port makes the developer model difficult. I agree, but I think that applies to addEventListener too (I see the same "isn't it lame that it isn't called automatically" appearing in a tutorial wrt addEventListener). 

Events are queued, so I don't really buy the need for more granularity over when you start the port when using addEventListener versus the property. Additionally, if my script isn't the only one registering for the event, then I don't want to have to worry about whether someone has already started the port or not.

I agree with Olli's statement and think it's a bigger disservice to the developer model to have one registration method work different than another. 

Special casing an event property may be easier to spec, but it's not necessarily any easier to implement. We have generic implementation that allows us to designate a property as an event registration method. Then it just falls out that setting that property registers a listener for the bubble phase. Special casing onmessage isn't really any easier to implement than also special casing addEventListener. 

In fact, it's probably easiest to implement (at least for IE) and the most straight-forward for web developers if the spec says that you should start the port upon registration of the first message event listener. Then you don't even have to spec something special for addEventListener or onmessage.
Comment 15 Ian 'Hixie' Hickson 2011-10-17 22:32:38 UTC
addEventListener() is not the best way to register event listeners. Any advocacy to that effect is wrong, IMHO. It's more complicated than the event handler attributes, and is only useful if there's going to be multiple handlers, which will rarely be the case in a worker.

We definitely don't want to special-case addEventListener(). It's one thing to have a particular attribute on a particular object have magical behaviour. It's _quite_ another to have a method on an interface implemented on half the objects of the platform have magical behaviour for one particular combination of arguments on one particular object. IMHO that's a complete non-starter.

We could remove the magic entirely, requiring start() to be invoked. But that makes the API a pain to use in the default case, which seems worse to me than having the magic in the first place.

Note that for compat reasons we're going to have to special case at least one other specific event handler IDL attribute, so a generic mechanism won't work for all cases anyway (see bug 14037).
Comment 16 Olli Pettay 2011-10-17 22:46:24 UTC
(In reply to comment #15)
> addEventListener() is not the best way to register event listeners. Any
> advocacy to that effect is wrong, IMHO. It's more complicated than the event
> handler attributes, and is only useful if there's going to be multiple
> handlers, which will rarely be the case in a worker.
> 
> We definitely don't want to special-case addEventListener(). It's one thing to
> have a particular attribute on a particular object have magical behaviour. It's
> _quite_ another to have a method on an interface implemented on half the
> objects of the platform have magical behaviour for one particular combination
> of arguments on one particular object. IMHO that's a complete non-starter.
I could agree with that. In general I don't like any special cases - I want
consistent APIs


> 
> We could remove the magic entirely, requiring start() to be invoked. But that
> makes the API a pain to use in the default case,
Really? How? Is calling a method painful?

> Note that for compat reasons we're going to have to special case at least one
> other specific event handler IDL attribute, so a generic mechanism won't work
> for all cases anyway (see bug 14037).
bug 14037 has nothing to do with setting an event handler to a valid 'this'
object, like this bug has.
Comment 17 Ian 'Hixie' Hickson 2011-10-18 20:27:11 UTC
> Really? How? Is calling a method painful?

Boilerplate code (code you have to always include, which is not solving your problem but is just working around limitations of an API) is painful.
Comment 18 Jacob Rossi [MSFT] 2011-10-18 20:43:04 UTC
I don't understand why it has to be special cased for addEventListener or the property. Tie it to the existance of at least one listener to the event. Upon registering the first listener, you start the port. 

Having implied behavior for one event registration mechanism that isn't present for another (and that behavior has nothing to do with the event registration itself) is inconsistent. Inconsistent APIs are worse than boilerplate IMO.

But I think this can be resolved without the need for the boilerplate by just starting the port upon registering the first listener.
Comment 19 Olli Pettay 2011-10-18 21:58:25 UTC
(In reply to comment #17)
> > Really? How? Is calling a method painful?
> 
> Boilerplate code (code you have to always include, which is not solving your
> problem but is just working around limitations of an API) is painful.

But why are addEventListener and onmessage any different?

I still think for consistency we should not add any special cases when just
possible. And in this case nothing requires us to add a special case.
It would be just terrible API if addEventListener and onmessage would 
be handled in a different way.
Comment 20 Anne 2011-10-19 03:25:12 UTC
(In reply to comment #18)
> But I think this can be resolved without the need for the boilerplate by just
> starting the port upon registering the first listener.

That is a special case too. Adding and removing event listeners should not trigger side effects. (Note that this is not the same as setting some event handler attribute having side effects.)
Comment 21 Ian 'Hixie' Hickson 2011-10-26 23:35:04 UTC
What the spec currently says is the least bad compromise, IMHO.
Comment 22 Jacob Rossi [MSFT] 2011-10-27 00:42:49 UTC
(In reply to comment #21)
> What the spec currently says is the least bad compromise, IMHO.

I think what is in the spec is worse. While I too hate boilerplate code, but I'd rather have to call start() always (whether addEventListener or event handler attribute) than have it behave one way for one registration method and another for the other method.

Here's another idea:
Let's remove the start() API altogether. Once a message port is constructed, it's started. It's up to the application to ensure it is ready to handle messages before giving the port out.

Is there really a valuable scenario where you need to create a port and add listeners but not have started it yet?
Comment 23 Travis Leithead [MSFT] 2011-10-27 17:03:29 UTC
There may be some scenarios wherein having the start() API is handy, however, I don't see any problem with letting the port be auto-started at the time the MessageChannel constructor is done. I think most use cases for MessagePorts are for setting up message channels between separate entities (Web Workers, IFrames, etc.) where the port has to be sent via postMessage. I don't think there's a problem of registering event handlers on the port before sending the port to it's final destination, and I also don't think the formerly assigned event handlers are supposed to fire on the old port after it's been sent.

Additionally, this follows the same model used by the Web Worker's "implicit" port (it is automatically started when the web worker is created).

+1 for removing the start() API.
Comment 24 Jonas Sicking 2011-10-27 17:45:55 UTC
That means that you can't create a message channel and send one of the ports somewhere using postMessage.
Comment 25 Olli Pettay 2011-10-27 18:02:23 UTC
Yeah, I don't think we can leave out start(), but we could make it obligatory.
Special cases just make APIs inconsistent, which is bad.
Comment 26 Travis Leithead [MSFT] 2011-10-27 18:25:41 UTC
(In reply to comment #24)
> That means that you can't create a message channel and send one of the ports
> somewhere using postMessage.

I don't understand. Why not? Aren't started message ports just as capable of being sent via postMessage as un-started ports?
Comment 27 Jonas Sicking 2011-10-27 21:37:58 UTC
Consider a page that wants to set up a communication channel with a widget running inside an iframe.

The page creates a MessageChannel and sends one of the ports to the iframe. When is it safe for it to start sending commands into the other port?

If it starts sending them immediately then by the time the messages arrive, the iframe might not yet have received its port. Even worse is if the widget in the iframe decides that it wants to handle the messages using a worker and so it doesn't attach a event listener but rather simply sends the message to the worker.

In this setup, the only safe thing to do is for the original page to wait with sending messages until it's received a message from the other side.

First off, this puts unfortunate restrictions on what protocols are possible to use over message channels.

Second, it's very easy for page to forget about the race condition here and just start sending messages. The result will be random bugs.

Third, it doesn't work for more complex scenarios.

For example, what if the page creating the message channel want's to send "it's" end to a worker. This means that it's not safe for the widget to start sending messages at any time. I.e. neither side will know when the other side is ready to start receiving messages.
Comment 28 Ian 'Hixie' Hickson 2011-10-28 19:48:14 UTC
Yeah we definitely can't just start sending messages before the event handler has even been attached. You'd end up always dropping some messages on the floor.
Comment 29 Jonas Sicking 2011-10-28 23:33:49 UTC
Another problem is that any changes here are going to be hard to make given that workers have been deployed for a while at this point.

I personally prefer to keep the current situation. It's not ideal, but neither is any of the proposed alternatives.
Comment 30 Olli Pettay 2011-10-29 00:09:52 UTC
( I think only two implementations support MessagePorts, and
don't see the API in anyway very stable yet.)

IMO, for API consistency and sanity, start() should be called
explicitly.
Comment 31 Jonas Sicking 2011-10-29 00:54:47 UTC
Ports are also used in shared workers. And there are implicit ports in dedicated workers which I *think* use the same semantics.
Comment 32 Olli Pettay 2011-10-29 10:38:17 UTC
So? I'm talking about all the objects implementing MessagePort interface, 
for which the spec say that setting onmessage calls start();
(addEventListener("message", function(){}) doesn't call start()).

onmessage is the only case where setting onfoo (to a valid 'this') has a
special case in the web platform, afaik. And I don't see any good 
reason for that. Special cases are bad for consistency.
Comment 33 Jonas Sicking 2011-10-30 02:35:13 UTC
I know what the bug is about.

I thought that we had the same semantics on the Worker and DedicatedWorkerGlobalScope interfaces, but it appears I'm wrong.

This does lessen the risk regarding breaking existing contents. But shared workers also do create MessagePort objects. So it's not just about MessageChannel.
Comment 34 Ian 'Hixie' Hickson 2011-10-30 17:04:05 UTC
Dedicated workers do use ports in the background, but they do the implicit call to start() when the worker is created, not when onmessage is set, because there's no race there, so we don't need it.
Comment 35 Travis Leithead [MSFT] 2011-10-31 17:22:26 UTC
(In reply to comment #27)
> In this setup, the only safe thing to do is for the original page to wait with
> sending messages until it's received a message from the other side.

That's true in general. I know this isn't specifically about workers, but implementations of web workers are not required to immediately start running the worker. In fact depending on the number of requested workers, a given worker may not get time on the CPU for awhile. In that case any code that needs to ensure communication must wait for the worker's first "hello" message before starting the protocol.

> Second, it's very easy for page to forget about the race condition here and
> just start sending messages. The result will be random bugs.

To clarify: the race you are describing is that messages already in the port's queue will be transferred to the destination port's queue (when sending the port via postMessage), and then immediately dispatched, but the destination won't have had an opportunity yet to setup a message handler, so the messages will be discarded. Is that it?

If so, then this problem may already exist (again, in the Worker scenario where the implicit port is auto-started). The worker's code is not required to assign an onmessage handler. Let's assume that the worker runs some async startup code and eventually assigns the onmessage handler. In this case, messages sent immediatly to the worker will have been dropped (i.e., dispatched but no message handlers processed the messages).

This was true in all the implementation I tested except Opera which has a "back buffer" of the last 5 recieved messages)--not sure if that implementation detail has changed in O12 or not.

My point is that defensive programming on a port requires some pre-flight ACKs in order to be reliable. Jacob's proposal does not change what web devs will need to do in practice.

> Third, it doesn't work for more complex scenarios.
> For example, what if the page creating the message channel want's to send
> "it's" end to a worker. This means that it's not safe for the widget to start
> sending messages at any time. I.e. neither side will know when the other side
> is ready to start receiving messages.

For brokered communication such as this, some type of high-level protocol will always be required. For example, both sides need to know how to process the data payload. Since some a-priori knowledge is required, knowing who goes first, and when it is safe to respond should be folded into the protocol.

Granted this is much more complicated, but we're balancing this against the problem of developers forgetting to call start() with addEventListener.
Comment 36 Travis Leithead [MSFT] 2011-10-31 17:26:15 UTC
(In reply to comment #35)

Also, what's to stop one end of these brokered communication scenarios from calling start() pre-maturely? Then you just end up with the same problem...
Comment 37 Ian 'Hixie' Hickson 2011-11-01 03:29:32 UTC
Yes, if a worker doesn't set up its onmessage handler in the main block of code, then it is liable to have the same race condition as the consumer of a port that calls start() before it's set up its handler. The point is that with a worker, there's always a time it can set up its handler safely, because the worker event loop doesn't start until after the worker has had the opportunity to register its listener. In the case of a port that's being sent to another part of the codebase through another port, that is not the case. This is why we need to make sure that messages aren't pumped until the code is ready, determined either by it registering its onmessage handler, or, for more complicated code using one or more addEventListener()s, by it calling start() explicitly.
Comment 38 Travis Leithead [MSFT] 2011-11-10 20:06:53 UTC
I discussed this with Jonas at TPAC 2011, and subsequently with Jacob. There are certainly some other approaches that could be taken to attempt to remove "start()" such that a port could be handed off from worker-to-worker asyncronously. However, there are an equal number of complexities with those proposals. Given where we are, the pre-existing implementations, and lack of anything obviously better, I'm in support of keeping the API the way it is.

I personally don't mind the magic in the onmessage event handler. I find that I personally use that technique for event handler binding much more frequently than addEventListener (simply because it's shorter and I rarely have multiple handlers).

Perhaps one outcome of this bug could be a short example in the spec for when you would need to call start() explicitly. (Use the example Hixie described previously if you want.)
Comment 39 Ian 'Hixie' Hickson 2012-01-30 20:49:41 UTC
I will add an example. That seems like a good idea.
Comment 40 contributor 2012-07-10 21:19:45 UTC
Checked in as WHATWG revision r7171.
Check-in comment: Explain start() with an example.
http://html5.org/tools/web-apps-tracker?from=7170&to=7171