Last active
January 4, 2016 16:49
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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