February 01, 2010
Michel Fortin wrote:
>> The program would compile successfully with the following changes to nyukNyuk and sneaky:
>> 
>> shared(double)* nyukNyuk; // or: shared(double*) nyukNyuk; void
>> sneaky(ref shared(double) r) { nyukNyuk = &r; }
> 
> How can that be safe? Leaking the reference, even as 'shared', doesn't prevent someone from accessing the variable without acquiring the lock. Are you suggesting that mixing atomic operations and mutex synchronization for the same variable is okay? Because it's not.

You are making a good point. I thought of banning escapes altogether. After all, if someone has access to a shared data member and the synchronized method assumes it can temporarily optimize access to that member as if it's unshared, things can get messed up.

On the other hand, if you hand out a reference to a member, you're kind of setting yourself up for surprises. Far as the type system is concerned, it's doing the right thing.

One related thought: a long time ago, Walter, Bartosz and I briefly coquetted with the idea of using "scope" to denote an unescaping parameter:

void print(scope int[] array) { ... }

With this signature print guarantees it won't escape anything in array. Then synchronized functions can pass (addresses of) members to functions like print.

It's not an easy feature to implement though.


Andrei
February 01, 2010
On Mon, 01 Feb 2010 15:09:53 -0500, Andrei Alexandrescu <andrei at erdani.com> wrote:
> Robert Jacques wrote:
>> Page 24, P1: "The notable absent is real, which is the only a platform
>> dependent type that the implementation has discretion regarding atomic
>> sharing."
>> which is the only a platform dependent type => which is a platform
>> dependent type
>>  Page 28, P3: "good ole days of classic multithreading;"
>> ole => old
>
> Isn't "good ole" an Americanism?

You're right. It appears to be a regional contraction/variant/slang of old, like ol' and has been in use since 1832. But it appear it's mostly used when writing dialogue in a specific accent and not in formal prose. *shrug* I think old gets the point across and won't confuse non-Americans and non-English speakers.

>> Page 28 Comments:
[snip]
>> - Would a transfer function like this be possible?
>>     synchronized class BankAccount {
>>       private double _balance;
>>       void transfer(BankAccount dst, double amount) {
>>         enforce(_balance >= amount);
>>         _balance     -= amount;
>>         dst._balance += amount;
>>       }
>>     }
>>     or would you have to do it like the later example?
>
> Relaxation of field typechecking only happens for "this", not for other objects. If the above were allowed, it would only lock one object.
>
> Now, say you use atomic add for dst. In that case, you can implement transfer as above. It would be a mild violation of encapsulation because you deposit without calling deposit().

Even if you used an atomic add for dst you'd still get a race. Consider, thread A tries to deposit to dst and so takes a lock and reads dst._balance. Then thread B tries to transfer to dst and commits an atomic add. When thread A writes the updated balance back to dst._balance, the transfer is over-written and lost.

[snip]
>> Section 1.14.1 Comments:
>> If a reference to data protected by synchronization escapes, then races
>> can and will occur. This seems like a fundamental flaw in the design. I
>> think a cast should be required to enable an escape.
>
> Low-level races won't occur because at most you escape references to shared values. No?

See the transfer example above. This is a variant of the problem with STM
designs which which Walter posted a link to in January. In essence, a
value protected by a lock my always be protected by that lock.
http://www.bluebytesoftware.com/blog/Default.aspx#a4cfad7df-ed84-46a4-a961-54aafdaeb9d7

