[dmd-concurrency] draft 8: the final countdown

Andrei Alexandrescu andrei at erdani.com
Wed Feb 10 13:54:39 PST 2010



Michel Fortin wrote:
> Le 2010-02-10 à 15:29, Andrei Alexandrescu a écrit :
> 
>> Michel Fortin wrote:
>>> Le 2010-02-10 à 14:08, Andrei Alexandrescu a écrit :
>>>> Nonono. What should happen is no escape of *field* addresses 
>>>> because those are exposed to races when accessed naively. 
>>>> Synchronized methods do NOT assumes that the *indirections* 
>>>> starting from fields are locked/protected/non-shared. If I have
>>>> a pointer to int as a field: - inside the sychronized method,
>>>> the pointer itself is protected by the lock and can be
>>>> considered not shared. Escaping THE ADDRESS OF that pointer
>>>> would create races because it breaks the assumption that only
>>>> the method messes with it - the int pointed to by that field is
>>>> STILL considered shared by the method AND by the rest of the
>>>> world so there's never the risk of an undue race. The method
>>>> can escape it all it wants.
>>> I understand quite well your intent. I just disagree with it. My
>>> point is that it will happen quite often that the compiler's 
>>> assumption (that synchronization does not apply beyond
>>> indirections) isn't adequate: when you need to use an internal
>>> array as a buffer, or when you need some other hidden data
>>> structure. I understand that in those cases you need to cast your
>>> way around, fine. It looks crippled, but let's say that's okay. 
>>> What is much less shiny is that in those cases where you need a
>>> cast to do the right thing, the compiler will also let you do the
>>> wrong thing (let a value escape) without a cast. For instance, in
>>> your example of "casting away shared" with a 'List!double'
>>> member, nothing prevents a function from escaping a reference to
>>> the list as a shared(List!double), but doing so will lead to
>>> races.
>> There are no races because the List!double type is either
>> thread-unaware in which case there is next to nothing you can do
>> with, or is thread-aware in which case it knows how to fend for
>> itself.
> 
> Are you saying you cannot access public members of a thread-unware
> object? This would lead to a race in this situation.

You can, but they're qualified with shared, just the same way the 
synchronized method sees them. There is no race. I'm sure there is a 
misunderstanding somewhere.

> Same with a
> struct, or any primitive type. As soon as you read or write a
> variable from outside the lock you have a race.

Clearly there is a misunderstanding somewhere, so I need to improve on 
the explanations. A struct is stored in situ within the object. Locking 
the object protects the fields of the struct. The indirections of those 
fields remain shared. There is no race. Please provide an example that 
you believe exposes a race.

(By the way I'm talking about low-level races, the kind you get when you 
share data that's not qualified with "shared".)

>>> Instead of having T* members implicitly converted to shared(T)*,
>>> the compiler could just prevent any access to non-shared data
>>> through an indirection. So you'd still need a cast to do the
>>> right thing, but the compiler won't let you do the wrong thing by
>>> accident.
>> With the no escape rule you cripple people who rent; without it I
>> cripple people who own. Most importantly, I allow escaping of
>> members of class type which I think is an important case.
> 
> Not at all. All you have to do is enforce the no-escpae rule for T*
> but not for shared(T)*. The 'shared' qualifier in a synchronized
> class context has the implicit meaning of 'not-owned'. If you want to
> give a reference to someone, all you have to do is declare your
> members explicitly as shared(T)*, and then give a reference to the
> shared part. Done. Implicitly changing T* to shared(T)* is what is
> breaking the safeties.

I strongly believe there is no breakage of safety at all. Again, an 
example should be of help here.


Andrei


More information about the dmd-concurrency mailing list