www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Scope Locks: The "shared" middle ground

This post is related to the "Sharing in D" thread.  It relates first to 
the idea that shared variables will be very limited in what you can do 
with them, and second to the thought that there should be some standard 
"middle ground" between shared and unshared data, like "const" is to 
"invariant" and mutable.  I think that the middle ground is "scope", 
along with language-supported lockes.

I came up with the idea of Scope Locks a while back based on a comment 
that Walter made that "there is no way to enforce that people use locks 
properly."  The design I'm posting here isn't totally refined yet, but I 
thought that we ought to get it into the mix before the design of 
"shared" gets too far.

SCOPE LOCKS IN GENERAL

The concept of a Scope Lock is a lock which (transitively) protects a 
piece of data.  The data is actually contained *within* the lock, and 
cannot be accessed (even read) externally.

When you lock a scope lock, it calls a callback that you provide, 
passing a pointer to the data as a "scope" argument.  (Are scope 
arguments implemented yet?  I haven't tested it...)  Since this is a 
scope argument, you cannot save a copy of this pointer (or anything that 
it points to) after the function returns.

If you lock a Scope Lock in exclusive mode, then the scope object that 
you are passed is mutable, but if you lock it in shared mode, then the 
copy will be const.

The lock is automatically released when you return from the function.

APPLICABILITY TO SHARED

I propose that we add a new modified, "locked", along with the "shared" 
modifier.  "locked" variables are "shared" variables which you can make 
unshared by locking them.  This could happen using something like the 
"with" syntax:

BEGIN CODE
	locked MyStruct foo;
	void bar()
	{
		// it is illegal to use any fields of foo out here
		unlock-read(foo)
		{
			// in this block, foo is a non-shared const
			writefln("current state = ", foo.field1);
		}
		unlock-write(foo)
		{
			// in this block, foo is non-shared and mutable
			foo.field1++;
		}
	}
END CODE

We could then allow "unlock" to be applied to function arguments, as a 
way to unify "shared" and unshared code.  This would be syntax sugar for 
grabbing the lock, and then calling the function:

BEGIN CODE
	// note that this argument has to be 'scope' or else the locking
	// scheme doesn't work
	void baz(scope MyStruct *thing) {...}
	void fred()
	{
		baz(unlock-write(foo));
	}
END CODE



OPEN ISSUES & LIMITATIONS
- Scope locks don't solve deadlock issues
- Scope locks should probably allow recursive locks (no self-deadlock)
- "const locked" should probably collapse to "locked" (that is, "const" 
is not transitive through "locked") so that, in the case of nested 
locks, you can lock a member in exclusive mode even if the container is 
shared)
- With nested locks, there's no way to relase the outer lock while you 
still hold the inner (without some more language features)
- "scope" is difficult to use in some situations.  If you take two 
pointers to members of the same scope object, and you pass those 
pointers to another function, how do you express to the other that it's 
safe to store pointers to one in the other?



Thoughts?
Aug 07 2008