[snip]
February 01, 2010
On Mon, 01 Feb 2010 15:30:47 -0500, Andrei Alexandrescu <andrei at erdani.com> wrote:
> Michel Fortin wrote:
>>> The program would compile successfully with the following changes
>>> to nyukNyuk and sneaky:
>>>  shared(double)* nyukNyuk; // or: shared(double*) nyukNyuk; void
>>> sneaky(ref shared(double) r) { nyukNyuk = &r; }
>>  How can that be safe? Leaking the reference, even as 'shared',
>> doesn't prevent someone from accessing the variable without acquiring
>> the lock. Are you suggesting that mixing atomic operations and mutex
>> synchronization for the same variable is okay? Because it's not.
>
> You are making a good point. I thought of banning escapes altogether. After all, if someone has access to a shared data member and the synchronized method assumes it can temporarily optimize access to that member as if it's unshared, things can get messed up.
>
> On the other hand, if you hand out a reference to a member, you're kind of setting yourself up for surprises. Far as the type system is concerned, it's doing the right thing.
>
> One related thought: a long time ago, Walter, Bartosz and I briefly coquetted with the idea of using "scope" to denote an unescaping parameter:
>
> void print(scope int[] array) { ... }
>
> With this signature print guarantees it won't escape anything in array. Then synchronized functions can pass (addresses of) members to functions like print.
>
> It's not an easy feature to implement though.

What's difficult to implement specifically? Full escape analysis is difficult, but you can provide the same guarantees by being a bit conservative. In essence, by removing the assignment operator, except at declaration, you can prevent escapes. This does necessitate the creation of a new, transitive type (lent), but doesn't seem difficult to implement. Indeed, final was proposed a while back (during the const debates) to behave in mostly the same way (except not transitively). Although it is a bit long in the tooth now, I did write out a proposal, fleshing these ideas out: http://prowiki.org/wiki4d/wiki.cgi?OwnershipTypesInD.
February 01, 2010
Robert Jacques wrote:
> On Mon, 01 Feb 2010 15:09:53 -0500, Andrei Alexandrescu <andrei at erdani.com> wrote:
>> Robert Jacques wrote:
>>> Page 24, P1: "The notable absent is real, which is the only a platform
>>> dependent type that the implementation has discretion regarding atomic
>>> sharing."
>>> which is the only a platform dependent type => which is a platform
>>> dependent type
>>>  Page 28, P3: "good ole days of classic multithreading;"
>>> ole => old
>>
>> Isn't "good ole" an Americanism?
> 
> You're right. It appears to be a regional contraction/variant/slang of old, like ol' and has been in use since 1832. But it appear it's mostly used when writing dialogue in a specific accent and not in formal prose. *shrug* I think old gets the point across and won't confuse non-Americans and non-English speakers.
> 
>>> Page 28 Comments:
> [snip]
>>> - Would a transfer function like this be possible?
>>>     synchronized class BankAccount {
>>>       private double _balance;
>>>       void transfer(BankAccount dst, double amount) {
>>>         enforce(_balance >= amount);
>>>         _balance     -= amount;
>>>         dst._balance += amount;
>>>       }
>>>     }
>>>     or would you have to do it like the later example?
>>
>> Relaxation of field typechecking only happens for "this", not for other objects. If the above were allowed, it would only lock one object.
>>
>> Now, say you use atomic add for dst. In that case, you can implement transfer as above. It would be a mild violation of encapsulation because you deposit without calling deposit().
> 
> Even if you used an atomic add for dst you'd still get a race. Consider, thread A tries to deposit to dst and so takes a lock and reads dst._balance. Then thread B tries to transfer to dst and commits an atomic add. When thread A writes the updated balance back to dst._balance, the transfer is over-written and lost.
> 
> [snip]
>>> Section 1.14.1 Comments:
>>> If a reference to data protected by synchronization escapes, then races
>>> can and will occur. This seems like a fundamental flaw in the design. I
>>> think a cast should be required to enable an escape.
>>
>> Low-level races won't occur because at most you escape references to shared values. No?
> 
> See the transfer example above. This is a variant of the problem with
> STM designs which which Walter posted a link to in January. In essence,
> a value protected by a lock my always be protected by that lock.
> http://www.bluebytesoftware.com/blog/Default.aspx#a4cfad7df-ed84-46a4-a961-54aafdaeb9d7

my => may or must?

