Skip to content

Instantly share code, notes, and snippets.

@kumpera
Created May 15, 2018 15:40
Show Gist options
  • Save kumpera/b11c28e4bcc197bbe6783d215d04a2ac to your computer and use it in GitHub Desktop.
Save kumpera/b11c28e4bcc197bbe6783d215d04a2ac to your computer and use it in GitHub Desktop.
Safe Object Publication

Safe Object Publication

The notion of SOP (Safe Object Publication) is that objects are made globally visible as whole after constructed or initialized to a given state. This assumption makes usage of immutable objects a lot simpler as you never need any read-side coordination.

For example:


class Foo {
	public readonly int b;
	public Foo (int b) {
		this.b = b;
	}
	static Foo instance;

	static void SetCurrentInstanceTo10 () {
		instance = new Foo (10);
	}
	static int CurrentInstance () { 
		var f = Instance;
		return f.b;
	}
}

In the above example, under SOP callers of Foo.Instance expect that they will always read 10 from b.

SOP under CoreClr's memory model

Under the CLR's memory model, there's a release fence on each store of a byref.

Write Side

On method SetCurrentInstanceTo10, the sequence of relevant memory accesses we'll see is:

//$0 is the temporary holding the freshly allocated instance of `Foo`
$0.b = 10
release fence
instance = $0

The release fence ensures that all stores previously to it are globally visible before any access happening after the fence.

Reader side

On method CurrentInstance, this is the sequence of relevant memory accesses we'll see:

$0 = instance
implicit data-dependent fence
$1 = $0.b

The implicit fence ensures that all reads that depend on $0 will happen afterwards.

Possible read outcomes

Assuming CurrentInstance see the new Foo object from SetCurrentInstanceTo10, the only allowed read outcome of CurrentInstance is 10.

SOP under ECMA's (Mono) memory model

Under mono's model, there's no SOP with the original code as there's no release fence before the store to instance.

Possible read outcomes

It's possible for CurrentInstance to read either 0 or 10 from instance as the stores will happen out of order.

Fixing SOP

The fix for this case is to mark the instance field as volatile, which will insert acquire-release fences around access to it and ensure SOP ordering of memory accesses.

How does this happen in Roslyn?

The code in question is: https://github.com/dotnet/roslyn/blob/master/src/Dependencies/PooledObjects/ObjectPool%601.cs#L194

Given this two methods (with debug code removed):

private T _firstItem;

internal void Free(T obj)
{
	if (_firstItem == null)
	{
		_firstItem = obj;
	}
}

internal T Allocate()
{
	T inst = _firstItem;
	if (inst == null || inst != Interlocked.CompareExchange(ref _firstItem, null, inst))
	{
		inst = AllocateSlow();
	}

	return inst;
}

As you can see, under mono, a call to Free with a newly allocated or initialized object can then be seen from Allocate with the init bits out of order.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment