Re: Simultaneous destructors
Chris Jones <[email protected]>
| Newsgroups | gmane.comp.mozilla.devel.dom |
|---|---|
| Message-ID | <[email protected]> |
Chris Jones wrote:
> Chris Jones wrote:
>> Benjamin Smedberg wrote:
>>> How does IPDL deal with destructors that are fired simultaneously
>>> from both
>>> sides, e.g. ~PBrowserStream? Even if we make one side "win the race",
>>> won't
>>> IPDL reject the second destructor message (because it has an invalid
>>> actor
>>> ID) and abort the process? If it doesn't abort the process, what is the
>>> behavior (return values) of the second destructor message?
>>>
>>
>> Right now, IPDL treats dtors as it does any other message, and by "any
>> message" standards that's not a race.
>>
>> But this is just a good old fashioned bug, as dtors define an implicit
>> transition, [alive]-->DEAD. I'll add this in soon.
>>
>
> Hmm, I was hasty in my response. On further consideration this is a
> deeper problem.
>
> There are two facets to the problem. The first mainly applies to
> nominally stateless, inherently racy protocols like NPAPI. The example
> you present with ~PBrowserStream forces a design decision: whether or
> not to allow destructor messages to race. I would prefer to just
> disallow racy destructors; I think there's a workaround for the NPAPI
> (generally, crappy API) case. The workaround is two-phase destruction.
> For example:
>
> protocol PBrowserStream {
> child: RequestDestruction; // first phase
> parent: NotifyDestruction; // first phase
> };
>
> protocol PPluginInstance {
> manages PBrowserStream;
>
> parent: PBrowserStream;
> ~PBrowserStream; // second phase
> };
>
> (I'm not sure "which" NPStream the PBrowserStream models; I'm assuming
> it's the one created with NPN_NewStream() and so sort of "owned" by the
> child).
>
> If the browser wants to destroy the BrowserStream, instead of directly
> invoking the dtor, it destroys the "real" NPStream wrt to NPAPI and then
> sends "RequestDestruction." In the common case, the plugin will be idle
> wrt the stream and simply send back the actual ~PBrowserStream message.
>
> If the plugin wants to destroy a BrowserStream, it first sends
> "NotifyDestruction" to warn the browser. In the common case, the
> browser will be idle wrt the stream, clean up the NPStream, and send
> "RequestDestruction" back to the plugin, upon which ~PBrowserStream will
> be sent.
>
> If, however, RequestDestruction and NotifyDestruction race, it causes no
> problems as the browser is guaranteed to have processed
> NotifyDestruction *before* ~PBrowserStream arrives. The C++
> implementations of browser stream actors just have to be careful not to
> double-"free" the NPStreams.
>
> That's one option, again (to me eyes) a hack around NPAPI limitations.
> Another option for this case is to *allow* racy destructor messages. All
> that has to be done in IPDL is, only for protocols with racy
> destructors, not to blow up when a destructor references an unknown
> actor. This approach trades a safety check for racy destructors. Both
> are feasible and easy, but again I lean towards two-phase destruction.
>
> I now see that the second facet of this problem isn't worth discussing
> until we resolve this first issue.
>
OK, looks like we should cover both issues together.
As Benjamin just pointed out, destructors are kind of busted
semantically right now. C'est la guerre. (Beware the easy analogy to C++!)
I see a few ways to fix them.
(1)
Make destructor messages part of the protocol of the actors they
destruct, rather than part of the manager's protocol. This would imply
that dtors can't race with any other message (including other dtors),
since dtors transition into a special _DEAD_ state. (I.e., the dtor and
another message can never commute.) For example
protocol PluginInstance {
manages BrowserStream;
parent: BrowserStream();
// no more ~BrowserStream()
}
protocol BrowserStream {
child: M1();
parent: M2();
child: delete();
// could also be ~BrowserStream()
state S1: send M1 goto S2;
state S2; recv M2 goto DYING;
state DYING: send delete;
}
This proposal adds a new keyword "delete" which is understood to mean
"the destructor message for this protocol's actors." (We could also
just reuse ~Protocol, it's just a syntax thing that I don't have strong
feelings about.)
I think this would work quite well ... except for "stateless" protocols
like some in NPAPI. For them it's impossible to declare a non-racy
destructor. They could be special-cased in that for them, IPDL would
not treat sending a message to a nonexistent actor as a "fatal" error,
only to the extent that doing so would cause a spurious fatal error.
(Fancier implementations are possible.)
(2)
Keep dtors as part of the manager protocol, but only allow dtors to be
uni-directional. I.e., |both: ~Subprotocol()| would be explicitly
disallowed. Then, on the side that *sends* a particular dtor, receiving
a message addressed to a nonexistent actor would no longer be fatal.
(3)
Do either or none of { (1), (2) }, and treat destructor messages as
special in that the IPDL actors aren't "actually" destroyed until the
other side ACKs the dtor message. That is, actors can continue to
receive messages (though not send them) until the other side replies to
the dtor message. It's not really clear what should happen when a
"half-deleted" actor receives a message. Options:
(a) IPDL automatically returns EDESTROYED or something like that
(b) IPDL invokes a special handler that decides what to do
(c) the actor is marked as DEAD, that state is visible to C++, and
it's up to the implementation to take care of itself.
Strong opinions? I lean towards (1) plus (3a), that is, use (3a) for
stateless protocols.
Cheers,
Chris