Andrei
February 01, 2010
Robert Jacques wrote:
> On Mon, 01 Feb 2010 15:30:47 -0500, Andrei Alexandrescu <andrei at erdani.com> wrote:
>> Michel Fortin wrote:
>>>> The program would compile successfully with the following changes
>>>> to nyukNyuk and sneaky:
>>>>  shared(double)* nyukNyuk; // or: shared(double*) nyukNyuk; void
>>>> sneaky(ref shared(double) r) { nyukNyuk = &r; }
>>>  How can that be safe? Leaking the reference, even as 'shared',
>>> doesn't prevent someone from accessing the variable without acquiring
>>> the lock. Are you suggesting that mixing atomic operations and mutex
>>> synchronization for the same variable is okay? Because it's not.
>>
>> You are making a good point. I thought of banning escapes altogether. After all, if someone has access to a shared data member and the synchronized method assumes it can temporarily optimize access to that member as if it's unshared, things can get messed up.
>>
>> On the other hand, if you hand out a reference to a member, you're kind of setting yourself up for surprises. Far as the type system is concerned, it's doing the right thing.
>>
>> One related thought: a long time ago, Walter, Bartosz and I briefly coquetted with the idea of using "scope" to denote an unescaping parameter:
>>
>> void print(scope int[] array) { ... }
>>
>> With this signature print guarantees it won't escape anything in array. Then synchronized functions can pass (addresses of) members to functions like print.
>>
>> It's not an easy feature to implement though.
> 
> What's difficult to implement specifically? Full escape analysis is difficult, but you can provide the same guarantees by being a bit conservative.

Nah, it's the details that are difficult:

* most functions do take scope parameters, so probably @escaping should be the explicit attribute

* interaction of scope with templates

* what annotation do you use for saying that "this" is scoped, e.g.

class A {
    void foo() scope { ... }
}

(and in fact most of the time this is scoped, so again @escaping...)

* figuring out how all of this aggravation will be received by users already displeased by the size of the language


Andrei
February 01, 2010
Le 2010-02-01 ? 15:30, Andrei Alexandrescu a ?crit :

> Michel Fortin wrote:
>>> The program would compile successfully with the following changes
>>> to nyukNyuk and sneaky:
>>> shared(double)* nyukNyuk; // or: shared(double*) nyukNyuk; void
>>> sneaky(ref shared(double) r) { nyukNyuk = &r; }
>> How can that be safe? Leaking the reference, even as 'shared', doesn't prevent someone from accessing the variable without acquiring the lock. Are you suggesting that mixing atomic operations and mutex synchronization for the same variable is okay? Because it's not.
> 
> You are making a good point. I thought of banning escapes altogether. After all, if someone has access to a shared data member and the synchronized method assumes it can temporarily optimize access to that member as if it's unshared, things can get messed up.
> 
> On the other hand, if you hand out a reference to a member, you're kind of setting yourself up for surprises. Far as the type system is concerned, it's doing the right thing.


Escaping a value, even as 'shared', breaks the synchronization guaranties. If the type system cares about synchronization, then it should not allow a variable to escape.


> One related thought: a long time ago, Walter, Bartosz and I briefly coquetted with the idea of using "scope" to denote an unescaping parameter:
> 
> void print(scope int[] array) { ... }
> 
> With this signature print guarantees it won't escape anything in array. Then synchronized functions can pass (addresses of) members to functions like print.

That's a simple solution. It's limited, since you can't implement something like swap(ref int[], ref int[]) and thus not sorting algorithm, but it's better than nothing.



-- 
Michel Fortin
michel.fortin at michelf.com
http://michelf.com/



February 01, 2010
I gave a quick reading about the fact that reading needs a lock
because otherwise it might not be updated almost forever, this is (as
far as I know) wrong.
Yes the view of one thread might be offset with respect with the one
of another, but not indefinitely so.
The main reason to put the sync is to ensure that one sees a
consistent view of the value.
If a value is always updated in an atomic way then the sync is not
needed.
Well with pointers that address other memory or  80bit reals it is
another story, but at least for doubles that should be ok, not even a
barrier is needed.
While you cannot mix atomic updates with locks, if all access are
atomic you can avoid locks in some places

