Skip to content

Instantly share code, notes, and snippets.

@dadhi
Last active January 4, 2016 16:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dadhi/8650365 to your computer and use it in GitHub Desktop.
Save dadhi/8650365 to your computer and use it in GitHub Desktop.
Ref wrapper type with consistent lock-free Update. Idea comes from: http://en.wikipedia.org/wiki/Multiversion_concurrency_control and http://ru.wikipedia.org/wiki/Software_transactional_memory. A bit similar to Clojure Ref as described in http://java.ociweb.com/mark/stm/article.html#STMImplementations.
using System;
using System.Linq;
using System.Threading;
using NUnit.Framework;
namespace Playground
{
[TestFixture]
public class RefTests
{
[Test]
public void Consistently_updated_by_multiple_threads()
{
const int itemCount = 10;
var items = new int[itemCount];
for (var i = 0; i < itemCount; i++)
items[i] = i;
var itemsRef = Ref.Of(items.ToArray());
const int threadCount = 10;
var latch = new CountdownLatch(threadCount);
for (var i = threadCount - 1; i >= 0; i--)
{
var delay = i * 10;
var thread = new Thread(() =>
{
Thread.Sleep(delay);
itemsRef.Swap(xs => xs.Reverse().ToArray());
latch.Signal();
}) { IsBackground = true };
thread.Start();
}
latch.Wait();
CollectionAssert.AreEqual(items, itemsRef.Value);
}
private sealed class CountdownLatch
{
public CountdownLatch(int count)
{
_remain = count;
_event = new ManualResetEvent(false);
}
public void Signal()
{
// The last thread to signal also sets the event.
if (Interlocked.Decrement(ref _remain) == 0)
_event.Set();
}
public void Wait()
{
_event.WaitOne();
}
private int _remain;
private readonly EventWaitHandle _event;
}
}
/// <summary>Provides optimistic-concurrency consistent <see cref="Swap{T}"/> operation.</summary>
public static class Ref
{
/// <summary>Factory for <see cref="Ref{T}"/> with type of value inference.</summary>
/// <typeparam name="T">Type of value to wrap.</typeparam>
/// <param name="value">Initial value to wrap.</param>
/// <returns>New ref.</returns>
public static Ref<T> Of<T>(T value) where T : class
{
return new Ref<T>(value);
}
/// <summary>Creates new ref to the value of original ref.</summary> <typeparam name="T">Ref value type.</typeparam>
/// <param name="original">Original ref.</param> <returns>New ref to original value.</returns>
public static Ref<T> NewRef<T>(this Ref<T> original) where T : class
{
return Of(original.Value);
}
/// <summary>First, it evaluates new value using <paramref name="getNewValue"/> function.
/// Second, it checks that original value is not changed.
/// If it is changed it will retry first step, otherwise it assigns new value and returns original (the one used for <paramref name="getNewValue"/>).</summary>
/// <typeparam name="T">Type of value to swap.</typeparam>
/// <param name="value">Reference to change to new value</param>
/// <param name="getNewValue">Delegate to get value from old one.</param>
/// <returns>Old/original value. By analogy with <see cref="Interlocked.Exchange(ref int,int)"/>.</returns>
/// <remarks>Important: <paramref name="getNewValue"/> May be called multiple times to retry update with value concurrently changed by other code.</remarks>
public static T Swap<T>(ref T value, Func<T, T> getNewValue) where T : class
{
var retryCount = 0;
while (true)
{
var oldValue = value;
var newValue = getNewValue(oldValue);
if (Interlocked.CompareExchange(ref value, newValue, oldValue) == oldValue)
return oldValue;
if (++retryCount > RETRY_COUNT_UNTIL_THROW)
throw new InvalidOperationException(_errorRetryCountExceeded);
}
}
private const int RETRY_COUNT_UNTIL_THROW = 50;
private static readonly string _errorRetryCountExceeded =
"Ref retried to Update for " + RETRY_COUNT_UNTIL_THROW + " times But there is always someone else intervened.";
}
/// <summary>Wrapper that provides optimistic-concurrency Swap operation implemented using <see cref="Ref.Swap{T}"/>.</summary>
/// <typeparam name="T">Type of object to wrap.</typeparam>
public sealed class Ref<T> where T : class
{
/// <summary>Gets the wrapped value.</summary>
public T Value { get { return _value; } }
/// <summary>Creates ref to object, optionally with initial value provided.</summary>
/// <param name="initialValue">(optional) Initial value.</param>
public Ref(T initialValue = default(T))
{
_value = initialValue;
}
/// <summary>Exchanges currently hold object with <paramref name="getNewValue"/> - see <see cref="Ref.Swap{T}"/> for details.</summary>
/// <param name="getNewValue">Delegate to produce new object value from current one passed as parameter.</param>
/// <returns>Returns old object value the same way as <see cref="Interlocked.Exchange(ref int,int)"/></returns>
/// <remarks>Important: <paramref name="getNewValue"/> May be called multiple times to retry update with value concurrently changed by other code.</remarks>
public T Swap(Func<T, T> getNewValue)
{
return Ref.Swap(ref _value, getNewValue);
}
/// <summary>Compares curret Refed value with <paramref name="currentValue"/> and if equal replaces current with <paramref name="newValue"/></summary>
/// <param name="currentValue"></param> <param name="newValue"></param>
/// <returns>True if current value was replaced with new value, and false if current value is outdated (already changed by other party).</returns>
public bool TrySwapIfStillCurrent(T currentValue, T newValue)
{
return Interlocked.CompareExchange(ref _value, newValue, currentValue) == currentValue;
}
private T _value;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment