A working way to improve the "shared" situation

Sönke Ludwig sludwig at outerproduct.org
Thu Nov 15 07:21:55 PST 2012


After working a bit more on it (accompanied by a bad flu with 40 °C fever, so hopefully it's not all
wrong in reality), I got a library approach that allows to use shared objects in a (statically
checked) safe and comfortable way. As a bonus, it also introduces an isolated/unique type that can
be safely moved between threads and converts safely to immutable (and mutable).

It would be really nice to get a discussion going to see if this or something similar should be
included in Phobos and which (if any) language extensions, that could help (or replace) such an
approach, are realistic to get implemented in the short term (e.g. Walter suggested
__unique(expression) to statically verify that an expression yields a value with no mutable aliasing
to the outside).

But first a rough description of the proposed system - there are three basic ingredients:

 - ScopedRef!T:

   wraps a type allowing only operations that are guaranteed to not leak any references in or out.
   This type is non-copyable but allows reference-like access to a value. In contrast to 'scope' it
   works recursively and also works on return values in addition to function parameters.

 - Isolated!T:

   Statically ensures that any contained aliasing is either immutable or is only reachable through
   the Isolated!T itself (*strong isolation*). This allows safe passing between threads and safe
   conversion to immutable. A less strict mode also allows shared aliasing (*weak isolation*).
   Implicit conversion to immutable is not possible for weakly isolated values, but they can still
   safely be moved between threads and accessed without locking or similar means. As such they
   provide a natural bridge between the shared and the thread local world. Isolated!T is
   non-copyable, but can be move()d between variables.

 - ScopedLock!T:

   Provides scoped access to shared objects. It will lock the object's mutex and provide access to
   its non-shared methods and fields. A convenience function lock() is used to construct a
   ScopedLock!T, which is also non-copyable. The type T must be weakly isolated, because otherwise
   it cannot be guaranteed that there are no shared references that are not also marked with
   'shared'.

The operations done on either of these three wrappers are forced to be (weakly) pure and may not
have parameters or return types that could leak references (neither /to/ nor /from/ the outside).

It solves a number of common usage patterns, not only removing the need for casts, but also
statically verifying the correctness of the code. The following example shows it in action. Apart
from the pure annotations ('pure:' would help), nothing else is necessary.

---
import stdx.typecons;

class Item {
	private double m_value;
	this(double value) pure { m_value = value; }
	@property double value() const pure { return m_value; }
}

class Manager {
	private {
		string m_name;
		Isolated!(Item) m_ownedItem;
		Isolated!(shared(Item)[]) m_items;
	}

	this(string name) pure
	{
		m_name = name;
		auto itm = makeIsolated!Item(3.5);
		// _move_ itm to m_ownedItem
		m_ownedItem = itm;
		// itm is now empty
	}

	void addItem(shared(Item) item) pure { m_items ~= item; }

	double getTotalValue()
	const pure {
		double sum = 0;

		// lock() is required to access shared objects
		foreach( ref itm; m_items ) sum += itm.lock().value;

		// owned objects can be accessed without locking
		sum += m_ownedItem.value;

		return sum;
	}
}

void main()
{
	import std.stdio;

	auto man = new shared(Manager)("My manager");
	{ // doing multiple method calls during a single lock is no problem
		auto l = man.lock();
		l.addItem(new shared(Item)(1.5));
		l.addItem(new shared(Item)(0.5));
	}

	writefln("Total value: %s", man.lock().getTotalValue());
}
---

This all works quite well and is able to come close to what the C# system that I linked some days
ago (*) is able to do. Notably, ScopedRef!T allows to directly modify isolated objects without
having to implement the recovery rules that the paper mentions. It cannot capture all those cases,
but is good enough in most cases. Note that there are a lot of small details that I left out, but
just to hopefully better get the general idea across.

There are still some open points where I think small language changes are needed to make this
bullet-proof:

 - It would be nice to be able to disallow 'auto var = somethingThatReturnsScopedRef();'. Copying
   can nicely be disabled using '@disable this(this)', but initializing a variable can't. This
   opens up a possible whole:

   ---
   Isolated!MyType myvalue = ...;
   ScopedRef!int fieldref = myvalue.someIntField;
   send(someThread, myvalue); // isolated values can be safely moved to different threads
   fieldref++; // but wait, we can still screw it up!
   ---

 - opApply() seemingly cannot be used in a pure context in a meaningful way. Making it pure means
   that also the delegate that it takes must be pure. But a pure foreach body basically means that
   the whole loop has no effect (okay, it could still modify the iterated elements). The workaround
   I did was to let the pure opApply take an impure delegate that is casted to pure upon calling it.

 - Locking is technically an impure operation, but from a high level view it has no visible effect.
   To make the whole system really usable, it is required that lock() can be used from a pure
   context. As a workaround I declared _d_monitorenter/exit as pure (these are used for locking the
   object's mutex).


github project containing the D implementation:

https://github.com/s-ludwig/d-isolated-test

A little documentation:

http://vibed.org/temp/d-isolated-test/stdx/typecons/lock.html
http://vibed.org/temp/d-isolated-test/stdx/typecons/makeIsolated.html
http://vibed.org/temp/d-isolated-test/stdx/typecons/makeIsolatedArray.html


(*) Microsoft paper about the C# type system extension, from which some of the ideas originate:

http://research.microsoft.com/pubs/170528/msr-tr-2012-79.pdf


More information about the Digitalmars-d mailing list