Re: Simultaneous destructors

Chris Jones <[email protected]>
Newsgroups gmane.comp.mozilla.devel.dom
Message-ID <[email protected]>
Chris Jones wrote:
> Benjamin Smedberg wrote:
>> I don't understand how the extra state helps, at least if there are any
>> async messages involved:
>>
>> protocol Channel {
>>   child: dataavailable();
>>   child: datadone()
>>   parent: cancel();
>>   parent: delete();
>>
>>   state NORMAL:
>>     send dataavailable goto NORMAL;
>>     send datadone goto DYING;
>>     recv cancel goto DYING;
>>
>>   state DYING:
>>     send delete;
>> }
>>
> 
> This protocol would fail type-checking because it has a race condition 
> in the NORMAL state, datadone vs. cancel.  The protocol is stateful, and 
> datadone/cancel don't adhere to the Diamond Rule.
> 

I was remiss in not showing how the races could be avoided in this 
example.  The way to avoid them is a two-phase delete like what I 
described in an earlier post.  For this protocol, it would look like


protocol Channel {
child:
     dataavailable();
     datadone();  NOTREACHED(); //<--ignore this
parent:
     moar();
     cancel();
     delete();

state NORMAL:
     send dataavailable goto SENTDATA;
     send datadone goto DONE;
     recv cancel goto CANCELED;

state SENTDATA:
     recv moar goto NORMAL;
     recv cancel goto DEAD;

state DONE:
     recv cancel goto DEAD;

state CANCELED:
     send dataavailable goto DEAD;
     send datadone goto DEAD;

state DEAD:
     recv delete goto __deleted__;

// IPDL would generate this "state" automatically, so it can be ignored
state __deleted__:  send NOTREACHED goto __deleted__;
};


Speaking generally, this protocol allows the child to send |cancel()| 
and the parent to send |datadone()| (the parent's equivalent to cancel() 
wrt to deletion) at any time.

If the child sends cancel(), then a delete sequence is initiated, 
finished by the child sending the actual |delete()| message back to the 
parent.  The tricky part here is that if the child sends an "abrupt 
cancel", a cancel from the NORMAL state, then the child must wait for 
the parent to ack the cancel with datavailable or datadone before 
sending the actual |delete()| message.

Likewise, if the parent sends |datadone()|, a delete sequence is also 
initiated.  Like |cancel()| sent from the NORMAL state, if |datadone()| 
is sent from NORMAL, then the child must ack with |cancel()| before it 
can send |delete()|.

The substantial modification from the original protocol is the addition 
of the SENTDATA state and the "moar()" message.  This captures the 
decision a child makes between wanting more data vs. cancelling.  It 
prevents two dataavailable() messages from being sent in a row.  This 
might seem strange, but we'll need this feature when the child and 
parent are transferring network data through two shmem buffers.  The 
|moar()| message is where the child would send back to the parent a 
handle to the "free" shmem buffer (i.e., the one it's not currently 
using).  (The parent would then fill that buffer and transfer it back to 
the child using |datavailable()|).

I agree that two-phase deletion is complicated, but (i) it's the price 
to be paid for race-free deletion; (ii) two-phase deletion is a kind of 
"IPDL pattern", like "empty subprotocols", that can be documented and 
copied by other protocols.

The alternative is, as I mentioned in the previous message, allowing 
|delete()| messages to be inherently racy even in stateful protocols, 
having IPDL detect when |delete()| indeed races, and generating special 
code to handle the races.  Personally I prefer expending the effort to 
declare non-racy protocols, like the one above.

Cheers,
Chris
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.