and now for something completely different, blip.parallel.smp seems to pass all my tests even with the new numa aware scheduler, I still think that Tasks are a better abstraction to expose to programmers than threads, so if anybody wants to take that for a spin...

Fawzi
On 1-feb-10, at 13:07, Andrei Alexandrescu wrote:

> I've entered "synchronized" full speed, but haven't finished (e.g. synchronized constructors aren't yet discussed). New stuff starts around page 21. Please give this a very close read. Thanks.
>
> http://erdani.com/d/fragment.preview.pdf
>
> Walter, you may want to make sure we're on the same page with everything.
>
>
> Andrei
> _______________________________________________
> dmd-concurrency mailing list
> dmd-concurrency at puremagic.com
> http://lists.puremagic.com/mailman/listinfo/dmd-concurrency

February 01, 2010
On Mon, 01 Feb 2010 16:10:06 -0500, Andrei Alexandrescu <andrei at erdani.com> wrote:

> Robert Jacques wrote:
>> On Mon, 01 Feb 2010 15:30:47 -0500, Andrei Alexandrescu <andrei at erdani.com> wrote:
>>> Michel Fortin wrote:
>>>>> The program would compile successfully with the following changes
>>>>> to nyukNyuk and sneaky:
>>>>>  shared(double)* nyukNyuk; // or: shared(double*) nyukNyuk; void
>>>>> sneaky(ref shared(double) r) { nyukNyuk = &r; }
>>>>  How can that be safe? Leaking the reference, even as 'shared',
>>>> doesn't prevent someone from accessing the variable without acquiring
>>>> the lock. Are you suggesting that mixing atomic operations and mutex
>>>> synchronization for the same variable is okay? Because it's not.
>>>
>>> You are making a good point. I thought of banning escapes altogether. After all, if someone has access to a shared data member and the synchronized method assumes it can temporarily optimize access to that member as if it's unshared, things can get messed up.
>>>
>>> On the other hand, if you hand out a reference to a member, you're kind of setting yourself up for surprises. Far as the type system is concerned, it's doing the right thing.
>>>
>>> One related thought: a long time ago, Walter, Bartosz and I briefly coquetted with the idea of using "scope" to denote an unescaping parameter:
>>>
>>> void print(scope int[] array) { ... }
>>>
>>> With this signature print guarantees it won't escape anything in array. Then synchronized functions can pass (addresses of) members to functions like print.
>>>
>>> It's not an easy feature to implement though.
>>  What's difficult to implement specifically? Full escape analysis is
>> difficult, but you can provide the same guarantees by being a bit
>> conservative.
>
> Nah, it's the details that are difficult:
>
> * most functions do take scope parameters, so probably @escaping should be the explicit attribute
>
> * interaction of scope with templates
>
> * what annotation do you use for saying that "this" is scoped, e.g.
>
> class A {
>     void foo() scope { ... }
> }
>
> (and in fact most of the time this is scoped, so again @escaping...)
>
> * figuring out how all of this aggravation will be received by users already displeased by the size of the language
>

Ah, thanks.
February 01, 2010
Michel Fortin wrote:
> Le 2010-02-01 ? 15:30, Andrei Alexandrescu a ?crit :
> 
>> Michel Fortin wrote:
>>>> The program would compile successfully with the following
>>>> changes to nyukNyuk and sneaky: shared(double)* nyukNyuk; //
>>>> or: shared(double*) nyukNyuk; void sneaky(ref shared(double) r)
>>>> { nyukNyuk = &r; }
>>> How can that be safe? Leaking the reference, even as 'shared', doesn't prevent someone from accessing the variable without acquiring the lock. Are you suggesting that mixing atomic operations and mutex synchronization for the same variable is okay? Because it's not.
>> You are making a good point. I thought of banning escapes altogether. After all, if someone has access to a shared data member and the synchronized method assumes it can temporarily optimize access to that member as if it's unshared, things can get messed up.
>> 
>> On the other hand, if you hand out a reference to a member, you're kind of setting yourself up for surprises. Far as the type system is concerned, it's doing the right thing.
> Escaping a value, even as 'shared', breaks the synchronization guaranties. If the type system cares about synchronization, then it should not allow a variable to escape.

OK, then all escaping from synchronized methods is out. I'm 80% convinced myself. Does everybody agree?

>> One related thought: a long time ago, Walter, Bartosz and I briefly coquetted with the idea of using "scope" to denote an unescaping parameter:
>> 
>> void print(scope int[] array) { ... }
>> 
>> With this signature print guarantees it won't escape anything in array. Then synchronized functions can pass (addresses of) members to functions like print.
> 
> That's a simple solution. It's limited, since you can't implement something like swap(ref int[], ref int[]) and thus not sorting algorithm, but it's better than nothing.

I think swap could be typed as:

swap(scope ref int[], scope ref int[]);

The point of scope parameter typechecking is that swap does not save a reference surreptitiously; I think escaping to another scope parameter should be fine (if somewhat conservative).

In fact I've had this idea for a while now that "ref" should imply "I won't escape it". That would make ref strictly pass-down. Walter is mildly favorable to that idea. I think it's worth giving it a closer look now.


Andrei
February 01, 2010
On Mon, 01 Feb 2010 16:49:27 -0500, Andrei Alexandrescu <andrei at erdani.com> wrote:
> Michel Fortin wrote:
>> Le 2010-02-01 ? 15:30, Andrei Alexandrescu a ?crit :
>>
>>> Michel Fortin wrote:
>>>>> The program would compile successfully with the following
>>>>> changes to nyukNyuk and sneaky: shared(double)* nyukNyuk; //
>>>>> or: shared(double*) nyukNyuk; void sneaky(ref shared(double) r)
>>>>> { nyukNyuk = &r; }
>>>> How can that be safe? Leaking the reference, even as 'shared', doesn't prevent someone from accessing the variable without acquiring the lock. Are you suggesting that mixing atomic operations and mutex synchronization for the same variable is okay? Because it's not.
>>> You are making a good point. I thought of banning escapes
>>> altogether. After all, if someone has access to a shared data
>>> member and the synchronized method assumes it can temporarily
>>> optimize access to that member as if it's unshared, things can get
>>> messed up.
>>>  On the other hand, if you hand out a reference to a member, you're
>>> kind of setting yourself up for surprises. Far as the type system
>>> is concerned, it's doing the right thing.
>> Escaping a value, even as 'shared', breaks the synchronization guaranties. If the type system cares about synchronization, then it should not allow a variable to escape.
>
> OK, then all escaping from synchronized methods is out. I'm 80% convinced myself. Does everybody agree?
>
>>> One related thought: a long time ago, Walter, Bartosz and I briefly
>>> coquetted with the idea of using "scope" to denote an unescaping
>>> parameter:
>>>  void print(scope int[] array) { ... }
>>>  With this signature print guarantees it won't escape anything in
>>> array. Then synchronized functions can pass (addresses of) members
>>> to functions like print.
>>  That's a simple solution. It's limited, since you can't implement
>> something like swap(ref int[], ref int[]) and thus not sorting
>> algorithm, but it's better than nothing.
>
> I think swap could be typed as:
>
> swap(scope ref int[], scope ref int[]);
>
> The point of scope parameter typechecking is that swap does not save a reference surreptitiously; I think escaping to another scope parameter should be fine (if somewhat conservative).

Scope needs to prevent escaping to any scope, which most importantly includes other scope variables. i.e.:

shared int[] x;
int[] y;
swap(x,y);

Causes a unshared array to become shared.

Generally, you'd want something like scope constraints or scope tags to do something like swap. But this can be better down using templates and template constraints today. Also, when you think in terms of 'lent' swapping two borrowed items and returning different items to the original owners is completely illogical.

> In fact I've had this idea for a while now that "ref" should imply "I won't escape it". That would make ref strictly pass-down. Walter is mildly favorable to that idea. I think it's worth giving it a closer look now.

This sounds like an okay idea, but I haven't thought through all the implications yet.