├── Engine ├── Atomic.cs ├── AtomicInt.cs ├── AtomicLong.cs ├── CLR │ ├── CLRAtomic.cs │ ├── CLRAtomicInt.cs │ ├── CLRAtomicLong.cs │ ├── Live │ │ ├── LiveAtomic.cs │ │ ├── LiveAtomicInt.cs │ │ ├── LiveAtomicLong.cs │ │ └── RealMonitor.cs │ ├── MonitorInstance.cs │ ├── RInterlocked.cs │ ├── RMonitor.cs │ ├── RUnordered.cs │ └── RVolatile.cs ├── Engine.csproj ├── EngineException.cs ├── EventLog.cs ├── ExecutionEvent.cs ├── Fence.cs ├── IRelaTest.cs ├── LiveRelaEngine.cs ├── MemoryModel │ ├── AccessData.cs │ ├── AccessDataPool.cs │ ├── AccessHistory.cs │ ├── ILookback.cs │ ├── InternalAtomic.cs │ ├── InternalRaceChecked.cs │ ├── ShadowThread.cs │ └── VectorClock.cs ├── MemoryOrder.cs ├── RaceChecked.cs ├── RelaEngine.cs ├── Scheduling │ ├── Exhaustive │ │ ├── Choice.cs │ │ ├── PriorityRelation.cs │ │ ├── SchedulingStrategy.cs │ │ └── ThreadSet.cs │ ├── ExhaustiveScheduler.cs │ ├── IScheduler.cs │ └── NaiveRandomScheduler.cs ├── TestEnvironment.cs ├── TestFailedException.cs ├── TestRunner.cs └── TestThreads.cs ├── Examples ├── AsymmetricPetersen.cs ├── BoundedSPSCQueue.cs ├── CLR │ ├── AsymmetricLock.cs │ ├── COWList.cs │ ├── DCLReadIndicator.cs │ ├── IPIReadWriteLock.cs │ ├── IncorrectLeftRight.cs │ ├── LeftRight.cs │ ├── SafeAsymmetricLock.cs │ ├── SingleCounterReadIndicator.cs │ └── StarvationLeftRight.cs ├── Deadlock.cs ├── EntryPoint │ ├── Options.cs │ └── RunExamples.cs ├── Examples.csproj ├── IRelaExample.cs ├── LiveLock.cs ├── LostWakeUp.cs ├── MichaelScottQueue.cs ├── MinimalIPI.cs ├── Petersen.cs ├── RelaxedModificationOrder.cs ├── SimpleAcquireRelease.cs ├── SimpleConfig.cs ├── SimpleTransitive.cs ├── StoreLoad.cs ├── TotalOrder.cs ├── TransitiveLastSeen.cs └── TreiberStack.cs ├── LICENSE ├── README.md ├── RelaSharp.sln └── TODO.txt /Engine/Atomic.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using RelaSharp.MemoryModel; 3 | 4 | namespace RelaSharp 5 | { 6 | interface IAtomic 7 | { 8 | void Store(T data, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0); 9 | T Load(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0); 10 | bool CompareExchange(T newData, T comparand, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0); 11 | T Exchange(T newData, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0); 12 | } 13 | 14 | 15 | public class Atomic : IAtomic 16 | { 17 | private static TestEnvironment TE = TestEnvironment.TE; 18 | internal InternalAtomic _memoryOrdered; // TODO: Change this to "private protected" once C# 7.2 useable with .NET Core 2.0. 19 | 20 | private void MaybeInit() 21 | { 22 | if(RelaEngine.Mode != EngineMode.Test) 23 | { 24 | throw new EngineException($"{nameof(Atomic)} must only be used when RelaEngine.Mode is {EngineMode.Test}, but it is {RelaEngine.Mode} (did you forget to assign it?)."); 25 | } 26 | if(_memoryOrdered == null) 27 | { 28 | _memoryOrdered = new InternalAtomic(TE.HistoryLength, TE.NumThreads, TE.Lookback); 29 | _memoryOrdered.Store(default(T), MemoryOrder.Relaxed, TE.RunningThread); 30 | } 31 | } 32 | 33 | protected void Preamble() 34 | { 35 | MaybeInit(); 36 | TE.MaybeSwitch(); 37 | var runningThread = TE.RunningThread; 38 | runningThread.IncrementClock(); 39 | return; 40 | } 41 | 42 | public void Store(T data, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 43 | { 44 | Preamble(); 45 | _memoryOrdered.Store(data, mo, TE.RunningThread); 46 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Store ({mo}) --> {Str(data)}"); 47 | } 48 | 49 | public T Load(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 50 | { 51 | Preamble(); 52 | var result = _memoryOrdered.Load(mo, TE.RunningThread); 53 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Load ({mo}) <-- {Str(result)}"); 54 | return result; 55 | } 56 | 57 | public bool CompareExchange(T newData, T comparand, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 58 | { 59 | Preamble(); 60 | T loadedData; 61 | var success = _memoryOrdered.CompareExchange(newData, comparand, mo, TE.RunningThread, out loadedData); 62 | var description = success ? $"Success: {Str(comparand)} == {Str(loadedData)}" : $"Failed: {Str(comparand)} != {Str(loadedData)}"; 63 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"CompareExchange ({mo}): --> {Str(newData)} ({description})"); 64 | return success; 65 | } 66 | 67 | public T Exchange(T newData, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 68 | { 69 | Preamble(); 70 | var oldData = _memoryOrdered.Exchange(newData, mo, TE.RunningThread); 71 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Exchange ({mo}): --> {Str(newData)} ({Str(oldData)})"); 72 | return oldData; 73 | } 74 | 75 | protected static string Str(T y) 76 | { 77 | return y == null ? "null" : y.ToString(); 78 | } 79 | 80 | public override string ToString() 81 | { 82 | return _memoryOrdered.CurrentValue.ToString(); 83 | } 84 | } 85 | } 86 | 87 | -------------------------------------------------------------------------------- /Engine/AtomicInt.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace RelaSharp 4 | { 5 | interface IAtomicInt : IAtomic 6 | { 7 | int Add(int x, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0); 8 | int Increment(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0); 9 | int Decrement(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0); 10 | } 11 | 12 | public class AtomicInt : Atomic, IAtomicInt 13 | { 14 | private static TestEnvironment TE = TestEnvironment.TE; 15 | 16 | public int Add(int x, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 17 | { 18 | Preamble(); 19 | var newValue = _memoryOrdered.CurrentValue + x; 20 | var oldValue = _memoryOrdered.Exchange(newValue, mo, TE.RunningThread); 21 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Add ({mo}): --> {Str(newValue)} ({Str(oldValue)})"); 22 | return newValue; 23 | } 24 | 25 | public int Increment(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 26 | { 27 | Preamble(); 28 | var newValue = _memoryOrdered.CurrentValue + 1; 29 | var oldValue = _memoryOrdered.Exchange(newValue, mo, TE.RunningThread); 30 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Increment ({mo}): --> {Str(newValue)} ({Str(oldValue)})"); 31 | return newValue; 32 | } 33 | 34 | public int Decrement(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 35 | { 36 | Preamble(); 37 | var newValue = _memoryOrdered.CurrentValue - 1; 38 | var oldValue = _memoryOrdered.Exchange(newValue, mo, TE.RunningThread); 39 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Decrement ({mo}): --> {Str(newValue)} ({Str(oldValue)})"); 40 | return newValue; 41 | } 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Engine/AtomicLong.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace RelaSharp 4 | { 5 | interface IAtomicLong : IAtomic 6 | { 7 | long Add(long x, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0); 8 | long Increment(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0); 9 | long Decrement(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0); 10 | } 11 | 12 | public class AtomicLong : Atomic, IAtomicLong 13 | { 14 | private static TestEnvironment TE = TestEnvironment.TE; 15 | 16 | public long Add(long x, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 17 | { 18 | Preamble(); 19 | var newValue = _memoryOrdered.CurrentValue + x; 20 | var oldValue = _memoryOrdered.Exchange(newValue, mo, TE.RunningThread); 21 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Add ({mo}): --> {Str(newValue)} ({Str(oldValue)})"); 22 | return newValue; 23 | } 24 | 25 | public long Increment(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 26 | { 27 | Preamble(); 28 | var newValue = _memoryOrdered.CurrentValue + 1; 29 | var oldValue = _memoryOrdered.Exchange(newValue, mo, TE.RunningThread); 30 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Increment ({mo}): --> {Str(newValue)} ({Str(oldValue)})"); 31 | return newValue; 32 | } 33 | 34 | public long Decrement(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 35 | { 36 | Preamble(); 37 | var newValue = _memoryOrdered.CurrentValue - 1; 38 | var oldValue = _memoryOrdered.Exchange(newValue, mo, TE.RunningThread); 39 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Decrement ({mo}): --> {Str(newValue)} ({Str(oldValue)})"); 40 | return newValue; 41 | } 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Engine/CLR/CLRAtomic.cs: -------------------------------------------------------------------------------- 1 | using RelaSharp.CLR.Live; 2 | 3 | namespace RelaSharp.CLR 4 | { 5 | public class CLRAtomic where T : class 6 | { 7 | private readonly IAtomic _atomic; 8 | 9 | private CLRAtomic() 10 | { 11 | switch(RelaEngine.Mode) 12 | { 13 | case EngineMode.Test: 14 | _atomic = new Atomic(); 15 | break; 16 | case EngineMode.Live: 17 | _atomic = new LiveAtomic(); 18 | break; 19 | default: 20 | throw new EngineException($"{nameof(CLRAtomic)} must only be used when RelaEngine.Mode is {EngineMode.Test} or {EngineMode.Live}, but it is {RelaEngine.Mode} (did you forget to assign it?)."); 21 | } 22 | } 23 | 24 | internal static IAtomic Get(ref CLRAtomic data) 25 | { 26 | if(data == null) 27 | { 28 | data = new CLRAtomic(); 29 | } 30 | return data._atomic; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Engine/CLR/CLRAtomicInt.cs: -------------------------------------------------------------------------------- 1 | namespace RelaSharp.CLR 2 | { 3 | public class CLRAtomicInt 4 | { 5 | private readonly IAtomicInt _atomic; 6 | private CLRAtomicInt() 7 | { 8 | switch(RelaEngine.Mode) 9 | { 10 | case EngineMode.Test: 11 | _atomic = new AtomicInt(); 12 | break; 13 | case EngineMode.Live: 14 | _atomic = new Live.LiveAtomicInt(); 15 | break; 16 | default: 17 | throw new EngineException($"{nameof(CLRAtomicInt)} must only be used when RelaEngine.Mode is {EngineMode.Test} or {EngineMode.Live}, but it is {RelaEngine.Mode} (did you forget to assign it?)."); 18 | } 19 | } 20 | 21 | internal static IAtomicInt Get(ref CLRAtomicInt data) 22 | { 23 | if(data == null) 24 | { 25 | data = new CLRAtomicInt(); 26 | } 27 | return data._atomic; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Engine/CLR/CLRAtomicLong.cs: -------------------------------------------------------------------------------- 1 | namespace RelaSharp.CLR 2 | { 3 | public class CLRAtomicLong 4 | { 5 | private readonly IAtomicLong _atomic; 6 | 7 | private CLRAtomicLong() 8 | { 9 | switch(RelaEngine.Mode) 10 | { 11 | case EngineMode.Test: 12 | _atomic = new AtomicLong(); 13 | break; 14 | case EngineMode.Live: 15 | _atomic = new Live.LiveAtomicLong(); 16 | break; 17 | default: 18 | throw new EngineException($"{nameof(CLRAtomicInt)} must only be used when RelaEngine.Mode is {EngineMode.Test} or {EngineMode.Live}, but it is {RelaEngine.Mode} (did you forget to assign it?)."); 19 | } 20 | } 21 | 22 | internal static IAtomicLong Get(ref CLRAtomicLong data) 23 | { 24 | if(data == null) 25 | { 26 | data = new CLRAtomicLong(); 27 | } 28 | return data._atomic; 29 | } 30 | 31 | } 32 | } -------------------------------------------------------------------------------- /Engine/CLR/Live/LiveAtomic.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using System.Threading; 3 | 4 | namespace RelaSharp.CLR.Live 5 | { 6 | class LiveAtomic : IAtomic where T : class 7 | { 8 | private T _data; 9 | 10 | public bool CompareExchange(T newData, T comparand, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 11 | => Interlocked.CompareExchange(ref _data, newData, comparand) == newData; 12 | 13 | public T Exchange(T newData, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 14 | => Interlocked.Exchange(ref _data, newData); 15 | 16 | public T Load(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 17 | { 18 | switch(mo) 19 | { 20 | case MemoryOrder.Acquire: 21 | return Volatile.Read(ref _data); 22 | case MemoryOrder.Relaxed: 23 | return _data; 24 | default: 25 | throw new EngineException($"LiveAtomicInt.Load should never be called with memory order {mo}."); 26 | } 27 | } 28 | 29 | public void Store(T data, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 30 | { 31 | switch (mo) 32 | { 33 | case MemoryOrder.Release: 34 | Volatile.Write(ref _data, data); 35 | break; 36 | case MemoryOrder.Relaxed: 37 | _data = data; 38 | break; 39 | default: 40 | throw new EngineException($"LiveAtomicInt.Store should never be called with memory order {mo}."); 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Engine/CLR/Live/LiveAtomicInt.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using System.Threading; 3 | 4 | namespace RelaSharp.CLR.Live 5 | { 6 | class LiveAtomicInt : IAtomicInt 7 | { 8 | private int _data; 9 | 10 | public int Add(int x, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 11 | => Interlocked.Add(ref _data, x); 12 | 13 | public bool CompareExchange(int newData, int comparand, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 14 | => Interlocked.CompareExchange(ref _data, newData, comparand) == newData; 15 | 16 | 17 | public int Decrement(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 18 | => Interlocked.Decrement(ref _data); 19 | 20 | public int Exchange(int newData, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 21 | => Interlocked.Exchange(ref _data, newData); 22 | 23 | public int Increment(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 24 | => Interlocked.Increment(ref _data); 25 | 26 | public int Load(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 27 | { 28 | switch(mo) 29 | { 30 | case MemoryOrder.Acquire: 31 | return Volatile.Read(ref _data); 32 | case MemoryOrder.Relaxed: 33 | return _data; 34 | default: 35 | throw new EngineException($"LiveAtomicInt.Load should never be called with memory order {mo}."); 36 | } 37 | } 38 | 39 | public void Store(int data, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 40 | { 41 | switch(mo) 42 | { 43 | case MemoryOrder.Release: 44 | Volatile.Write(ref _data, data); 45 | break; 46 | case MemoryOrder.Relaxed: 47 | _data = data; 48 | break; 49 | default: 50 | throw new EngineException($"LiveAtomicInt.Store should never be called with memory order {mo}."); 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Engine/CLR/Live/LiveAtomicLong.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using System.Threading; 3 | 4 | namespace RelaSharp.CLR.Live 5 | { 6 | class LiveAtomicLong : IAtomicLong 7 | { 8 | private long _data; 9 | 10 | public long Add(long x, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 11 | => Interlocked.Add(ref _data, x); 12 | 13 | public bool CompareExchange(long newData, long comparand, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 14 | => Interlocked.CompareExchange(ref _data, newData, comparand) == newData; 15 | 16 | 17 | public long Decrement(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 18 | => Interlocked.Decrement(ref _data); 19 | 20 | public long Exchange(long newData, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 21 | => Interlocked.Exchange(ref _data, newData); 22 | 23 | public long Increment(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 24 | => Interlocked.Increment(ref _data); 25 | 26 | public long Load(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 27 | { 28 | switch (mo) 29 | { 30 | case MemoryOrder.SequentiallyConsistent: 31 | return Interlocked.Read(ref _data); 32 | case MemoryOrder.Acquire: 33 | return Volatile.Read(ref _data); 34 | case MemoryOrder.Relaxed: 35 | return _data; 36 | default: 37 | throw new EngineException($"LiveAtomicInt.Load should never be called with memory order {mo}."); 38 | } 39 | } 40 | 41 | public void Store(long data, MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 42 | { 43 | switch(mo) 44 | { 45 | case MemoryOrder.Release: 46 | Volatile.Write(ref _data, data); 47 | break; 48 | case MemoryOrder.Relaxed: 49 | _data = data; 50 | break; 51 | default: 52 | throw new EngineException($"LiveAtomicInt.Store should never be called with memory order {mo}."); 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Engine/CLR/Live/RealMonitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace RelaSharp.CLR.Live 5 | { 6 | class RealMonitor : IMonitor 7 | { 8 | public static readonly RealMonitor TheInstance = new RealMonitor(); 9 | public void Enter(object lockObject, string memberName, string sourceFilePath, int sourceLineNumber) 10 | { 11 | Monitor.Enter(lockObject); 12 | } 13 | 14 | public void Enter(object lockObject, ref bool lockTaken) 15 | { 16 | Monitor.Enter(lockObject, ref lockTaken); 17 | } 18 | 19 | public void Exit(object lockObject, string memberName, string sourceFilePath, int sourceLineNumber) 20 | { 21 | Monitor.Exit(lockObject); 22 | } 23 | 24 | public void Pulse(object lockObject, string memberName, string sourceFilePath, int sourceLineNumber) 25 | { 26 | Monitor.Pulse(lockObject); 27 | } 28 | 29 | public void PulseAll(object lockObject, string memberName, string sourceFilePath, int sourceLineNumber) 30 | { 31 | Monitor.PulseAll(lockObject); 32 | } 33 | 34 | public bool Wait(object lockObject, string memberName, string sourceFilePath, int sourceLineNumber) 35 | { 36 | return Monitor.Wait(lockObject); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Engine/CLR/MonitorInstance.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using RelaSharp.MemoryModel; 4 | 5 | namespace RelaSharp.CLR 6 | { 7 | interface IMonitor 8 | { 9 | void Enter(object lockObject, string memberName, string sourceFilePath, int sourceLineNumber); 10 | void Enter(object lockObject, ref bool lockTaken); 11 | void Exit(object lockObject, string memberName, string sourceFilePath, int sourceLineNumber); 12 | void Pulse(object lockObject, string memberName, string sourceFilePath, int sourceLineNumber); 13 | void PulseAll(object lockObject, string memberName, string sourceFilePath, int sourceLineNumber); 14 | bool Wait(object lockObject, string memberName, string sourceFilePath, int sourceLineNumber); 15 | } 16 | 17 | class MonitorInstance : IMonitor 18 | { 19 | private static TestEnvironment TE = TestEnvironment.TE; 20 | 21 | private VectorClock _lockClock; 22 | 23 | private Queue _waiting; 24 | private Queue _ready; 25 | 26 | private ShadowThread _heldBy; 27 | private int _timesEntered; 28 | 29 | public MonitorInstance() 30 | { 31 | _waiting = new Queue(); 32 | _ready = new Queue(); 33 | _lockClock = new VectorClock(TE.NumThreads); 34 | } 35 | 36 | private bool IsHeld => _heldBy != null; 37 | 38 | private int HoldingThreadId => IsHeld ? _heldBy.Id : -1; 39 | 40 | public void Enter(object lockObject, string memberName, string sourceFilePath, int sourceLineNumber) 41 | { 42 | var runningThread = Preamble(); 43 | if (IsHeld && _heldBy != runningThread) 44 | { 45 | while (IsHeld) 46 | { 47 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Monitor.Enter: (waiting {_timesEntered})."); 48 | TE.ThreadWaiting(HoldingThreadId, lockObject); 49 | } 50 | TE.ThreadFinishedWaiting(); 51 | } 52 | AcquireLock(runningThread, memberName, sourceFilePath, sourceLineNumber); 53 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Monitor.Enter: Lock-acquired ({_timesEntered})."); 54 | TE.MaybeSwitch(); 55 | } 56 | 57 | public void Enter(object lockObject, ref bool lockTaken) 58 | { 59 | if(lockTaken) 60 | { 61 | throw new ArgumentException($"{nameof(lockTaken)} must be passed as false"); 62 | } 63 | } 64 | 65 | public void Exit(object lockObject, string memberName, string sourceFilePath, int sourceLineNumber) 66 | { 67 | var runningThread = Preamble(); 68 | if(_heldBy != runningThread) 69 | { 70 | FailLockNotHeld(memberName, sourceFilePath, sourceLineNumber, runningThread.Id, "Exit"); 71 | return; 72 | } 73 | ReleaseLock(lockObject, memberName, sourceFilePath, sourceLineNumber); 74 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Monitor.Exit: Lock-released ({_timesEntered})."); 75 | TE.MaybeSwitch(); 76 | } 77 | 78 | public void Pulse(object lockObject, string memberName, string sourceFilePath, int sourceLineNumber) 79 | { 80 | var runningThread = Preamble(); 81 | if(_heldBy != runningThread) 82 | { 83 | FailLockNotHeld(memberName, sourceFilePath, sourceLineNumber, runningThread.Id, "Pulse"); 84 | return; 85 | } 86 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Monitor.Pulse"); 87 | if(_waiting.Count > 0) 88 | { 89 | _ready.Enqueue(_waiting.Dequeue()); 90 | } 91 | } 92 | 93 | public void PulseAll(object lockObject, string memberName, string sourceFilePath, int sourceLineNumber) 94 | { 95 | var runningThread = Preamble(); 96 | if(_heldBy != runningThread) 97 | { 98 | FailLockNotHeld(memberName, sourceFilePath, sourceLineNumber, runningThread.Id, "PulseAll"); 99 | return; 100 | } 101 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Monitor.PulseAll"); 102 | while(_waiting.Count > 0) 103 | { 104 | _ready.Enqueue(_waiting.Dequeue()); 105 | } 106 | } 107 | 108 | public bool Wait(object lockObject, string memberName, string sourceFilePath, int sourceLineNumber) 109 | { 110 | var runningThread = Preamble(); 111 | if(_heldBy != runningThread) 112 | { 113 | FailLockNotHeld(memberName, sourceFilePath, sourceLineNumber, runningThread.Id, "Pulse"); 114 | return false; 115 | } 116 | _waiting.Enqueue(runningThread.Id); 117 | 118 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Monitor.Wait (waiting)."); 119 | ReleaseLock(lockObject, memberName, sourceFilePath, sourceLineNumber); 120 | while(_ready.Count == 0 || _ready.Peek() != runningThread.Id) 121 | { 122 | TE.ThreadWaiting(HoldingThreadId, lockObject); 123 | } 124 | _ready.Dequeue(); 125 | TE.ThreadFinishedWaiting(); 126 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Monitor.Wait (woken)."); 127 | while (IsHeld) 128 | { 129 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Monitor.Wait: (woken-waiting {_timesEntered})."); 130 | TE.ThreadWaiting(HoldingThreadId, lockObject); 131 | } 132 | TE.ThreadFinishedWaiting(); 133 | AcquireLock(runningThread, memberName, sourceFilePath, sourceLineNumber); 134 | return true; 135 | } 136 | 137 | 138 | private void AcquireLock(ShadowThread runningThread, string memberName, string sourceFilePath, int sourceLineNumber) 139 | { 140 | // TODO: inc clock? 141 | runningThread.ReleasesAcquired.Join(_lockClock); 142 | _heldBy = runningThread; 143 | _timesEntered++; 144 | } 145 | 146 | private void ReleaseLock(object lockObject, string memberName, string sourceFilePath, int sourceLineNumber) 147 | { 148 | // TODO: inc clock? 149 | _lockClock.Join(_heldBy.ReleasesAcquired); 150 | _timesEntered--; 151 | if(_timesEntered == 0) 152 | { 153 | _heldBy = null; 154 | } 155 | TE.LockReleased(lockObject); 156 | } 157 | 158 | private ShadowThread Preamble() 159 | { 160 | TE.MaybeSwitch(); 161 | var runningThread = TE.RunningThread; 162 | runningThread.IncrementClock(); 163 | return runningThread; 164 | } 165 | 166 | private void FailLockNotHeld(string memberName, string sourceFilePath, int sourceLineNumber, int runningThreadId, string operationAttempted) 167 | { 168 | var heldByDesc = _heldBy == null ? "Nobody" : $"{_heldBy.Id}"; 169 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Monitor.{operationAttempted} (failure!)."); 170 | TE.FailTest($"Attempt to Monitor.{operationAttempted} on thread {runningThreadId}, but lock is held by {heldByDesc}."); 171 | } 172 | } 173 | } -------------------------------------------------------------------------------- /Engine/CLR/RInterlocked.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace RelaSharp.CLR 4 | { 5 | public class RInterlocked // TODO: Check Joe Duffy -- do these imply a SeqCstFence ? 6 | { 7 | public static bool CompareExchange(ref CLRAtomic data, T newData, T comparand, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) where T : class 8 | { 9 | var atomic = CLRAtomic.Get(ref data); 10 | return atomic.CompareExchange(newData, comparand, MemoryOrder.SequentiallyConsistent); 11 | } 12 | 13 | public static void Exchange(ref CLRAtomic data, T newData, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) where T : class 14 | { 15 | var atomic = CLRAtomic.Get(ref data); 16 | atomic.Exchange(newData, MemoryOrder.SequentiallyConsistent, memberName, sourceFilePath, sourceLineNumber); 17 | } 18 | public static int Increment(ref CLRAtomicInt data, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 19 | { 20 | var atomic = CLRAtomicInt.Get(ref data); 21 | return atomic.Increment(MemoryOrder.SequentiallyConsistent, memberName, sourceFilePath, sourceLineNumber); 22 | } 23 | 24 | public static int Decrement(ref CLRAtomicInt data, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 25 | { 26 | var atomic = CLRAtomicInt.Get(ref data); 27 | return atomic.Decrement(MemoryOrder.SequentiallyConsistent, memberName, sourceFilePath, sourceLineNumber); 28 | } 29 | 30 | public static void Exchange(ref CLRAtomicInt data, int newData, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 31 | { 32 | var atomic = CLRAtomicInt.Get(ref data); 33 | atomic.Exchange(newData, MemoryOrder.SequentiallyConsistent, memberName, sourceFilePath, sourceLineNumber); 34 | } 35 | 36 | public static bool CompareExchange(ref CLRAtomicInt data, int newData, int comparand, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 37 | { 38 | var atomic = CLRAtomicInt.Get(ref data); 39 | return atomic.CompareExchange(newData, comparand, MemoryOrder.SequentiallyConsistent, memberName, sourceFilePath, sourceLineNumber); 40 | } 41 | 42 | public static long Increment(ref CLRAtomicLong data, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 43 | { 44 | var atomic = CLRAtomicLong.Get(ref data); 45 | return atomic.Increment(MemoryOrder.SequentiallyConsistent, memberName, sourceFilePath, sourceLineNumber); 46 | } 47 | 48 | public static long Decrement(ref CLRAtomicLong data, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 49 | { 50 | var atomic = CLRAtomicLong.Get(ref data); 51 | return atomic.Decrement(MemoryOrder.SequentiallyConsistent, memberName, sourceFilePath, sourceLineNumber); 52 | } 53 | public static long Read(ref CLRAtomicLong data, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 54 | { 55 | var atomic = CLRAtomicLong.Get(ref data); 56 | return atomic.Load(MemoryOrder.SequentiallyConsistent, memberName, sourceFilePath, sourceLineNumber); 57 | } 58 | public static void Exchange(ref CLRAtomicLong data, long newData, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 59 | { 60 | var atomic = CLRAtomicLong.Get(ref data); 61 | atomic.Exchange(newData, MemoryOrder.SequentiallyConsistent, memberName, sourceFilePath, sourceLineNumber); 62 | } 63 | public static bool CompareExchange(ref CLRAtomicLong data, long newData, long comparand, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 64 | { 65 | var atomic = CLRAtomicLong.Get(ref data); 66 | return atomic.CompareExchange(newData, comparand, MemoryOrder.SequentiallyConsistent, memberName, sourceFilePath, sourceLineNumber); 67 | } 68 | 69 | public static void MemoryBarrier([CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 70 | { 71 | Fence.Insert(MemoryOrder.SequentiallyConsistent, memberName, sourceFilePath, sourceLineNumber); 72 | } 73 | 74 | public static void MemoryBarrierProcessWide([CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 75 | { 76 | Fence.InsertProcessWide(memberName, sourceFilePath, sourceLineNumber); 77 | } 78 | 79 | } 80 | } -------------------------------------------------------------------------------- /Engine/CLR/RMonitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace RelaSharp.CLR 6 | { 7 | public static class RMonitor 8 | { 9 | private static Dictionary _lockToMonitor = new Dictionary(); 10 | 11 | private static IMonitor GetMonitorInstance(Object lockObject) 12 | { 13 | if(RelaEngine.Mode == EngineMode.Test) 14 | { 15 | MonitorInstance instance; 16 | if (!_lockToMonitor.TryGetValue(lockObject, out instance)) 17 | { 18 | instance = new MonitorInstance(); 19 | _lockToMonitor.Add(lockObject, instance); 20 | } 21 | return instance; 22 | } 23 | else if(RelaEngine.Mode == EngineMode.Live) 24 | { 25 | return Live.RealMonitor.TheInstance; 26 | } 27 | throw new EngineException($"{nameof(RMonitor)} must only be used when RelaEngine.Mode is {EngineMode.Test} or {EngineMode.Live}, but it is {RelaEngine.Mode} (did you forget to assign it?)."); 28 | } 29 | 30 | public static void Enter(Object lockObject, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 31 | { 32 | var instance = GetMonitorInstance(lockObject); 33 | instance.Enter(lockObject, memberName, sourceFilePath, sourceLineNumber); 34 | } 35 | 36 | public static void Enter(Object lockObject, ref bool lockTaken) 37 | { 38 | var instance = GetMonitorInstance(lockObject); 39 | instance.Enter(lockObject, ref lockTaken); 40 | } 41 | 42 | public static void Exit(Object lockObject, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 43 | { 44 | var instance = GetMonitorInstance(lockObject); 45 | instance.Exit(lockObject, memberName, sourceFilePath, sourceLineNumber); 46 | } 47 | 48 | public static bool IsEntered(Object lockObject) 49 | { 50 | throw new NotImplementedException(); 51 | } 52 | 53 | public static void Pulse(Object lockObject, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 54 | { 55 | var instance = GetMonitorInstance(lockObject); 56 | instance.Pulse(lockObject, memberName, sourceFilePath, sourceLineNumber); 57 | } 58 | 59 | public static void PulseAll(Object lockObject, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 60 | { 61 | var instance = GetMonitorInstance(lockObject); 62 | instance.PulseAll(lockObject, memberName, sourceFilePath, sourceLineNumber); 63 | } 64 | 65 | public static bool TryEnter(Object lockObject) 66 | { 67 | throw new NotImplementedException(); 68 | } 69 | public static void TryEnter(Object lockObject, ref bool lockTaken) 70 | { 71 | throw new NotImplementedException(); 72 | } 73 | 74 | public static bool TryEnter(Object lockObject, int millisecondsTimeout) 75 | { 76 | throw new NotImplementedException(); 77 | } 78 | 79 | public static void TryEnter(Object lockObject, int millisecondsTimeout, ref bool lockTaken) 80 | { 81 | throw new NotImplementedException(); 82 | } 83 | public static bool TryEnter(Object lockObject, TimeSpan timeout) 84 | { 85 | throw new NotImplementedException(); 86 | } 87 | 88 | public static void TryEnter(Object lockObject, TimeSpan timeout, ref bool lockTaken) 89 | { 90 | throw new NotImplementedException(); 91 | } 92 | 93 | public static void Wait(Object lockObject, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 94 | { 95 | var instance = GetMonitorInstance(lockObject); 96 | instance.Wait(lockObject, memberName, sourceFilePath, sourceLineNumber); 97 | } 98 | 99 | public static void Wait(Object lockObject, int millisecondsTimeout) 100 | { 101 | 102 | } 103 | 104 | public static void Wait(Object lockObject, int millisecondsTimeout, bool exitContext) 105 | { 106 | 107 | } 108 | 109 | public static void Wait(Object lockObject, TimeSpan timeout) 110 | { 111 | 112 | } 113 | 114 | public static void Wait(Object lockObject, TimeSpan timeout, bool exitContext) 115 | { 116 | 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /Engine/CLR/RUnordered.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace RelaSharp.CLR 4 | { 5 | public static class RUnordered 6 | { 7 | public static T Read(ref CLRAtomic data, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) where T : class 8 | { 9 | var atomic = CLRAtomic.Get(ref data); 10 | return atomic.Load(MemoryOrder.Relaxed, memberName, sourceFilePath, sourceLineNumber); 11 | } 12 | 13 | public static void Write(ref CLRAtomic data, T newValue, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) where T : class 14 | { 15 | // All writes are release in the CLR, but specify them as Relaxed anyway. 16 | // This is a compromise that feels OK, it means that LiveAtomics won't use Volatile unnecessarily, 17 | // and it's probably very bad practise to rely upon stores being release in the CLR implicitly in the first place. 18 | var atomic = CLRAtomic.Get(ref data); 19 | atomic.Store(newValue, MemoryOrder.Relaxed, memberName, sourceFilePath, sourceLineNumber); 20 | } 21 | 22 | public static int Read(ref CLRAtomicInt data, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 23 | { 24 | var atomic = CLRAtomicInt.Get(ref data); 25 | return atomic.Load(MemoryOrder.Relaxed, memberName, sourceFilePath, sourceLineNumber); 26 | } 27 | 28 | public static void Write(ref CLRAtomicInt data, int newValue, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 29 | { 30 | var atomic = CLRAtomicInt.Get(ref data); 31 | // All writes are release in the CLR, but see the comment above. 32 | atomic.Store(newValue, MemoryOrder.Relaxed, memberName, sourceFilePath, sourceLineNumber); 33 | } 34 | 35 | public static long Read(ref CLRAtomicLong data, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 36 | { 37 | var atomic = CLRAtomicLong.Get(ref data); 38 | return atomic.Load(MemoryOrder.Relaxed, memberName, sourceFilePath, sourceLineNumber); 39 | } 40 | 41 | public static void Write(ref CLRAtomicLong data, long newValue, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 42 | { 43 | var atomic = CLRAtomicLong.Get(ref data); 44 | // All writes are release in the CLR, but see the comment above. 45 | atomic.Store(newValue, MemoryOrder.Relaxed, memberName, sourceFilePath, sourceLineNumber); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /Engine/CLR/RVolatile.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace RelaSharp.CLR 4 | { 5 | public static class RVolatile 6 | { 7 | public static T Read(ref CLRAtomic data, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) where T : class 8 | { 9 | var atomic = CLRAtomic.Get(ref data); 10 | return atomic.Load(MemoryOrder.Acquire, memberName, sourceFilePath, sourceLineNumber); 11 | } 12 | 13 | public static void Write(ref CLRAtomic data, T newValue, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) where T : class 14 | { 15 | var atomic = CLRAtomic.Get(ref data); 16 | atomic.Store(newValue, MemoryOrder.Release, memberName, sourceFilePath, sourceLineNumber); 17 | } 18 | public static int Read(ref CLRAtomicInt data, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 19 | { 20 | var atomic = CLRAtomicInt.Get(ref data); 21 | return atomic.Load(MemoryOrder.Acquire, memberName, sourceFilePath, sourceLineNumber); 22 | } 23 | 24 | public static void Write(ref CLRAtomicInt data, int newValue, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 25 | { 26 | var atomic = CLRAtomicInt.Get(ref data); 27 | atomic.Store(newValue, MemoryOrder.Release, memberName, sourceFilePath, sourceLineNumber); 28 | } 29 | 30 | public static long Read(ref CLRAtomicLong data, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 31 | { 32 | var atomic = CLRAtomicLong.Get(ref data); 33 | return atomic.Load(MemoryOrder.Acquire, memberName, sourceFilePath, sourceLineNumber); 34 | } 35 | 36 | public static void Write(ref CLRAtomicLong data, long newValue, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 37 | { 38 | var atomic = CLRAtomicLong.Get(ref data); 39 | atomic.Store(newValue, MemoryOrder.Release, memberName, sourceFilePath, sourceLineNumber); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /Engine/Engine.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | portable 6 | RelaSharp.Engine 7 | RelaSharp.Engine 8 | 2.0.0 9 | 1.0.5 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Engine/EngineException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RelaSharp 4 | { 5 | class EngineException : Exception 6 | { 7 | internal EngineException(string message) : base(message) 8 | { 9 | 10 | } 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /Engine/EventLog.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace RelaSharp 6 | { 7 | class EventLog 8 | { 9 | private List _eventLog; 10 | private int _numThreads; 11 | 12 | public EventLog(int numThreads) 13 | { 14 | _numThreads = numThreads; 15 | _eventLog = new List(10000); 16 | } 17 | 18 | public void RecordEvent(int runningThreadId, long runningThreadClock, string callingThreadFuncName, string sourceFilePath, int sourceLineNumber, string eventInfo) 19 | { 20 | _eventLog.Add(new ExecutionEvent(runningThreadId, runningThreadClock, callingThreadFuncName, sourceFilePath, sourceLineNumber, eventInfo)); 21 | } 22 | 23 | public void Dump(TextWriter output) 24 | { 25 | var directories = _eventLog.Select(e => Path.GetDirectoryName(e.SourceFilePath)).Distinct(); 26 | output.WriteLine(); 27 | output.WriteLine($"Code executed in directories: {string.Join(",", directories)}"); 28 | output.WriteLine(); 29 | output.WriteLine("Interleaved execution log"); 30 | output.WriteLine("*************************"); 31 | for(int i = 0; i < _eventLog.Count; ++i) 32 | { 33 | var e = _eventLog[i]; 34 | var fileName = Path.GetFileName(e.SourceFilePath); 35 | output.WriteLine($"[{i}] {e.ThreadId}@{e.ThreadClock} in {fileName}:{e.CallingMemberName}:{e.SourceLineNumber} | {e.EventInfo}"); 36 | } 37 | output.WriteLine(); 38 | output.WriteLine("Individual thread logs"); 39 | output.WriteLine("**********************"); 40 | for(int i = 0; i < _numThreads; ++i) 41 | { 42 | output.WriteLine($"Thread {i}"); 43 | output.WriteLine("--------"); 44 | for(int j = 0; j < _eventLog.Count; ++j) 45 | { 46 | var e = _eventLog[j]; 47 | if(e.ThreadId == i) 48 | { 49 | var fileName = Path.GetFileName(e.SourceFilePath); 50 | output.WriteLine($"[{j}] {e.ThreadId}@{e.ThreadClock} in {fileName}:{e.CallingMemberName}:{e.SourceLineNumber} | {e.EventInfo}"); 51 | } 52 | } 53 | } 54 | } 55 | 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /Engine/ExecutionEvent.cs: -------------------------------------------------------------------------------- 1 | namespace RelaSharp 2 | { 3 | class ExecutionEvent 4 | { 5 | public readonly int ThreadId; 6 | public readonly long ThreadClock; 7 | public readonly string CallingMemberName; 8 | public readonly string SourceFilePath; 9 | public readonly int SourceLineNumber; 10 | public readonly string EventInfo; 11 | 12 | public ExecutionEvent(int threadId, long threadClock, string callingThreadFuncName, string sourceFilePath, int sourceLineNumber, string eventInfo) 13 | { 14 | ThreadId = threadId; 15 | ThreadClock = threadClock; 16 | CallingMemberName = callingThreadFuncName; 17 | SourceFilePath = sourceFilePath; 18 | SourceLineNumber = sourceLineNumber; 19 | EventInfo = eventInfo; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Engine/Fence.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace RelaSharp 4 | { 5 | public class Fence 6 | { 7 | private static TestEnvironment TE = TestEnvironment.TE; 8 | 9 | public static void Insert(MemoryOrder mo, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 10 | { 11 | TE.MaybeSwitch(); 12 | var runningThread = TE.RunningThread; 13 | runningThread.Fence(mo, TE.SequentiallyConsistentFence); 14 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Fence: {mo}"); 15 | } 16 | 17 | /// 18 | /// Intended to mimic the semantics of mprotect (Unix) or FlushProcessWriteBuffers (Windows) 19 | /// I'm unsure of their exact semantics, but think this is correct. This operation is "fully serializing" in the sense that 20 | /// all subsequent loads by all threads will see the latest store to every atomic by all other threads before the fence. 21 | /// 22 | public static void InsertProcessWide([CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 23 | { 24 | TE.MaybeSwitch(); 25 | var seqCstFence = TE.SequentiallyConsistentFence; 26 | foreach(var thread in TE.AllThreads) 27 | { 28 | seqCstFence.Join(thread.ReleasesAcquired); 29 | } 30 | foreach(var thread in TE.AllThreads) 31 | { 32 | thread.Fence(MemoryOrder.SequentiallyConsistent, seqCstFence); 33 | } 34 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, "Fence: ProcessWide"); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Engine/IRelaTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RelaSharp 5 | { 6 | public interface IRelaTest 7 | { 8 | IReadOnlyList ThreadEntries { get; } 9 | void OnBegin(); 10 | void OnFinished(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Engine/LiveRelaEngine.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace RelaSharp 5 | { 6 | class LiveRelaEngine : IRelaEngine 7 | { 8 | public void Assert(bool shouldBeTrue, string reason, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 9 | { 10 | } 11 | 12 | public void MaybeSwitch() 13 | { 14 | } 15 | 16 | public void Yield() 17 | { 18 | Thread.Yield(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Engine/MemoryModel/AccessData.cs: -------------------------------------------------------------------------------- 1 | namespace RelaSharp.MemoryModel 2 | { 3 | class AccessData // Not sure if encapsulation provided by this type is desirable or right structure. 4 | { 5 | public int LastStoredThreadId { get; private set; } 6 | public long LastStoredThreadClock { get; private set; } 7 | public bool LastStoreWasSequentiallyConsistent { get; private set;} 8 | public VectorClock ReleasesToAcquire { get; private set; } 9 | public VectorClock LastSeen { get; private set; } 10 | public T Payload { get; private set; } 11 | public bool IsInitialized { get; private set; } 12 | 13 | public AccessData(int numThreads) 14 | { 15 | ReleasesToAcquire = new VectorClock(numThreads); 16 | LastSeen = new VectorClock(numThreads); 17 | } 18 | 19 | public void RecordStore(int threadIdx, long threadClock, MemoryOrder mo, T payload) 20 | { 21 | LastSeen.SetAllClocks(VectorClock.MaxTime); 22 | LastSeen[threadIdx] = threadClock; 23 | LastStoredThreadId = threadIdx; 24 | LastStoredThreadClock = threadClock; 25 | LastStoreWasSequentiallyConsistent = mo == MemoryOrder.SequentiallyConsistent; 26 | IsInitialized = true; 27 | Payload = payload; 28 | } 29 | 30 | public void RecordLoad(int threadIdx, long threadClock) 31 | { 32 | LastSeen[threadIdx] = threadClock; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Engine/MemoryModel/AccessDataPool.cs: -------------------------------------------------------------------------------- 1 | namespace RelaSharp.MemoryModel 2 | { 3 | class AccessDataPool 4 | { 5 | public int CurrentIndex { get; private set; } 6 | public int SizeOccupied { get; private set; } 7 | private AccessData[] _pool; 8 | 9 | public AccessDataPool(int length, int numThreads) 10 | { 11 | _pool = new AccessData[length]; 12 | for(int i = 0; i < length; ++i) 13 | { 14 | _pool[i] = new AccessData(numThreads); 15 | } 16 | } 17 | 18 | public AccessData GetNext() 19 | { 20 | CurrentIndex++; 21 | if(SizeOccupied < _pool.Length) 22 | { 23 | SizeOccupied = CurrentIndex; 24 | } 25 | return this[CurrentIndex]; 26 | } 27 | 28 | public AccessData this[int idx] 29 | { 30 | get 31 | { 32 | int wrapped = idx % _pool.Length; 33 | if(wrapped < 0) 34 | { 35 | wrapped += _pool.Length; 36 | } 37 | return _pool[wrapped]; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Engine/MemoryModel/AccessHistory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RelaSharp.MemoryModel 4 | { 5 | class AccessHistory 6 | { 7 | private readonly AccessDataPool _history; 8 | private readonly int _numThreads; 9 | private readonly ILookback _lookback; 10 | 11 | public T CurrentValue => _history[_history.CurrentIndex].Payload; 12 | 13 | public AccessHistory(int length, int numThreads, ILookback lookback) 14 | { 15 | _history = new AccessDataPool(length, numThreads); 16 | _numThreads = numThreads; 17 | _lookback = lookback; 18 | } 19 | 20 | public void RecordRMWStore(T data, MemoryOrder mo, ShadowThread runningThread) 21 | { 22 | RecordStore(data, mo, runningThread, true); 23 | } 24 | 25 | public void RecordStore(T data, MemoryOrder mo, ShadowThread runningThread) 26 | { 27 | var previous = _history[_history.CurrentIndex - 1]; 28 | RecordStore(data, mo, runningThread, previous.IsInitialized && previous.LastStoredThreadId == runningThread.Id); 29 | } 30 | 31 | private void RecordStore(T data, MemoryOrder mo, ShadowThread runningThread, bool isReleaseSequence) 32 | { 33 | var storeTarget = _history.GetNext(); 34 | storeTarget.RecordStore(runningThread.Id, runningThread.Clock, mo, data); 35 | 36 | bool isAtLeastRelease = mo == MemoryOrder.Release || mo == MemoryOrder.AcquireRelease || mo == MemoryOrder.SequentiallyConsistent; 37 | 38 | // Here 'sourceClock' is the clock that other threads must synchronize with if they read-acquire this data. 39 | // If this store is a release (or stronger), then those threads must synchronize with the latest clocks that 40 | // this thread has synchronized with (i.e. the releases it has acquired: runningThread.ReleasesAcquired). 41 | // Otherwise, if this store is relaxed, then those threads need only synchronize with the latest release fence of this thread 42 | // (i.e. runningThread.FenceReleasesAcquired) 43 | var sourceClock = isAtLeastRelease ? runningThread.ReleasesAcquired : runningThread.FenceReleasesAcquired; 44 | 45 | var previous = _history[_history.CurrentIndex - 1]; 46 | var targetClock = storeTarget.ReleasesToAcquire; 47 | if(isReleaseSequence) 48 | { 49 | targetClock.Assign(previous.ReleasesToAcquire); 50 | targetClock.Join(sourceClock); 51 | } 52 | else 53 | { 54 | targetClock.Assign(sourceClock); 55 | } 56 | } 57 | 58 | public T RecordRMWLoad(MemoryOrder mo, ShadowThread runningThread) 59 | { 60 | var loadData = _history[_history.CurrentIndex]; 61 | return RecordLoad(mo, runningThread, loadData); 62 | } 63 | 64 | public T RecordPossibleLoad(MemoryOrder mo, ShadowThread runningThread) 65 | { 66 | var loadData = GetPossibleLoad(runningThread.ReleasesAcquired, runningThread.Id, mo); 67 | return RecordLoad(mo, runningThread, loadData); 68 | } 69 | 70 | private T RecordLoad(MemoryOrder mo, ShadowThread runningThread, AccessData loadData) 71 | { 72 | loadData.RecordLoad(runningThread.Id, runningThread.Clock); 73 | bool isAtLeastAcquire = mo == MemoryOrder.Acquire || mo == MemoryOrder.AcquireRelease || mo == MemoryOrder.SequentiallyConsistent; 74 | 75 | // Here 'destinationClock' is the clock that must synchronize with the last release to this data. 76 | // If this load is an acquire (or stronger), then this thread's clock must synchronize with the last release 77 | // (i.e. it should acquire the release to this data so runningThread.ReleasesAcquired must update). 78 | // Otherwise, if this load is relaxed, then other threads must only synchronize with the last release to this data 79 | // if an acquire fence is issued. So to allow for updating the releases acquired by this thread in the case of an acquire 80 | // fence being issued at some point, update runningThread.FenceReleasesToAcquire 81 | var destinationClock = isAtLeastAcquire ? runningThread.ReleasesAcquired : runningThread.FenceReleasesToAcquire; 82 | destinationClock.Join(loadData.ReleasesToAcquire); 83 | return loadData.Payload; 84 | } 85 | 86 | private AccessData GetPossibleLoad(VectorClock releasesAcquired, int threadId, MemoryOrder mo) 87 | { 88 | int maxLookback = 0; 89 | for(maxLookback = 0; maxLookback < _history.SizeOccupied; ++maxLookback) 90 | { 91 | int j = _history.CurrentIndex - maxLookback; 92 | if(!_history[j].IsInitialized) 93 | { 94 | throw new Exception("This should never happen: access to uninitialized variable."); 95 | } 96 | var accessData = _history[j]; 97 | if(mo == MemoryOrder.SequentiallyConsistent && accessData.LastStoreWasSequentiallyConsistent) 98 | { 99 | break; 100 | } 101 | // Has the loading thread synchronized-with this or a later release of the last storing thread to this variable? 102 | if(releasesAcquired[accessData.LastStoredThreadId] >= accessData.LastStoredThreadClock) 103 | { 104 | // If so, this is the oldest load that can be returned, since this thread has synchronized-with 105 | // ("acquired a release" of) the storing thread at or after this store. 106 | break; 107 | } 108 | // Has the loading thread synchronized-with any thread that has loaded this or a later value 109 | // of this variable? 110 | if(releasesAcquired.AnyGreaterOrEqual(accessData.LastSeen)) 111 | { 112 | // If so, this is the oldest load that can be returned to the loading thread, otherwise it'd 113 | // be going back in time, since it has synchronized-with ("acquired a release" of) a thread that has seen this or a later value 114 | // of this variable. 115 | break; 116 | } 117 | } 118 | int lookback = _lookback.ChooseLookback(maxLookback); 119 | return _history[_history.CurrentIndex - lookback]; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Engine/MemoryModel/ILookback.cs: -------------------------------------------------------------------------------- 1 | namespace RelaSharp.MemoryModel 2 | { 3 | public interface ILookback 4 | { 5 | int ChooseLookback(int maxLookback); 6 | } 7 | } -------------------------------------------------------------------------------- /Engine/MemoryModel/InternalAtomic.cs: -------------------------------------------------------------------------------- 1 | namespace RelaSharp.MemoryModel 2 | { 3 | class InternalAtomic 4 | { 5 | private AccessHistory _history; 6 | 7 | public T CurrentValue => _history.CurrentValue; 8 | 9 | public InternalAtomic(int historyLength, int numThreads, ILookback lookback) 10 | { 11 | _history = new AccessHistory(historyLength, numThreads, lookback); 12 | } 13 | 14 | public void Store(T data, MemoryOrder mo, ShadowThread runningThread) 15 | { 16 | _history.RecordStore(data, mo, runningThread); 17 | } 18 | 19 | public T Load(MemoryOrder mo, ShadowThread runningThread) 20 | { 21 | T result = _history.RecordPossibleLoad(mo, runningThread); 22 | return result; 23 | } 24 | 25 | public bool CompareExchange(T newData, T comparand, MemoryOrder mo, ShadowThread runningThread, out T loadedData) 26 | { 27 | loadedData = _history.RecordRMWLoad(mo, runningThread); 28 | if(loadedData == null && comparand == null || loadedData != null && loadedData.Equals(comparand)) 29 | { 30 | _history.RecordRMWStore(newData, mo, runningThread); 31 | return true; 32 | } 33 | return false; 34 | } 35 | 36 | public T Exchange(T newData, MemoryOrder mo, ShadowThread runningThread) 37 | { 38 | var oldData = _history.RecordRMWLoad(mo, runningThread); 39 | _history.RecordRMWStore(newData, mo, runningThread); 40 | return oldData; 41 | } 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Engine/MemoryModel/InternalRaceChecked.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RelaSharp.MemoryModel 4 | { 5 | class InternalRaceChecked 6 | { 7 | private T _data; 8 | private VectorClock _loadClock; 9 | private VectorClock _storeClock; 10 | 11 | public InternalRaceChecked(int numThreads) 12 | { 13 | _loadClock = new VectorClock(numThreads); 14 | _storeClock = new VectorClock(numThreads); 15 | } 16 | 17 | public void Store(T data, ShadowThread runningThread, Action failTest) 18 | { 19 | if(_loadClock.AnyGreater(runningThread.ReleasesAcquired) || _storeClock.AnyGreater(runningThread.ReleasesAcquired)) 20 | { 21 | failTest($"Data race detected in store on thread {runningThread.Id} @ {runningThread.Clock}"); 22 | return; 23 | } 24 | runningThread.IncrementClock(); 25 | _storeClock[runningThread.Id] = runningThread.Clock; 26 | _data = data; 27 | return; 28 | } 29 | 30 | public T Load(ShadowThread runningThread, Action failTest) 31 | { 32 | if(_storeClock.AnyGreater(runningThread.ReleasesAcquired)) 33 | { 34 | failTest($"Data race detected in load on thread {runningThread.Id} @ {runningThread.Clock}"); 35 | return default(T); 36 | } 37 | runningThread.IncrementClock(); 38 | _loadClock[runningThread.Id] = runningThread.Clock; 39 | return _data; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /Engine/MemoryModel/ShadowThread.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RelaSharp.MemoryModel 4 | { 5 | class ShadowThread 6 | { 7 | public long Clock => ReleasesAcquired[Id]; 8 | public readonly VectorClock ReleasesAcquired; 9 | public readonly VectorClock FenceReleasesToAcquire; 10 | public readonly VectorClock FenceReleasesAcquired; 11 | public readonly int Id; 12 | 13 | public ShadowThread(int id, int numThreads) 14 | { 15 | Id = id; 16 | ReleasesAcquired = new VectorClock(numThreads); 17 | FenceReleasesToAcquire = new VectorClock(numThreads); 18 | FenceReleasesAcquired = new VectorClock(numThreads); 19 | } 20 | 21 | public void Fence(MemoryOrder mo, VectorClock sequentiallyConsistentFence) 22 | { 23 | switch(mo) // TODO: Should this be done in Fence.Insert ? 24 | { 25 | case MemoryOrder.Acquire: 26 | AcquireFence(); 27 | break; 28 | case MemoryOrder.Release: 29 | ReleaseFence(); 30 | break; 31 | case MemoryOrder.AcquireRelease: 32 | AcquireFence(); 33 | ReleaseFence(); 34 | break; 35 | case MemoryOrder.SequentiallyConsistent: 36 | AcquireFence(); 37 | sequentiallyConsistentFence.Join(ReleasesAcquired); 38 | ReleasesAcquired.Assign(sequentiallyConsistentFence); 39 | ReleaseFence(); 40 | break; 41 | default: 42 | throw new Exception($"Unsupported memory fence order {mo}"); 43 | } 44 | } 45 | 46 | private void AcquireFence() 47 | { 48 | ReleasesAcquired.Join(FenceReleasesToAcquire); 49 | } 50 | private void ReleaseFence() 51 | { 52 | FenceReleasesAcquired.Assign(ReleasesAcquired); 53 | } 54 | private void AcquireReleaseFence() 55 | { 56 | AcquireFence(); 57 | ReleaseFence(); 58 | } 59 | public void IncrementClock() 60 | { 61 | ReleasesAcquired[Id]++; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /Engine/MemoryModel/VectorClock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace RelaSharp.MemoryModel 5 | { 6 | class VectorClock 7 | { 8 | public const long MaxTime = long.MaxValue; 9 | private long[] _clocks; 10 | public readonly int Size; 11 | 12 | public VectorClock(int size) 13 | { 14 | if(size <= 0) 15 | { 16 | throw new ArgumentOutOfRangeException(nameof(size)); 17 | } 18 | _clocks = new long[size]; 19 | Size = size; 20 | } 21 | 22 | private void CheckSize(VectorClock other) 23 | { 24 | if(Size != other.Size) 25 | { 26 | throw new Exception($"Cannot operate on vector clocks of different sizes, this size = {Size}, other size = {other.Size}"); 27 | } 28 | } 29 | 30 | public bool AnyGreaterOrEqual(VectorClock other) 31 | { 32 | return Any(other, (x, y) => x >= y); 33 | } 34 | 35 | public bool AnyGreater(VectorClock other) 36 | { 37 | return Any(other, (x, y) => x > y); 38 | } 39 | 40 | private bool Any(VectorClock other, Func cmp) 41 | { 42 | CheckSize(other); 43 | for(int i = 0; i < Size; ++i) 44 | { 45 | if(cmp(_clocks[i], other._clocks[i])) 46 | { 47 | return true; 48 | } 49 | } 50 | return false; 51 | } 52 | 53 | public long this[int idx] 54 | { 55 | get 56 | { 57 | return _clocks[idx]; 58 | } 59 | set 60 | { 61 | _clocks[idx] = value; 62 | } 63 | 64 | } 65 | 66 | public void SetAllClocks(long v) 67 | { 68 | for(int i = 0; i < Size; ++i) 69 | { 70 | _clocks[i] = v; 71 | } 72 | } 73 | 74 | public void Join(VectorClock other) 75 | { 76 | CheckSize(other); 77 | for(int i = 0; i < Size; ++i) 78 | { 79 | if(other._clocks[i] > _clocks[i]) 80 | { 81 | _clocks[i] = other._clocks[i]; 82 | } 83 | } 84 | } 85 | 86 | public void Assign(VectorClock other) 87 | { 88 | CheckSize(other); 89 | for(int i = 0; i < Size; ++i) 90 | { 91 | _clocks[i] = other._clocks[i]; 92 | } 93 | } 94 | 95 | public override string ToString() 96 | { 97 | var sb = new StringBuilder(Size); 98 | sb.Append(_clocks[0]); 99 | for(int i = 1; i < Size; ++i) 100 | { 101 | sb.Append($"^{_clocks[i]}"); 102 | } 103 | return sb.ToString(); 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /Engine/MemoryOrder.cs: -------------------------------------------------------------------------------- 1 | namespace RelaSharp 2 | { 3 | public enum MemoryOrder 4 | { 5 | Relaxed, 6 | Acquire, 7 | Release, 8 | AcquireRelease, 9 | SequentiallyConsistent 10 | } 11 | } -------------------------------------------------------------------------------- /Engine/RaceChecked.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using RelaSharp.MemoryModel; 3 | 4 | namespace RelaSharp 5 | { 6 | 7 | public class RaceChecked 8 | { 9 | private static TestEnvironment TE = TestEnvironment.TE; 10 | 11 | private InternalRaceChecked _raceChecked; 12 | 13 | public void Store(T data, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 14 | { 15 | MaybeInit(); 16 | var runningThread = TE.RunningThread; 17 | _raceChecked.Store(data, TE.RunningThread, TE.FailTest); 18 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Store --> {data}"); 19 | } 20 | 21 | public T Load([CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 22 | { 23 | MaybeInit(); 24 | var result = _raceChecked.Load(TE.RunningThread, TE.FailTest); 25 | TE.RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Load <-- {result}"); 26 | return result; 27 | } 28 | 29 | private void MaybeInit() 30 | { 31 | if(_raceChecked == null) 32 | { 33 | _raceChecked = new InternalRaceChecked(TE.NumThreads); 34 | _raceChecked.Store(default(T), TE.RunningThread, TE.FailTest); 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Engine/RelaEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace RelaSharp 5 | { 6 | public enum EngineMode 7 | { 8 | Undefined, // Use this in RunTest to blow up? 9 | Test, 10 | Live 11 | } 12 | 13 | public interface IRelaEngine 14 | { 15 | void Assert(bool shouldBeTrue, string reason, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0); 16 | void Yield(); 17 | void MaybeSwitch(); 18 | } 19 | 20 | class TestRelaEngine : IRelaEngine 21 | { 22 | private static TestEnvironment TE = TestEnvironment.TE; 23 | public void Assert(bool shouldBeTrue, string reason, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 24 | => TE.Assert(shouldBeTrue, reason, memberName, sourceFilePath, sourceLineNumber); 25 | public void Yield() 26 | => TE.Yield(); 27 | public void MaybeSwitch() 28 | => TE.MaybeSwitch(); 29 | } 30 | 31 | public class RelaEngine 32 | { 33 | public static EngineMode Mode; 34 | private static TestRelaEngine TestEngine = new TestRelaEngine(); 35 | private static LiveRelaEngine LiveEngine = new LiveRelaEngine(); 36 | public static IRelaEngine RE 37 | { 38 | get 39 | { 40 | switch (Mode) 41 | { 42 | case EngineMode.Test: 43 | return TestEngine; 44 | case EngineMode.Live: 45 | return LiveEngine; 46 | } 47 | throw new ArgumentOutOfRangeException($"Please set the {nameof(Mode)} property to either {EngineMode.Test} or {EngineMode.Live}. The latter is uninstrumented, for 'production' use."); 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /Engine/Scheduling/Exhaustive/Choice.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RelaSharp.Scheduling.Exhaustive 4 | { 5 | class Choice 6 | { 7 | public int Chosen { get; private set; } 8 | public readonly ThreadSet _alternatives; 9 | public bool FullyExplored => _numElemsSeen >= _alternatives.NumElems; 10 | private int _numElemsSeen; 11 | 12 | public Choice(ThreadSet alternatives) 13 | { 14 | _alternatives = alternatives; 15 | int n = alternatives.NumElems; 16 | if (n == 0) 17 | { 18 | throw new ArgumentOutOfRangeException($"No alternatives to choose between."); 19 | } 20 | Chosen = _alternatives.Successor(0); 21 | _numElemsSeen = 1; 22 | } 23 | 24 | public void Next() 25 | { 26 | if (FullyExplored) 27 | { 28 | throw new Exception("Scheduling choice already exhausted."); 29 | } 30 | Chosen = _alternatives.Successor(Chosen + 1); 31 | _numElemsSeen++; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Engine/Scheduling/Exhaustive/PriorityRelation.cs: -------------------------------------------------------------------------------- 1 | namespace RelaSharp.Scheduling.Exhaustive 2 | { 3 | class PriorityRelation 4 | { 5 | private readonly int _numThreads; 6 | private readonly bool[,] _hasPriority; // _hasPriority[x, y] = true <=> x will not be scheduled when y is enabled. 7 | 8 | public PriorityRelation(int numThreads) 9 | { 10 | _numThreads = numThreads; 11 | _hasPriority = new bool[numThreads, numThreads]; 12 | } 13 | 14 | public void GivePriorityOver(int x, ThreadSet threads) 15 | { 16 | for (int i = 0; i < _numThreads; ++i) 17 | { 18 | if(threads[i] && i != x) 19 | { 20 | _hasPriority[x, i] = true; 21 | } 22 | } 23 | } 24 | 25 | public void RemovePriorityOf(int x) 26 | { 27 | for (int i = 0; i < _numThreads; ++i) 28 | { 29 | _hasPriority[i, x] = false; 30 | } 31 | } 32 | 33 | public ThreadSet GetSchedulableThreads(ThreadSet enabled) 34 | { 35 | var admissable = new ThreadSet(_numThreads); 36 | admissable.ReplaceWith(enabled); 37 | for(int i = 0; i < _numThreads; ++i) 38 | { 39 | if(!admissable[i]) 40 | { 41 | continue; 42 | } 43 | for(int j = 0; j < _numThreads; ++j) 44 | { 45 | // Thread i cannot be scheduled if an enabled thread 46 | // has priority over it. 47 | if(_hasPriority[i, j] && enabled[j]) 48 | { 49 | admissable.Remove(i); 50 | break; 51 | } 52 | } 53 | } 54 | return admissable; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /Engine/Scheduling/Exhaustive/SchedulingStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RelaSharp.Scheduling.Exhaustive 4 | { 5 | class SchedulingStrategy 6 | { 7 | private Choice[] _choices; 8 | private int _choiceIdx; 9 | private int _lastChoiceIdx; 10 | private bool ResumeInProgress => _choiceIdx <= _lastChoiceIdx; 11 | private int[] _lookbacks; 12 | private Random _random; 13 | private int _lookBackIdx; 14 | public bool Finished { get; private set; } 15 | 16 | public SchedulingStrategy(ulong maxChoices) 17 | { 18 | _choices = new Choice[maxChoices]; 19 | _lookbacks = new int[maxChoices]; 20 | _random = new Random(); 21 | _lookBackIdx = 0; 22 | _choiceIdx = 0; 23 | _lastChoiceIdx = -1; 24 | } 25 | 26 | public int GetNextThreadId(PriorityRelation priority, ThreadSet enabled) 27 | { 28 | Choice result; 29 | if (ResumeInProgress) 30 | { 31 | result = _choices[_choiceIdx]; 32 | _choiceIdx++; 33 | } 34 | else 35 | { 36 | var schedulable = priority.GetSchedulableThreads(enabled); 37 | result = new Choice(schedulable); 38 | _choices[_choiceIdx] = result; 39 | _lastChoiceIdx++; 40 | _choiceIdx++; 41 | } 42 | return result.Chosen; 43 | } 44 | 45 | public void Rollback() 46 | { 47 | _lastChoiceIdx--; 48 | _choiceIdx--; 49 | } 50 | 51 | public int GetLookback(int maxLookback) 52 | { 53 | // We ensure look-back is deterministic for resume purposes, but not exhaustive. 54 | if(ResumeInProgress) 55 | { 56 | return _lookbacks[_lookBackIdx++]; 57 | } 58 | var lookback = _random.Next(maxLookback + 1); 59 | _lookbacks[_lookBackIdx++] = lookback; 60 | return lookback; 61 | } 62 | 63 | public int GetZeroLookback() 64 | { 65 | if(ResumeInProgress) 66 | { 67 | return _lookbacks[_lookBackIdx++]; 68 | } 69 | _lookbacks[_lookBackIdx++] = 0; 70 | return 0; 71 | } 72 | 73 | public bool Advance() 74 | { 75 | _choiceIdx = 0; 76 | _lookBackIdx = 0; 77 | bool resuming = _lastChoiceIdx >= 0; 78 | while (_lastChoiceIdx >= 0 && _choices[_lastChoiceIdx].FullyExplored) 79 | { 80 | _lastChoiceIdx--; 81 | } 82 | if (_lastChoiceIdx >= 0) 83 | { 84 | _choices[_lastChoiceIdx].Next(); 85 | } 86 | else if (resuming) 87 | { 88 | Finished = true; 89 | } 90 | return !Finished; 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /Engine/Scheduling/Exhaustive/ThreadSet.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace RelaSharp.Scheduling.Exhaustive 4 | { 5 | class ThreadSet 6 | { 7 | private readonly bool[] _elems; 8 | public int NumElems { get; private set; } 9 | public int NumThreads { get; private set;} 10 | public ThreadSet(int numThreads) 11 | { 12 | NumThreads = numThreads; 13 | _elems = new bool[numThreads]; 14 | NumElems = 0; 15 | } 16 | 17 | public void Add(int idx) 18 | { 19 | if(!_elems[idx]) 20 | { 21 | _elems[idx] = true; 22 | ++NumElems; 23 | } 24 | } 25 | 26 | public void Remove(int idx) 27 | { 28 | if(_elems[idx]) 29 | { 30 | _elems[idx] = false; 31 | --NumElems; 32 | } 33 | } 34 | 35 | public int Successor(int idx) 36 | { 37 | int i = idx; 38 | while(i < _elems.Length && !_elems[i]) 39 | { 40 | ++i; 41 | } 42 | return i; // Return _elems.Length if no successor 43 | } 44 | 45 | public bool this[int idx] => _elems[idx]; 46 | 47 | public void Clear() 48 | { 49 | if (NumElems > 0) 50 | { 51 | NumElems = 0; 52 | for (int i = 0; i < _elems.Length; ++i) 53 | { 54 | _elems[i] = false; 55 | } 56 | } 57 | } 58 | 59 | public void ReplaceWith(ThreadSet other) 60 | { 61 | for (int i = 0; i < _elems.Length; ++i) 62 | { 63 | _elems[i] = other._elems[i]; 64 | } 65 | NumElems = other.NumElems; 66 | } 67 | 68 | public void IntersectWith(ThreadSet other) 69 | { 70 | NumElems = 0; 71 | for(int i = 0; i < _elems.Length; ++i) 72 | { 73 | _elems[i] &= other._elems[i]; 74 | if(_elems[i]) 75 | { 76 | ++NumElems; 77 | } 78 | } 79 | return; 80 | } 81 | 82 | public void LessWith(ThreadSet other) 83 | { 84 | NumElems = 0; 85 | for(int i = 0; i < _elems.Length; ++i) 86 | { 87 | _elems[i] &= !other._elems[i]; 88 | if(_elems[i]) 89 | { 90 | ++NumElems; 91 | } 92 | } 93 | } 94 | 95 | public void UnionWith(ThreadSet other) 96 | { 97 | NumElems = 0; 98 | for(int i = 0; i < _elems.Length; ++i) 99 | { 100 | _elems[i] |= other._elems[i]; 101 | if(_elems[i]) 102 | { 103 | ++NumElems; 104 | } 105 | } 106 | } 107 | 108 | public override string ToString() 109 | { 110 | var sb = new StringBuilder(_elems.Length); 111 | sb.Append(_elems[0] ? "1" : "0"); 112 | for(int i = 1; i < _elems.Length; ++i) 113 | { 114 | var e = _elems[i] ? 1 : 0; 115 | sb.Append($"^{e}"); 116 | } 117 | return sb.ToString(); 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /Engine/Scheduling/ExhaustiveScheduler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RelaSharp.Scheduling.Exhaustive; 3 | 4 | namespace RelaSharp.Scheduling 5 | { 6 | public class ExhaustiveScheduler : IScheduler 7 | { 8 | private readonly SchedulingStrategy _strategy; 9 | private readonly int _numThreads; 10 | private readonly int _yieldLookbackPenalty; 11 | 12 | private ThreadSet _finished; 13 | private ThreadSet _enabled; 14 | private ThreadSet[] _disabledSince; 15 | private ThreadSet[] _scheduledSince; 16 | private ThreadSet[] _enabledSince; 17 | private object[] _waitingOnLock; 18 | private PriorityRelation _priority; 19 | private int _currentYieldPenalty; 20 | public bool AllThreadsFinished => _finished.NumElems == _numThreads; 21 | public int RunningThreadId { get; private set; } 22 | private bool _deadlock; 23 | 24 | public ExhaustiveScheduler(int numThreads, ulong maxChoices, int yieldLookbackPenalty) 25 | { 26 | _numThreads = numThreads; 27 | _strategy = new SchedulingStrategy(maxChoices); 28 | _yieldLookbackPenalty = yieldLookbackPenalty; 29 | } 30 | 31 | 32 | private void PrepareForScheduling() 33 | { 34 | _finished = new ThreadSet(_numThreads); 35 | _enabled = new ThreadSet(_numThreads); 36 | _disabledSince = new ThreadSet[_numThreads]; 37 | _scheduledSince = new ThreadSet[_numThreads]; 38 | _enabledSince = new ThreadSet[_numThreads]; 39 | for(int i = 0; i < _numThreads; ++i) 40 | { 41 | _disabledSince[i] = new ThreadSet(_numThreads); 42 | _enabledSince[i] = new ThreadSet(_numThreads); 43 | _scheduledSince[i] = new ThreadSet(_numThreads); 44 | _enabled.Add(i); 45 | } 46 | _waitingOnLock = new object[_numThreads]; 47 | _priority = new PriorityRelation(_numThreads); 48 | _currentYieldPenalty = 0; 49 | _deadlock = false; 50 | } 51 | 52 | public int ChooseLookback(int maxLookback) 53 | { 54 | return _currentYieldPenalty > 0 ? _strategy.GetZeroLookback() : _strategy.GetLookback(maxLookback); 55 | } 56 | 57 | public bool NewIteration() 58 | { 59 | PrepareForScheduling(); 60 | if(_strategy.Finished) 61 | { 62 | throw new Exception("Already finished"); 63 | } 64 | bool result = _strategy.Advance(); 65 | MaybeSwitch(); 66 | return result; 67 | } 68 | 69 | public void MaybeSwitch() 70 | { 71 | if(AllThreadsFinished) 72 | { 73 | throw new Exception("All threads finished. Who called?"); 74 | } 75 | if(_currentYieldPenalty > 0) 76 | { 77 | _currentYieldPenalty--; 78 | } 79 | ChooseRunningThread(); 80 | SchedulingEpilogue(); 81 | return; 82 | } 83 | 84 | private void ChooseRunningThread() 85 | { 86 | RunningThreadId = _strategy.GetNextThreadId(_priority, _enabled); 87 | } 88 | private void SchedulingEpilogue() 89 | { 90 | for(int i = 0; i < _numThreads; ++i) 91 | { 92 | _scheduledSince[i].Add(RunningThreadId); 93 | } 94 | _priority.RemovePriorityOf(RunningThreadId); 95 | } 96 | 97 | public bool ThreadWaiting(int waitingOnThreadId, object lockObject) 98 | { 99 | _strategy.Rollback(); 100 | _enabled.Remove(RunningThreadId); 101 | // If waitingOnThreadId == -1, then the running thread has blocked itself waiting on a condition. 102 | // So there isn't a clear notion of which thread it is waiting on, or has been disabled since the 103 | // last yield of. It might make sense to add the running thread to all the _disableSince sets 104 | // rather than none, but I'm not going to explore this scheduling more deeply, at least for now. 105 | bool threadWaitingOnCondition = waitingOnThreadId == -1; 106 | if(!threadWaitingOnCondition) 107 | { 108 | _disabledSince[waitingOnThreadId].Add(RunningThreadId); 109 | } 110 | _waitingOnLock[RunningThreadId] = lockObject; 111 | for(int i = 0; i < _numThreads; ++i) 112 | { 113 | _enabledSince[i].Remove(RunningThreadId); 114 | } 115 | _deadlock = _enabled.NumElems == 0; 116 | if(!_deadlock) 117 | { 118 | MaybeSwitch(); 119 | } 120 | return _deadlock; 121 | } 122 | 123 | public void ThreadFinishedWaiting() 124 | { 125 | _enabled.Add(RunningThreadId); 126 | _waitingOnLock[RunningThreadId] = null; 127 | } 128 | 129 | public void LockReleased(object lockObject) 130 | { 131 | for(int i = 0; i < _numThreads; ++i) 132 | { 133 | // Re-enabling all threads waiting on the lock allows spurious wake-ups for threads 134 | // that have waited on a condition variable (i.e. RMonitor.Wait in CLR land). 135 | // I think this is fine, as it at worst it makes running inside the model checker a potentially harsher environment 136 | // than outside. 137 | if(_waitingOnLock[i] == lockObject) 138 | { 139 | _waitingOnLock[i] = null; 140 | _enabled.Add(i); 141 | _disabledSince[i].Remove(i); 142 | } 143 | } 144 | } 145 | 146 | public void Yield() 147 | { 148 | // This is the core of the algorithm, inspired by Musuvathi and Qadeer's "Fair Stateless Model Checking" from PLDI'08, which is the basis 149 | // of the CHESS scheduler. 150 | // 151 | // The rationale is as follows: Give threads priority of the currently running thread that have been continuously enabled since 152 | // its last yield, or which have been disabled since it last yielded 153 | // Note, this is different from Musuvathi and Qadeer, because their _disabledSince 154 | // is defined to include all threads disabled during some transition of the running thread 155 | // since its last yield. In reality, threads don't disable each other like this. From looking at the CHESS source it appears it 156 | // implements this by having a lookahead which it searches for locks. So basically their condition is 157 | // (ContinuouslyEnabledSinceLastYield UNION DisabledByRunningThreadSinceLastYield) LESS ScheduledSinceLastYield 158 | // and mine is: 159 | // (ContinuouslyEnabledSinceLastYield LESS ScheduledSinceLastYield) UNION DisabledSinceLastYield 160 | // My 'DisabledSinceLastYield' includes the notion that the thread was disabled by the currently running thread, but note that 161 | // a thread can only be disabled by scheduling it, so something like Disabled LESS Scheduled would always be empty for my scheduler. 162 | var unfairlyStarved = _enabledSince[RunningThreadId]; 163 | unfairlyStarved.LessWith(_scheduledSince[RunningThreadId]); 164 | unfairlyStarved.UnionWith(_disabledSince[RunningThreadId]); 165 | _priority.GivePriorityOver(RunningThreadId, unfairlyStarved); 166 | 167 | ChooseRunningThread(); 168 | 169 | _enabledSince[RunningThreadId].ReplaceWith(_enabled); 170 | _disabledSince[RunningThreadId].Clear(); 171 | _scheduledSince[RunningThreadId].Clear(); 172 | SchedulingEpilogue(); 173 | _currentYieldPenalty += _yieldLookbackPenalty; 174 | } 175 | 176 | public void ThreadFinished() 177 | { 178 | _finished.Add(RunningThreadId); 179 | _enabled.Remove(RunningThreadId); 180 | _priority.RemovePriorityOf(RunningThreadId); 181 | if(!AllThreadsFinished) 182 | { 183 | if(_deadlock) 184 | { 185 | // If there is a deadlock, manually schedule the threads 186 | // in turn, so they have the opportunity to throw an exception and exit. 187 | // Calling MaybeSwitch() here isn't possible, because there are no 188 | // enabled threads to schedule. 189 | var unfinished = new ThreadSet(_numThreads); 190 | for(int i = 0; i < _numThreads; ++i) 191 | { 192 | if(!_finished[i]) 193 | { 194 | RunningThreadId = i; 195 | } 196 | } 197 | } 198 | else 199 | { 200 | MaybeSwitch(); 201 | } 202 | } 203 | } 204 | } 205 | } -------------------------------------------------------------------------------- /Engine/Scheduling/IScheduler.cs: -------------------------------------------------------------------------------- 1 | using RelaSharp.MemoryModel; 2 | 3 | namespace RelaSharp.Scheduling 4 | { 5 | public interface IScheduler : ILookback 6 | { 7 | void MaybeSwitch(); 8 | bool ThreadWaiting(int waitingOnThreadId, object lockObject); 9 | void ThreadFinishedWaiting(); 10 | void ThreadFinished(); 11 | void LockReleased(object lockObject); 12 | void Yield(); 13 | bool NewIteration(); 14 | int RunningThreadId { get; } 15 | bool AllThreadsFinished { get; } 16 | } 17 | } -------------------------------------------------------------------------------- /Engine/Scheduling/NaiveRandomScheduler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace RelaSharp.Scheduling 5 | { 6 | public class NaiveRandomScheduler : IScheduler 7 | { 8 | class ArraySet 9 | { 10 | private int[] _elems; 11 | public int NumElems { get; private set; } 12 | public ArraySet(int capacity) 13 | { 14 | _elems = new int[capacity]; 15 | } 16 | 17 | public bool Add(int x) 18 | { 19 | int idx = Array.IndexOf(_elems, x, 0, NumElems); 20 | if (idx != -1) 21 | { 22 | return false; 23 | } 24 | _elems[NumElems] = x; 25 | NumElems++; 26 | return true; 27 | } 28 | 29 | public bool Remove(int x) 30 | { 31 | int idx = Array.IndexOf(_elems, x, 0, NumElems); 32 | if (idx == -1) 33 | { 34 | return false; 35 | } 36 | NumElems--; 37 | _elems[idx] = _elems[NumElems]; 38 | return true; 39 | } 40 | 41 | public int this[int idx] => _elems[idx]; 42 | 43 | public void Clear() 44 | { 45 | NumElems = 0; 46 | } 47 | 48 | public override string ToString() => $"{NumElems}:{String.Join(",", _elems.Take(NumElems))}"; 49 | } 50 | 51 | private readonly Random _random = new Random(); 52 | private readonly int _numIterations; 53 | private readonly int _numThreads; 54 | private ArraySet _unfinishedThreadIds; 55 | private ArraySet _waitingThreadIds; 56 | private ArraySet _threadIdsSeenWhileAllWaiting; 57 | private int _runningThreadIndex; 58 | private int NumUnfinishedThreads => _unfinishedThreadIds.NumElems; 59 | public bool AllThreadsFinished => _unfinishedThreadIds.NumElems == 0; 60 | public int RunningThreadId => _unfinishedThreadIds[_runningThreadIndex]; 61 | private int _iterationCount; 62 | 63 | public NaiveRandomScheduler(int numThreads, int numIterations) 64 | { 65 | _numThreads = numThreads; 66 | _numIterations = numIterations; 67 | PrepareForScheduling(); 68 | MaybeSwitch(); 69 | } 70 | 71 | private void PrepareForScheduling() 72 | { 73 | _unfinishedThreadIds = new ArraySet(_numThreads); 74 | _waitingThreadIds = new ArraySet(_numThreads); 75 | _threadIdsSeenWhileAllWaiting = new ArraySet(_numThreads); 76 | for(int i = 0; i < _numThreads; ++i) 77 | { 78 | _unfinishedThreadIds.Add(i); 79 | } 80 | } 81 | 82 | public int ChooseLookback(int maxLookback) 83 | { 84 | return _random.Next(maxLookback + 1); 85 | } 86 | 87 | public void MaybeSwitch() 88 | { 89 | if(AllThreadsFinished) 90 | { 91 | throw new Exception("All threads finished. Who called?"); 92 | } 93 | _runningThreadIndex = _random.Next(NumUnfinishedThreads); 94 | return; 95 | } 96 | 97 | public bool ThreadWaiting(int waitingOnThreadId, object lockObject) 98 | { 99 | _waitingThreadIds.Add(RunningThreadId); 100 | if(_waitingThreadIds.NumElems == NumUnfinishedThreads) 101 | { 102 | _threadIdsSeenWhileAllWaiting.Add(RunningThreadId); 103 | } 104 | else 105 | { 106 | _threadIdsSeenWhileAllWaiting.Clear(); 107 | } 108 | bool deadlock = NumUnfinishedThreads == 1 || _threadIdsSeenWhileAllWaiting.NumElems == NumUnfinishedThreads; 109 | int originalId = RunningThreadId; 110 | if(!deadlock) 111 | { 112 | while(RunningThreadId == originalId) 113 | { 114 | MaybeSwitch(); 115 | } 116 | } 117 | return deadlock; 118 | } 119 | 120 | public void ThreadFinishedWaiting() => _waitingThreadIds.Remove(RunningThreadId); // TODO: Should clear _threadIdsSeenWhileAllWaiting() ? 121 | 122 | public void Yield() 123 | { 124 | } 125 | 126 | public void LockReleased(object lockObject) 127 | { 128 | } 129 | 130 | public void ThreadFinished() 131 | { 132 | _unfinishedThreadIds.Remove(RunningThreadId); 133 | if(!AllThreadsFinished) 134 | { 135 | MaybeSwitch(); 136 | } 137 | } 138 | 139 | public bool NewIteration() 140 | { 141 | ++_iterationCount; 142 | PrepareForScheduling(); 143 | return _iterationCount <= _numIterations; 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /Engine/TestEnvironment.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using RelaSharp.Scheduling; 5 | using RelaSharp.MemoryModel; 6 | 7 | namespace RelaSharp 8 | { 9 | class TestEnvironment 10 | { 11 | public int HistoryLength => 20; 12 | public ulong LiveLockLimit { get; private set;} 13 | public static TestEnvironment TE = new TestEnvironment(); 14 | public ShadowThread RunningThread => _shadowThreads[_scheduler.RunningThreadId]; 15 | public IReadOnlyList AllThreads => _shadowThreads; 16 | 17 | public int NumThreads { get; private set; } 18 | public bool TestFailed { get; private set; } 19 | public string TestFailureReason { get; private set;} 20 | public ulong ExecutionLength { get; private set; } 21 | public VectorClock SequentiallyConsistentFence { get; private set; } 22 | public ILookback Lookback => _scheduler; 23 | private IScheduler _scheduler; 24 | private TestThreads _testThreads; 25 | private ShadowThread[] _shadowThreads; 26 | private EventLog _eventLog; 27 | private bool _testStarted; 28 | 29 | public void RunTest(IRelaTest test, IScheduler scheduler, ulong liveLockLimit) 30 | { 31 | NumThreads = test.ThreadEntries.Count; 32 | _scheduler = scheduler; 33 | LiveLockLimit = liveLockLimit; 34 | TestFailed = false; 35 | _shadowThreads = new ShadowThread[NumThreads]; 36 | _eventLog = new EventLog(NumThreads); 37 | _testStarted = false; 38 | SequentiallyConsistentFence = new VectorClock(NumThreads); 39 | ExecutionLength = 0; 40 | 41 | for(int i = 0; i < NumThreads; ++i) 42 | { 43 | _shadowThreads[i] = new ShadowThread(i, NumThreads); 44 | } 45 | 46 | _testThreads = new TestThreads(test, _scheduler); 47 | test.OnBegin(); 48 | _testStarted = true; 49 | _testThreads.WakeThread(); 50 | _testThreads.Join(); 51 | test.OnFinished(); 52 | } 53 | 54 | private int SchedulingPreamble() 55 | { 56 | if(ExecutionLength > LiveLockLimit) 57 | { 58 | FailTest($"Possible live-lock: execution length has exceeded {LiveLockLimit}"); 59 | } 60 | if(TestFailed) 61 | { 62 | throw new TestFailedException(); 63 | } 64 | ++ExecutionLength; 65 | return _scheduler.RunningThreadId; 66 | } 67 | 68 | public void MaybeSwitch() 69 | { 70 | if(!_testStarted) 71 | { 72 | return; // This happens due to OnBegin running before the thread starts (on some default thread) 73 | } 74 | int previousThreadId = SchedulingPreamble(); 75 | _scheduler.MaybeSwitch(); 76 | if(_scheduler.RunningThreadId == previousThreadId) 77 | { 78 | return; 79 | } 80 | _testThreads.WakeNewThreadAndBlockPrevious(previousThreadId); 81 | } 82 | 83 | public void ThreadWaiting(int waitingOnThreadId, object lockObject) 84 | { 85 | if(!_testStarted) 86 | { 87 | return; 88 | } 89 | int previousThreadId = SchedulingPreamble(); 90 | if(_scheduler.ThreadWaiting(waitingOnThreadId, lockObject)) 91 | { 92 | FailTest($"Deadlock detected: all threads waiting."); 93 | } 94 | _testThreads.WakeNewThreadAndBlockPrevious(previousThreadId); 95 | } 96 | 97 | public void ThreadFinishedWaiting() 98 | { 99 | _scheduler.ThreadFinishedWaiting(); 100 | } 101 | 102 | public void Yield() 103 | { 104 | int previousThreadId = SchedulingPreamble(); 105 | _scheduler.Yield(); 106 | if(_scheduler.RunningThreadId == previousThreadId) 107 | { 108 | return; 109 | } 110 | _testThreads.WakeNewThreadAndBlockPrevious(previousThreadId); 111 | } 112 | 113 | public void LockReleased(object lockObject) 114 | { 115 | _scheduler.LockReleased(lockObject); 116 | } 117 | 118 | public void Assert(bool shouldBeTrue, string reason, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 119 | { 120 | string assertResult = shouldBeTrue ? "passed" : "failed"; 121 | RecordEvent(memberName, sourceFilePath, sourceLineNumber, $"Assert ({assertResult}): {reason}"); 122 | if(!shouldBeTrue) 123 | { 124 | FailTest(TestFailed ? $"{TestFailureReason} AND {reason}" : reason); 125 | } 126 | } 127 | 128 | public void RecordEvent(string callingThreadFuncName, string sourceFilePath, int sourceLineNumber, string eventInfo) 129 | { 130 | _eventLog.RecordEvent(RunningThread.Id, RunningThread.Clock, callingThreadFuncName, sourceFilePath, sourceLineNumber, eventInfo); 131 | } 132 | 133 | public void FailTest(string reason) 134 | { 135 | TestFailureReason = reason; 136 | TestFailed = true; 137 | } 138 | 139 | public void DumpExecutionLog(TextWriter output) 140 | { 141 | output.WriteLine("----- Begin Test Execution Log ----"); 142 | output.WriteLine(); 143 | if(TestFailed) 144 | { 145 | output.WriteLine($"Test failed with reason: '{TestFailureReason}'"); 146 | } 147 | else 148 | { 149 | output.WriteLine($"Test passed."); 150 | } 151 | _eventLog.Dump(output); 152 | output.WriteLine("----- End Test Execution Log ----"); 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /Engine/TestFailedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RelaSharp 4 | { 5 | class TestFailedException : Exception 6 | { 7 | 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /Engine/TestRunner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using RelaSharp.Scheduling; 4 | 5 | namespace RelaSharp 6 | { 7 | public class TestRunner // TODO: Should the RunTest() functions sanity check EngineMode? 8 | { 9 | public static TestRunner TR = new TestRunner(); 10 | 11 | private static TestEnvironment TE = TestEnvironment.TE; 12 | public void RunTest(IRelaTest test, IScheduler scheduler, ulong liveLockLimit) // Maybe re-name to RunTestOnce 13 | => TE.RunTest(test, scheduler, liveLockLimit); 14 | 15 | public void RunTest(Func makeTest, int numIterations = 10000, ulong liveLockLimit = 5000) 16 | { 17 | var test = makeTest(); 18 | var scheduler = new NaiveRandomScheduler(test.ThreadEntries.Count, numIterations); 19 | while(scheduler.NewIteration() && !TestFailed) 20 | { 21 | TE.RunTest(test, scheduler, liveLockLimit); 22 | test = makeTest(); 23 | } 24 | } 25 | 26 | public bool TestFailed => TE.TestFailed; 27 | 28 | public ulong ExecutionLength => TE.ExecutionLength; 29 | 30 | public void DumpExecutionLog(TextWriter output) 31 | => TE.DumpExecutionLog(output); 32 | } 33 | } -------------------------------------------------------------------------------- /Engine/TestThreads.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Collections.Generic; 4 | using RelaSharp.Scheduling; 5 | 6 | namespace RelaSharp 7 | { 8 | class TestThreads 9 | { 10 | private enum ThreadState 11 | { 12 | Running, 13 | Blocked, 14 | Finished 15 | } 16 | 17 | private Thread[] _threads; 18 | private ThreadState[] _threadStates; 19 | private Object[] _threadLocks; 20 | private object _runningThreadLock = new object(); 21 | private IScheduler _scheduler; 22 | 23 | public TestThreads(IRelaTest test, IScheduler scheduler) 24 | { 25 | var numThreads = test.ThreadEntries.Count; 26 | _threads = new Thread[numThreads]; 27 | _threadStates = new ThreadState[numThreads]; 28 | _threadLocks = new Object[numThreads]; 29 | _scheduler = scheduler; 30 | CreateThreads(test.ThreadEntries); 31 | } 32 | 33 | private void CreateThreads(IReadOnlyList threadEntries) 34 | { 35 | var numThreads = threadEntries.Count; 36 | for (int i = 0; i < numThreads; ++i) 37 | { 38 | _threadLocks[i] = new Object(); 39 | int j = i; 40 | Action threadEntry = threadEntries[j]; 41 | Action wrapped = () => MakeThreadFunction(threadEntry, j); 42 | _threadStates[i] = ThreadState.Running; 43 | _threads[i] = new Thread(new ThreadStart(wrapped)); 44 | _threads[i].Start(); 45 | } 46 | for (int i = 0; i < numThreads; ++i) 47 | { 48 | var l = _threadLocks[i]; 49 | lock (l) 50 | { 51 | while (_threadStates[i] == ThreadState.Running) 52 | { 53 | Monitor.Wait(l); 54 | } 55 | } 56 | } 57 | } 58 | 59 | public void WakeThread() 60 | { 61 | int idx = _scheduler.RunningThreadId; 62 | _threadStates[idx] = ThreadState.Running; 63 | var l = _threadLocks[idx]; 64 | lock(l) 65 | { 66 | Monitor.Pulse(l); 67 | } 68 | } 69 | 70 | public void WakeNewThreadAndBlockPrevious(int previousThreadId) 71 | { 72 | _threadStates[previousThreadId] = ThreadState.Blocked; 73 | WakeThread(); 74 | var runningLock = _threadLocks[previousThreadId]; 75 | lock(runningLock) 76 | { 77 | Monitor.Exit(_runningThreadLock); 78 | while(_threadStates[previousThreadId] == ThreadState.Blocked) 79 | { 80 | Monitor.Wait(runningLock); 81 | } 82 | } 83 | Monitor.Enter(_runningThreadLock); 84 | } 85 | 86 | public void Join() 87 | { 88 | for(int i = 0; i < _threads.Length; ++i) 89 | { 90 | _threads[i].Join(); 91 | } 92 | } 93 | 94 | private void MakeThreadFunction(Action threadFunction, int threadIdx) 95 | { 96 | var l = _threadLocks[threadIdx]; 97 | 98 | lock(l) 99 | { 100 | _threadStates[threadIdx] = ThreadState.Blocked; 101 | Monitor.Pulse(l); 102 | Monitor.Wait(l); 103 | } 104 | try 105 | { 106 | Monitor.Enter(_runningThreadLock); 107 | threadFunction(); 108 | } 109 | catch(TestFailedException) 110 | { 111 | } 112 | Monitor.Exit(_runningThreadLock); 113 | _threadStates[threadIdx] = ThreadState.Finished; 114 | _scheduler.ThreadFinished(); 115 | if(!_scheduler.AllThreadsFinished) 116 | { 117 | WakeThread(); 118 | } 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /Examples/AsymmetricPetersen.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RelaSharp.Examples 5 | { 6 | public class AsymmetricPetersen : IRelaExample 7 | { 8 | public IReadOnlyList ThreadEntries { get; private set; } 9 | 10 | public string Name => "Asymmetric Petersen lock via Interprocessor Interrupt"; 11 | public string Description => "Uses Interlocked.MemoryBarrierProcessWide (FlushProcessWriteBuffers / mprotect)"; 12 | public bool ExpectedToFail => false; 13 | private static IRelaEngine RE = RelaEngine.RE; 14 | private Atomic interestedF; 15 | private Atomic interestedS; 16 | private Atomic victim; 17 | private int _threadsPassed; 18 | private bool _hasRun; 19 | 20 | public AsymmetricPetersen() 21 | { 22 | ThreadEntries = new List {FastThread,SlowThread}; 23 | } 24 | 25 | public void PrepareForIteration() 26 | { 27 | PrepareForNewConfig(); 28 | } 29 | 30 | public void FastThread() 31 | { 32 | interestedF.Store(1, MemoryOrder.Relaxed); 33 | victim.Store(0, MemoryOrder.Release); 34 | while(true) 35 | { 36 | if(interestedS.Load(MemoryOrder.Relaxed) != 1) 37 | { 38 | break; 39 | } 40 | if(victim.Load(MemoryOrder.Relaxed) != 0) 41 | { 42 | break; 43 | } 44 | RE.Yield(); 45 | } 46 | RE.Assert(_threadsPassed == 0, $"Fast thread entered while slow thread in critical section! ({_threadsPassed})"); 47 | _threadsPassed++; 48 | interestedF.Store(0, MemoryOrder.Relaxed); 49 | _threadsPassed--; 50 | } 51 | 52 | public void SlowThread() 53 | { 54 | interestedS.Store(1, MemoryOrder.Relaxed); 55 | Fence.InsertProcessWide(); 56 | victim.Exchange(1, MemoryOrder.AcquireRelease); 57 | while(true) 58 | { 59 | if(interestedF.Load(MemoryOrder.Relaxed) != 1) 60 | { 61 | break; 62 | } 63 | if(victim.Load(MemoryOrder.Relaxed) != 1) 64 | { 65 | break; 66 | } 67 | RE.Yield(); 68 | } 69 | RE.Assert(_threadsPassed == 0, $"Slow thread entered while fast thread in critical section! ({_threadsPassed})"); 70 | _threadsPassed++; 71 | interestedS.Store(0, MemoryOrder.Relaxed); 72 | _threadsPassed--; 73 | 74 | } 75 | public void OnBegin() 76 | { 77 | } 78 | public void OnFinished() 79 | { 80 | } 81 | 82 | private void PrepareForNewConfig() 83 | { 84 | interestedF = new Atomic(); 85 | interestedS = new Atomic(); 86 | victim = new Atomic(); 87 | _threadsPassed = 0; 88 | } 89 | 90 | public bool SetNextConfiguration() 91 | { 92 | bool oldHasRun = _hasRun; 93 | PrepareForNewConfig(); 94 | _hasRun = true; 95 | return !oldHasRun; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/BoundedSPSCQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RelaSharp.Examples 5 | { 6 | class BoundedSPSCQueue : IRelaExample 7 | { 8 | private class Config 9 | { 10 | public readonly string Description; 11 | public readonly MemoryOrder EnqueueMemoryOrder; 12 | public readonly MemoryOrder DequeueMemoryOrder; 13 | public readonly bool UseEnqueueReleaseFence; 14 | public readonly bool UseDequeueAcquireFence; 15 | public readonly bool ExpectedToFail; 16 | 17 | public Config(string description, MemoryOrder enqueueMemoryOrder, MemoryOrder dequeueMemoryOrder, bool useEnqueueReleaseFence, bool useDequeueAcquireFence, bool expectedToFail) 18 | { 19 | Description = description; 20 | EnqueueMemoryOrder = enqueueMemoryOrder; 21 | DequeueMemoryOrder = dequeueMemoryOrder; 22 | UseEnqueueReleaseFence = useEnqueueReleaseFence; 23 | UseDequeueAcquireFence = useDequeueAcquireFence; 24 | ExpectedToFail = expectedToFail; 25 | } 26 | } 27 | 28 | private class QueueEntry 29 | { 30 | public readonly Atomic Data; 31 | 32 | public QueueEntry() 33 | { 34 | Data = new Atomic(); 35 | } 36 | 37 | public override string ToString() 38 | { 39 | return Data.ToString(); 40 | } 41 | } 42 | private class Queue 43 | { 44 | private Atomic[] _data; 45 | private RaceChecked _read = new RaceChecked(); 46 | private RaceChecked _write = new RaceChecked(); 47 | private int _size; 48 | private Config _config; 49 | public Queue(int size, Config config) 50 | { 51 | _data = new Atomic[size]; 52 | for(int i = 0; i < size; ++i) 53 | { 54 | _data[i] = new Atomic(); 55 | } 56 | _size = size; 57 | _config = config; 58 | } 59 | 60 | public bool Enqueue(QueueEntry x) 61 | { 62 | var w = _write.Load(); 63 | if(_data[w].Load(MemoryOrder.Relaxed) != null) 64 | { 65 | return false; 66 | } 67 | if(_config.UseEnqueueReleaseFence) 68 | { 69 | Fence.Insert(MemoryOrder.Release); 70 | } 71 | _data[w].Store(x, _config.EnqueueMemoryOrder); 72 | _write.Store((w + 1) % _size); 73 | return true; 74 | } 75 | 76 | public QueueEntry Dequeue() 77 | { 78 | var r = _read.Load(); 79 | var result = _data[r].Load(_config.DequeueMemoryOrder); 80 | if(_config.UseDequeueAcquireFence) 81 | { 82 | Fence.Insert(MemoryOrder.Acquire); 83 | } 84 | if(result == null) 85 | { 86 | return null; 87 | } 88 | _data[r].Store(null, MemoryOrder.Relaxed); 89 | _read.Store((r + 1) % _size); 90 | return result; 91 | } 92 | } 93 | public IReadOnlyList ThreadEntries { get; private set;} 94 | public string Name => "SPSC queue tests"; 95 | public string Description => ActiveConfig.Description; 96 | public bool ExpectedToFail => ActiveConfig.ExpectedToFail; 97 | private Queue _queue; 98 | private IEnumerator _configs; 99 | private Config ActiveConfig => _configs.Current; 100 | private int _size; 101 | private static IRelaEngine RE = RelaEngine.RE; 102 | 103 | public BoundedSPSCQueue() 104 | { 105 | ThreadEntries = new List{Producer, Consumer}; 106 | _size = 3; 107 | var configList = new List{ 108 | new Config("Enqueue and dequeue relaxed, no memory fences", MemoryOrder.Relaxed, MemoryOrder.Relaxed, false, false, true), 109 | new Config("Enqueue and dequeue relaxed, dequeue acquire fence only", MemoryOrder.Relaxed, MemoryOrder.Relaxed, false, true, true), 110 | new Config("Enqueue and dequeue relaxed, enqueue release fence only", MemoryOrder.Relaxed, MemoryOrder.Relaxed, true, false, true), 111 | new Config("Enqueue and dequeue relaxed, enqueue release and dequeue acquire fence", MemoryOrder.Relaxed, MemoryOrder.Relaxed, true, true, false), 112 | new Config("Enqueue release, dequeue relaxed, no memory fences", MemoryOrder.Release, MemoryOrder.Relaxed, false, false, true), 113 | new Config("Enqueue relaxed, dequeue acquire, no memory fences", MemoryOrder.Relaxed, MemoryOrder.Acquire, false, false, true), 114 | new Config("Enqueue release, dequeue acquire, no memory fences", MemoryOrder.Release, MemoryOrder.Acquire, false, false, false), 115 | }; 116 | _configs = configList.GetEnumerator(); 117 | } 118 | private const int _offset = 123; 119 | private void Producer() 120 | { 121 | for(int i = 0; i < _size; ++i) 122 | { 123 | var x = new QueueEntry(); 124 | x.Data.Store(i + _offset, MemoryOrder.Relaxed); 125 | _queue.Enqueue(x); 126 | } 127 | } 128 | private void Consumer() 129 | { 130 | int numDequeued = 0; 131 | while(numDequeued < _size) 132 | { 133 | var x = _queue.Dequeue(); 134 | if(x != null) 135 | { 136 | var z = x.Data.Load(MemoryOrder.Relaxed); 137 | int expected = _offset + numDequeued; 138 | RE.Assert(z == expected, $"Partially initialised object detected: Data = {z}, expected to be {expected}"); 139 | numDequeued++; 140 | } 141 | } 142 | } 143 | 144 | public void OnBegin() 145 | { 146 | 147 | } 148 | public void OnFinished() 149 | { 150 | 151 | } 152 | private void PrepareForNewConfig() 153 | { 154 | _queue = new Queue(_size, ActiveConfig); 155 | } 156 | public void PrepareForIteration() 157 | { 158 | PrepareForNewConfig(); 159 | } 160 | public bool SetNextConfiguration() 161 | { 162 | bool moreConfigurations = _configs.MoveNext(); 163 | PrepareForNewConfig(); 164 | return moreConfigurations; 165 | } 166 | } 167 | 168 | } -------------------------------------------------------------------------------- /Examples/CLR/AsymmetricLock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using RelaSharp.CLR; 4 | 5 | namespace RelaSharp.Examples 6 | { 7 | public class AsymmetricLock : IRelaExample 8 | { 9 | class AsymmetricLockInternal 10 | { 11 | CLRAtomicInt _holdingThreadId; 12 | CLRAtomicInt _isHeld; 13 | public AsymmetricLockInternal() 14 | { 15 | } 16 | 17 | internal void Enter() 18 | { 19 | if (RUnordered.Read(ref _isHeld) == 1) 20 | { 21 | int currentThreadId = Environment.CurrentManagedThreadId; 22 | if (RUnordered.Read(ref _holdingThreadId) == currentThreadId) 23 | { 24 | return; 25 | } 26 | } 27 | EnterSlow(); 28 | } 29 | 30 | private object _lockObj = new object(); 31 | 32 | private void EnterSlow() 33 | { 34 | RMonitor.Enter(_lockObj); 35 | RUnordered.Write(ref _holdingThreadId, Environment.CurrentManagedThreadId); 36 | RInterlocked.MemoryBarrierProcessWide(); 37 | while (RUnordered.Read(ref _isHeld) == 1) 38 | { 39 | RE.Yield(); 40 | } 41 | RUnordered.Write(ref _isHeld, 1); 42 | RMonitor.Exit(_lockObj); 43 | return; 44 | } 45 | 46 | public void Exit() 47 | { 48 | RUnordered.Write(ref _isHeld, 0); 49 | } 50 | } 51 | public IReadOnlyList ThreadEntries { get; private set; } 52 | 53 | public string Name => "Quickly re-acquirable lock via interprocessor interrupt"; 54 | public string Description => "Uses Interlocked.MemoryBarrierProcessWide"; 55 | public bool ExpectedToFail => false; 56 | private static IRelaEngine RE = RelaEngine.RE; 57 | private int _threadsPassed; 58 | private bool _hasRun; 59 | private AsymmetricLockInternal _asymLock; 60 | 61 | public AsymmetricLock() 62 | { 63 | ThreadEntries = new List {Thread0,Thread1}; 64 | } 65 | 66 | public void PrepareForIteration() 67 | { 68 | PrepareForNewConfig(); 69 | } 70 | 71 | public void Thread0() 72 | { 73 | for (int i = 0; i < 2; ++i) 74 | { 75 | _asymLock.Enter(); 76 | RE.Assert(_threadsPassed == 0, $"Iteration {i}: Thread0 entered while Thread1 in critical section! ({_threadsPassed})"); 77 | _threadsPassed++; 78 | _asymLock.Exit(); 79 | _threadsPassed--; 80 | } 81 | } 82 | 83 | public void Thread1() 84 | { 85 | for (int i = 0; i < 2; ++i) 86 | { 87 | _asymLock.Enter(); 88 | RE.Assert(_threadsPassed == 0, $"Iteration {i}: Thread1 entered while Thread0 in critical section! ({_threadsPassed})"); 89 | _threadsPassed++; 90 | _asymLock.Exit(); 91 | _threadsPassed--; 92 | } 93 | } 94 | public void OnBegin() 95 | { 96 | } 97 | public void OnFinished() 98 | { 99 | } 100 | 101 | private void PrepareForNewConfig() 102 | { 103 | _threadsPassed = 0; 104 | _asymLock = new AsymmetricLockInternal(); 105 | } 106 | 107 | public bool SetNextConfiguration() 108 | { 109 | bool oldHasRun = _hasRun; 110 | PrepareForNewConfig(); 111 | _hasRun = true; 112 | return !oldHasRun; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Examples/CLR/COWList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Linq; 5 | 6 | using RelaSharp.CLR; 7 | 8 | namespace RelaSharp.Examples 9 | { 10 | class COWList : IRelaExample 11 | { 12 | private static IRelaEngine RE = RelaEngine.RE; 13 | 14 | public string Name => "Multiple-writer copy-on-write list."; 15 | 16 | public string Description => $"{NumWritingThreads} writing threads, 1 reading thread."; 17 | 18 | public bool ExpectedToFail => false; 19 | 20 | public IReadOnlyList ThreadEntries { get; } 21 | 22 | private bool _moreConfigurations = true; 23 | 24 | private MultipleWriterCOWList _list; 25 | private const int NumWrittenPerThread = 10; 26 | private const int NumWritingThreads = 5; 27 | 28 | public COWList() 29 | { 30 | var writingThreads = Enumerable.Range(0, NumWritingThreads).Select(MakeWritingThread); 31 | ThreadEntries = new Action[]{ReadingThread}.Concat(writingThreads).ToList(); 32 | } 33 | 34 | public void OnBegin() 35 | { 36 | _list = new MultipleWriterCOWList(); 37 | for(int i = 0; i < NumWritingThreads; ++i) 38 | { 39 | _writingThreadFinished[i] = new Atomic(); 40 | } 41 | } 42 | 43 | public void OnFinished() 44 | { 45 | } 46 | 47 | private Action MakeWritingThread(int threadIdx) 48 | { 49 | return () => WritingThread(threadIdx); 50 | } 51 | 52 | private Atomic[] _writingThreadFinished = new Atomic[NumWritingThreads]; 53 | 54 | private void WritingThread(int threadIdx) 55 | { 56 | for(int i = 0; i < NumWrittenPerThread; ++i) 57 | { 58 | _list.Add(threadIdx * NumWrittenPerThread + i); 59 | } 60 | _writingThreadFinished[threadIdx].Store(true, MemoryOrder.SequentiallyConsistent); 61 | } 62 | 63 | private void ReadingThread() 64 | { 65 | while(true) 66 | { 67 | int i; 68 | for(i = 0; i < NumWritingThreads; ++i) 69 | { 70 | if(!_writingThreadFinished[i].Load(MemoryOrder.SequentiallyConsistent)) 71 | { 72 | break; 73 | } 74 | } 75 | if(i == NumWritingThreads) 76 | { 77 | break; 78 | } 79 | } 80 | var final = new List(); 81 | var numElems = NumWritingThreads * NumWrittenPerThread; 82 | RE.Assert(_list.Count == numElems, $"Expected final list to have {numElems} elements, but instead it has {_list.Count}"); 83 | for(int i = 0; i < numElems; ++i) 84 | { 85 | final.Add(_list[i]); 86 | } 87 | final.Sort(); 88 | for(int i = 0; i < numElems; ++i) 89 | { 90 | RE.Assert(final[i] == i, $"Expected sorted final list element number {i} to be {i}, but it's {final[i]}."); 91 | } 92 | } 93 | 94 | public void PrepareForIteration() 95 | { 96 | } 97 | public bool SetNextConfiguration() 98 | { 99 | var result = _moreConfigurations; 100 | _moreConfigurations = false; 101 | return result; 102 | } 103 | 104 | class MultipleWriterCOWList 105 | { 106 | private CLRAtomic> _data; 107 | 108 | public int Count => RUnordered.Read(ref _data).Count; 109 | 110 | public MultipleWriterCOWList() 111 | { 112 | //RVolatile.Write(ref _data, new List()); 113 | } 114 | 115 | public void Add(int v) 116 | { 117 | while (true) 118 | { 119 | var local = RVolatile.Read(ref _data); 120 | var copy = local == null ? new List() : new List(local); 121 | copy.Add(v); 122 | if (RInterlocked.CompareExchange(ref _data, copy, local)) 123 | { 124 | break; 125 | } 126 | } 127 | } 128 | 129 | public int this[int idx] => RUnordered.Read(ref _data)[idx]; 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /Examples/CLR/DCLReadIndicator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Linq; 5 | 6 | using RelaSharp.CLR; 7 | 8 | namespace RelaSharp.Examples 9 | { 10 | class DCLReadIndicator : IRelaExample 11 | { 12 | internal class HashedReadIndicator 13 | { 14 | private CLRAtomicLong[] _occupancyCounts; 15 | private int _paddingPower; 16 | private int _numEntries; 17 | 18 | public HashedReadIndicator(int sizePower, int paddingPower) 19 | { 20 | _numEntries = 1 << sizePower; 21 | int size = _numEntries << paddingPower; 22 | _occupancyCounts = new CLRAtomicLong[size]; 23 | _paddingPower = paddingPower; 24 | } 25 | 26 | private int GetIndex() 27 | { 28 | var threadId = Thread.CurrentThread.ManagedThreadId; 29 | var result = (threadId.GetHashCode() & (_numEntries - 1)) << _paddingPower; 30 | return result; 31 | } 32 | 33 | public void Arrive() 34 | { 35 | int index = GetIndex(); 36 | RInterlocked.Increment(ref _occupancyCounts[index]); 37 | } 38 | 39 | public void Depart() 40 | { 41 | int index = GetIndex(); 42 | RInterlocked.Decrement(ref _occupancyCounts[index]); 43 | } 44 | 45 | public bool IsOccupied 46 | { 47 | get 48 | { 49 | RInterlocked.Read(ref _occupancyCounts[0]); 50 | for (int i = 1; i < _numEntries; ++i) 51 | { 52 | if (RUnordered.Read(ref _occupancyCounts[i << _paddingPower]) > 0) 53 | { 54 | return true; 55 | } 56 | } 57 | Fence.Insert(MemoryOrder.Release); 58 | return false; 59 | } 60 | } 61 | } 62 | private static IRelaEngine RE = RelaEngine.RE; 63 | 64 | public string Name => "A distributed cache line read indicator."; 65 | 66 | public string Description => "1 writing threads, 2 reading threads"; 67 | 68 | public bool ExpectedToFail => true; // TODO: Revise this example and set to false. 69 | 70 | public IReadOnlyList ThreadEntries { get; } 71 | 72 | private bool _moreConfigurations = true; 73 | 74 | private int _numReading = 0; 75 | private HashedReadIndicator _readIndicator; 76 | 77 | public DCLReadIndicator() 78 | { 79 | ThreadEntries = new Action[]{ReadingThread,WritingThread}.ToList(); 80 | } 81 | 82 | public void OnBegin() 83 | { 84 | } 85 | 86 | public void OnFinished() 87 | { 88 | } 89 | 90 | private void WritingThread() 91 | { 92 | int numWrites = 3; 93 | for(int i = 0; i < numWrites; ++i) 94 | { 95 | while(_readIndicator.IsOccupied) 96 | { 97 | RE.Yield(); 98 | } 99 | RE.Assert(_numReading == 0, $"Write in progress but _numReading is {_numReading}"); 100 | } 101 | } 102 | 103 | private void ReadingThread() 104 | { 105 | int numReads = 3; 106 | for(int i = 0; i < numReads; ++i) 107 | { 108 | _readIndicator.Arrive(); 109 | _numReading++; 110 | _readIndicator.Depart(); 111 | _numReading--; 112 | } 113 | } 114 | 115 | public void PrepareForIteration() 116 | { 117 | _numReading = 0; 118 | _readIndicator = new HashedReadIndicator(4, 3); 119 | } 120 | public bool SetNextConfiguration() 121 | { 122 | var result = _moreConfigurations; 123 | _moreConfigurations = false; 124 | return result; 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /Examples/CLR/IPIReadWriteLock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using RelaSharp.CLR; 5 | 6 | namespace RelaSharp.Examples 7 | { 8 | public class IPIReadWriteLock : IRelaExample 9 | { 10 | class Snoop 11 | { 12 | private int _numReaders = 0; 13 | private bool _writeInProgress = false; 14 | public void BeginRead() 15 | { 16 | _numReaders++; 17 | RE.Assert(!_writeInProgress, $"Write in progress with {_numReaders} readers!"); 18 | } 19 | 20 | public void EndRead() 21 | { 22 | _numReaders--; 23 | } 24 | 25 | public void BeginWrite() 26 | { 27 | _writeInProgress = true; 28 | RE.Assert(_numReaders == 0, $"Write in progress with {_numReaders} readers!"); 29 | } 30 | 31 | public void EndWrite() 32 | { 33 | _writeInProgress = false; 34 | } 35 | } 36 | class IPIReadWriteLockInternal 37 | { 38 | private CLRAtomicInt[] _readIndicator; // In a real implementation this would be cache-line padded 39 | private ThreadLocal _thisReaderIndex; 40 | private CLRAtomicInt _nextReaderIndex; 41 | private CLRAtomicInt _writerActive; 42 | private Snoop _snoop = new Snoop(); 43 | public IPIReadWriteLockInternal(int numReaders) 44 | { 45 | _readIndicator = new CLRAtomicInt[numReaders + 1]; 46 | _thisReaderIndex = new ThreadLocal(); 47 | } 48 | 49 | private int GetReaderIndex() 50 | { 51 | if(_thisReaderIndex.IsValueCreated) 52 | { 53 | return _thisReaderIndex.Value; 54 | } 55 | var result = RInterlocked.Increment(ref _nextReaderIndex) - 1; 56 | _thisReaderIndex.Value = result; 57 | return result; 58 | } 59 | 60 | internal void EnterReadLock() 61 | { 62 | var idx = GetReaderIndex(); 63 | RUnordered.Write(ref _readIndicator[idx], 1); 64 | if(RUnordered.Read(ref _writerActive) == 1) 65 | { 66 | RUnordered.Write(ref _readIndicator[idx], 0); 67 | RMonitor.Enter(_lockObj); 68 | RUnordered.Write(ref _readIndicator[idx], 1); 69 | RMonitor.Exit(_lockObj); 70 | } 71 | _snoop.BeginRead(); 72 | } 73 | 74 | internal void EnterWriteLock() 75 | { 76 | RMonitor.Enter(_lockObj); 77 | RUnordered.Write(ref _writerActive, 1); 78 | RInterlocked.MemoryBarrierProcessWide(); 79 | while (ReadInProgress()) 80 | { 81 | RE.Yield(); 82 | } 83 | _snoop.BeginWrite(); 84 | return; 85 | } 86 | 87 | private bool ReadInProgress() 88 | { 89 | for(int i = 0; i < _readIndicator.Length; ++i) 90 | { 91 | if(RUnordered.Read(ref _readIndicator[i]) != 0) 92 | { 93 | return true; 94 | } 95 | } 96 | return false; 97 | } 98 | 99 | private object _lockObj = new object(); 100 | 101 | internal void ExitWriteLock() 102 | { 103 | RUnordered.Write(ref _writerActive, 0); 104 | _snoop.EndWrite(); 105 | RMonitor.Exit(_lockObj); 106 | } 107 | 108 | internal void ExitReadLock() 109 | { 110 | RUnordered.Write(ref _readIndicator[_thisReaderIndex.Value], 0); 111 | _snoop.EndRead(); 112 | } 113 | } 114 | public IReadOnlyList ThreadEntries { get; private set; } 115 | 116 | public string Name => "Read-write lock via interprocessor interrupt"; 117 | public string Description => "Uses Interlocked.MemoryBarrierProcessWide"; 118 | public bool ExpectedToFail => false; 119 | private static IRelaEngine RE = RelaEngine.RE; 120 | private bool _hasRun; 121 | private IPIReadWriteLockInternal _rwLock; 122 | 123 | public IPIReadWriteLock() 124 | { 125 | ThreadEntries = new List {Reader,Reader,Writer}; 126 | } 127 | 128 | public void PrepareForIteration() 129 | { 130 | PrepareForNewConfig(); 131 | } 132 | 133 | public void Reader() 134 | { 135 | for (int i = 0; i < 2; ++i) 136 | { 137 | _rwLock.EnterReadLock(); 138 | _rwLock.ExitReadLock(); 139 | } 140 | } 141 | 142 | public void Writer() 143 | { 144 | for (int i = 0; i < 2; ++i) 145 | { 146 | _rwLock.EnterWriteLock(); 147 | _rwLock.ExitWriteLock(); 148 | } 149 | } 150 | public void OnBegin() 151 | { 152 | } 153 | public void OnFinished() 154 | { 155 | } 156 | 157 | private void PrepareForNewConfig() 158 | { 159 | _rwLock = new IPIReadWriteLockInternal(2); 160 | } 161 | 162 | public bool SetNextConfiguration() 163 | { 164 | bool oldHasRun = _hasRun; 165 | PrepareForNewConfig(); 166 | _hasRun = true; 167 | return !oldHasRun; 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /Examples/CLR/IncorrectLeftRight.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using RelaSharp.CLR; 5 | 6 | namespace RelaSharp.Examples.CLR 7 | { 8 | public class IncorrectLeftRight : IRelaExample 9 | { 10 | class ExampleConfig : SimpleConfig 11 | { 12 | public bool WaitOnFirstWrite { get; } 13 | 14 | public ExampleConfig(string description, bool expectedToFail, bool waitOnFirstWrite) 15 | : base(description, MemoryOrder.Relaxed, expectedToFail) 16 | { 17 | WaitOnFirstWrite = waitOnFirstWrite; 18 | } 19 | } 20 | 21 | public IReadOnlyList ThreadEntries { get; private set; } 22 | 23 | public string Name => "Incorrect versions of the left-right readers-writers lock"; 24 | public string Description => ActiveConfig.Description; 25 | public bool ExpectedToFail => ActiveConfig.ExpectedToFail; 26 | private static IRelaEngine RE = RelaEngine.RE; 27 | 28 | private IEnumerator _configs; 29 | private ExampleConfig ActiveConfig => _configs.Current; 30 | 31 | class ReadIndicator 32 | { 33 | private CLRAtomicLong _numReaders; 34 | public void Arrive() 35 | { 36 | RInterlocked.Increment(ref _numReaders); 37 | } 38 | 39 | public void Depart() 40 | { 41 | RInterlocked.Decrement(ref _numReaders); 42 | } 43 | 44 | public bool IsEmpty => RInterlocked.Read(ref _numReaders) == 0; 45 | } 46 | 47 | class InstanceSnoop 48 | { 49 | private HashSet _reading = new HashSet(); 50 | private HashSet _writing = new HashSet(); 51 | 52 | public void BeginRead(long which) 53 | { 54 | RE.MaybeSwitch(); 55 | RE.Assert(!_writing.Contains(which), $"Write in progress during read at {which}"); 56 | _reading.Add(which); 57 | } 58 | 59 | public void EndRead(long which) 60 | { 61 | RE.MaybeSwitch(); 62 | _reading.Remove(which); 63 | RE.Assert(!_writing.Contains(which), $"Write in progress during read at {which}"); 64 | } 65 | 66 | public void BeginWrite(long which) 67 | { 68 | RE.MaybeSwitch(); 69 | RE.Assert(!_reading.Contains(which), $"Read in progress during write at {which}"); 70 | RE.Assert(!_writing.Contains(which), $"Write in progress during write at {which}"); 71 | _writing.Add(which); 72 | } 73 | 74 | public void EndWrite(long which) 75 | { 76 | RE.MaybeSwitch(); 77 | RE.Assert(!_reading.Contains(which), $"Write in progress during read at {which}"); 78 | _writing.Remove(which); 79 | } 80 | } 81 | 82 | class LeftRightLock 83 | { 84 | private readonly Object _writersMutex = new Object(); 85 | private ReadIndicator[] _readIndicator; 86 | private CLRAtomicLong _index; 87 | private InstanceSnoop _snoop = new InstanceSnoop(); 88 | 89 | // This property is used in test configurations to control whether the first write 90 | // waits for reads to finish on the next instance or not. When it is false, 91 | // mutual exclusion fails. When it is true, mutual exclusion also fails (but not as easily) 92 | // and writers can be starved by newly arriving readers. 93 | public bool WaitOnFirstWrite { get; set; } 94 | 95 | public LeftRightLock() 96 | { 97 | _readIndicator = new ReadIndicator[2]; 98 | _readIndicator[0] = new ReadIndicator(); 99 | _readIndicator[1] = new ReadIndicator(); 100 | } 101 | 102 | public U Read(T[] instances, Func read) 103 | { 104 | var index = RInterlocked.Read(ref _index); 105 | var readIndicator = _readIndicator[index]; 106 | readIndicator.Arrive(); 107 | try 108 | { 109 | _snoop.BeginRead(index); 110 | var result = read(instances[index]); 111 | _snoop.EndRead(index); 112 | return result; 113 | } 114 | finally 115 | { 116 | readIndicator.Depart(); 117 | } 118 | } 119 | 120 | public void Write(T[] instances, Action write) 121 | { 122 | RMonitor.Enter(_writersMutex); 123 | try 124 | { 125 | var index = RInterlocked.Read(ref _index); 126 | var nextIndex = Toggle(index); 127 | if(WaitOnFirstWrite) 128 | { 129 | WaitWhileOccupied(_readIndicator[nextIndex]); // Now we're subject to starvation by (new) readers. 130 | } // And mutual exclusion may still be violated. 131 | _snoop.BeginWrite(nextIndex); 132 | write(instances[nextIndex]); 133 | _snoop.EndWrite(nextIndex); 134 | 135 | // Move subsequent readers to 'next' instance 136 | RInterlocked.Exchange(ref _index, nextIndex); 137 | 138 | // Wait for all readers to finish reading the instance we want to write next 139 | WaitWhileOccupied(_readIndicator[index]); 140 | // At this point there may be readers, but they must be on nextReadIndex, we can 141 | // safely write. 142 | _snoop.BeginWrite(index); 143 | write(instances[index]); 144 | _snoop.EndWrite(index); 145 | } 146 | finally 147 | { 148 | RMonitor.Exit(_writersMutex); 149 | } 150 | } 151 | 152 | private static void WaitWhileOccupied(ReadIndicator readIndicator) 153 | { 154 | while (!readIndicator.IsEmpty) RE.Yield(); 155 | } 156 | private static long Toggle(long i) 157 | { 158 | return i;// ^ 1; 159 | } 160 | } 161 | 162 | private LeftRightLock _lrLock; 163 | private Dictionary[] _instances; 164 | 165 | 166 | public IncorrectLeftRight() 167 | { 168 | ThreadEntries = new List { ReadThread, WriteThread }; 169 | var configList = new List{new ExampleConfig("Wait for next instance on first write", true, true) 170 | ,new ExampleConfig("No wait for next instance on first write", true, false)}; 171 | _configs = configList.GetEnumerator(); 172 | } 173 | 174 | public void PrepareForIteration() 175 | { 176 | PrepareForNewConfig(); 177 | _lrLock.WaitOnFirstWrite = ActiveConfig.WaitOnFirstWrite; 178 | } 179 | 180 | public void ReadThread() 181 | { 182 | for(int i = 0; i < 1; ++i) 183 | { 184 | string message = null; 185 | bool read = _lrLock.Read(_instances, d => d.TryGetValue(i, out message)); 186 | } 187 | } 188 | 189 | public void WriteThread() 190 | { 191 | for(int i = 0; i < 1; ++i) 192 | { 193 | _lrLock.Write(_instances, d => d[i] = $"Wrote This: {i}"); 194 | } 195 | } 196 | public void OnBegin() 197 | { 198 | } 199 | public void OnFinished() 200 | { 201 | 202 | } 203 | private void SetupActiveConfig() 204 | { 205 | } 206 | 207 | private void PrepareForNewConfig() 208 | { 209 | _lrLock = new LeftRightLock(); 210 | _instances = new Dictionary[2]; 211 | _instances[0] = new Dictionary(); 212 | _instances[1] = new Dictionary(); 213 | } 214 | 215 | public bool SetNextConfiguration() 216 | { 217 | PrepareForNewConfig(); 218 | bool moreConfigurations = _configs.MoveNext(); 219 | if (ActiveConfig != null) 220 | { 221 | SetupActiveConfig(); 222 | } 223 | return moreConfigurations; 224 | } 225 | 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /Examples/CLR/SafeAsymmetricLock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using RelaSharp.CLR; 4 | 5 | namespace RelaSharp.Examples 6 | { 7 | public class SafeAsymmetricLock : IRelaExample 8 | { 9 | class SafeAsymmetricLockInternal 10 | { 11 | internal class LockCookie 12 | { 13 | internal LockCookie(int threadId) 14 | { 15 | ThreadId = threadId; 16 | RUnordered.Write(ref Taken, 1); 17 | } 18 | 19 | public void Exit() 20 | { 21 | RUnordered.Write(ref Taken, 0); 22 | } 23 | 24 | internal readonly int ThreadId; 25 | internal CLRAtomicInt Taken; 26 | } 27 | 28 | CLRAtomic _current; 29 | 30 | public SafeAsymmetricLockInternal() 31 | { 32 | } 33 | 34 | internal LockCookie Enter() 35 | { 36 | int currentThreadId = Environment.CurrentManagedThreadId; 37 | 38 | LockCookie entry = RUnordered.Read(ref _current); 39 | 40 | if (entry?.ThreadId == currentThreadId) 41 | { 42 | RUnordered.Write(ref entry.Taken, 1); 43 | 44 | if (RVolatile.Read(ref _current) == entry) 45 | { 46 | return entry; 47 | } 48 | RUnordered.Write(ref entry.Taken, 0); 49 | } 50 | return EnterSlow(); 51 | } 52 | 53 | private object _lockObj = new object(); 54 | 55 | private LockCookie EnterSlow() 56 | { 57 | RMonitor.Enter(_lockObj); 58 | var oldEntry = RUnordered.Read(ref _current); 59 | RUnordered.Write(ref _current, new LockCookie(Environment.CurrentManagedThreadId)); 60 | RInterlocked.MemoryBarrierProcessWide(); 61 | while (oldEntry != null && RUnordered.Read(ref oldEntry.Taken) == 1) 62 | { 63 | RE.Yield(); 64 | } 65 | var current = RUnordered.Read(ref _current); 66 | RUnordered.Write(ref current.Taken, 1); 67 | RMonitor.Exit(_lockObj); 68 | return current; 69 | } 70 | } 71 | public IReadOnlyList ThreadEntries { get; private set; } 72 | 73 | public string Name => "Quickly re-acquirable lock via interprocessor interrupt ('safe' version, only releasable by holding thread)"; 74 | public string Description => "Uses Interlocked.MemoryBarrierProcessWide"; 75 | public bool ExpectedToFail => false; 76 | private static IRelaEngine RE = RelaEngine.RE; 77 | private int _threadsPassed; 78 | private bool _hasRun; 79 | private SafeAsymmetricLockInternal _asymLock; 80 | 81 | public SafeAsymmetricLock() 82 | { 83 | ThreadEntries = new List {Thread0,Thread1}; 84 | } 85 | 86 | public void PrepareForIteration() 87 | { 88 | PrepareForNewConfig(); 89 | } 90 | 91 | public void Thread0() 92 | { 93 | for (int i = 0; i < 2; ++i) 94 | { 95 | var c = _asymLock.Enter(); 96 | RE.Assert(_threadsPassed == 0, $"Iteration {i}: Thread0 entered while Thread1 in critical section! ({_threadsPassed})"); 97 | _threadsPassed++; 98 | c.Exit(); 99 | _threadsPassed--; 100 | } 101 | } 102 | 103 | public void Thread1() 104 | { 105 | for (int i = 0; i < 2; ++i) 106 | { 107 | var c = _asymLock.Enter(); 108 | RE.Assert(_threadsPassed == 0, $"Iteration {i}: Thread1 entered while Thread0 in critical section! ({_threadsPassed})"); 109 | _threadsPassed++; 110 | c.Exit(); 111 | _threadsPassed--; 112 | } 113 | } 114 | public void OnBegin() 115 | { 116 | } 117 | public void OnFinished() 118 | { 119 | } 120 | 121 | private void PrepareForNewConfig() 122 | { 123 | _threadsPassed = 0; 124 | _asymLock = new SafeAsymmetricLockInternal(); 125 | } 126 | 127 | public bool SetNextConfiguration() 128 | { 129 | bool oldHasRun = _hasRun; 130 | PrepareForNewConfig(); 131 | _hasRun = true; 132 | return !oldHasRun; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Examples/CLR/SingleCounterReadIndicator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Linq; 5 | 6 | using RelaSharp.CLR; 7 | 8 | namespace RelaSharp.Examples 9 | { 10 | class SingleCounterReadIndicator : IRelaExample 11 | { 12 | class ReadIndicator 13 | { 14 | private CLRAtomicLong _numReaders; 15 | public void Arrive() 16 | { 17 | RInterlocked.Increment(ref _numReaders); 18 | } 19 | 20 | public void Depart() 21 | { 22 | RInterlocked.Decrement(ref _numReaders); 23 | } 24 | 25 | public bool IsEmpty => RInterlocked.Read(ref _numReaders) == 0; 26 | } 27 | 28 | private static IRelaEngine RE = RelaEngine.RE; 29 | 30 | public string Name => "A read-indicator implemented as single counter"; 31 | 32 | public string Description => "1 writing threads, 2 reading threads"; 33 | 34 | public bool ExpectedToFail => false; 35 | 36 | public IReadOnlyList ThreadEntries { get; } 37 | 38 | private bool _moreConfigurations = true; 39 | 40 | private int _numReading = 0; 41 | private ReadIndicator _readIndicator; 42 | 43 | public SingleCounterReadIndicator() 44 | { 45 | ThreadEntries = new Action[]{ReadingThread,WritingThread}.ToList(); 46 | } 47 | 48 | public void OnBegin() 49 | { 50 | } 51 | 52 | public void OnFinished() 53 | { 54 | } 55 | 56 | private void WritingThread() 57 | { 58 | int numWrites = 3; 59 | for(int i = 0; i < numWrites; ++i) 60 | { 61 | while(!_readIndicator.IsEmpty) 62 | { 63 | RE.Yield(); 64 | } 65 | RE.Assert(_numReading == 0, $"Write in progress but _numReading is {_numReading}"); 66 | } 67 | } 68 | 69 | private void ReadingThread() 70 | { 71 | int numReads = 3; 72 | for(int i = 0; i < numReads; ++i) 73 | { 74 | _readIndicator.Arrive(); 75 | _numReading++; 76 | _readIndicator.Depart(); 77 | _numReading--; 78 | } 79 | } 80 | 81 | public void PrepareForIteration() 82 | { 83 | _numReading = 0; 84 | _readIndicator = new ReadIndicator(); 85 | 86 | } 87 | public bool SetNextConfiguration() 88 | { 89 | var result = _moreConfigurations; 90 | _moreConfigurations = false; 91 | return result; 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /Examples/CLR/StarvationLeftRight.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using RelaSharp.CLR; 5 | 6 | namespace RelaSharp.Examples.CLR 7 | { 8 | public class StarvationLeftRight : IRelaExample 9 | { 10 | public IReadOnlyList ThreadEntries { get; private set; } 11 | 12 | public string Name => "Correct Left-Right implementation that allows writer starvation"; 13 | public string Description => ActiveConfig.Description; 14 | public bool ExpectedToFail => ActiveConfig.ExpectedToFail; 15 | private static IRelaEngine RE = RelaEngine.RE; 16 | 17 | private IEnumerator _configs; 18 | private SimpleConfig ActiveConfig => _configs.Current; 19 | class ReadIndicator 20 | { 21 | private CLRAtomicLong _numReaders; 22 | public void Arrive() 23 | { 24 | RInterlocked.Increment(ref _numReaders); 25 | } 26 | 27 | public void Depart() 28 | { 29 | RInterlocked.Decrement(ref _numReaders); 30 | } 31 | 32 | public bool IsEmpty => RInterlocked.Read(ref _numReaders) == 0; 33 | } 34 | 35 | class InstanceSnoop 36 | { 37 | private HashSet _reading = new HashSet(); 38 | private HashSet _writing = new HashSet(); 39 | 40 | public void BeginRead(long which) 41 | { 42 | RE.MaybeSwitch(); 43 | RE.Assert(!_writing.Contains(which), $"Write in progress during read at {which}"); 44 | _reading.Add(which); 45 | } 46 | 47 | public void EndRead(long which) 48 | { 49 | RE.MaybeSwitch(); 50 | _reading.Remove(which); 51 | RE.Assert(!_writing.Contains(which), $"Write in progress during read at {which}"); 52 | } 53 | 54 | public void BeginWrite(long which) 55 | { 56 | RE.MaybeSwitch(); 57 | RE.Assert(!_reading.Contains(which), $"Read in progress during write at {which}"); 58 | RE.Assert(!_writing.Contains(which), $"Write in progress during write at {which}"); 59 | _writing.Add(which); 60 | } 61 | 62 | public void EndWrite(long which) 63 | { 64 | RE.MaybeSwitch(); 65 | RE.Assert(!_reading.Contains(which), $"Write in progress during read at {which}"); 66 | _writing.Remove(which); 67 | } 68 | } 69 | 70 | class LeftRightLock 71 | { 72 | private readonly Object _writersMutex = new Object(); 73 | private ReadIndicator _readIndicator; 74 | private CLRAtomicLong _readIndex; 75 | private InstanceSnoop _snoop = new InstanceSnoop(); 76 | 77 | public LeftRightLock() 78 | { 79 | _readIndicator = new ReadIndicator(); 80 | } 81 | 82 | public U Read(T[] instances, Func read) 83 | { 84 | _readIndicator.Arrive(); 85 | try 86 | { 87 | var idx = RInterlocked.Read(ref _readIndex); 88 | _snoop.BeginRead(idx); 89 | var result = read(instances[idx]); 90 | _snoop.EndRead(idx); 91 | return result; 92 | } 93 | finally 94 | { 95 | _readIndicator.Depart(); 96 | } 97 | } 98 | 99 | public void Write(T[] instances, Action write) 100 | { 101 | RMonitor.Enter(_writersMutex); 102 | try 103 | { 104 | var readIndex = RInterlocked.Read(ref _readIndex); 105 | var nextReadIndex = Toggle(readIndex); 106 | _snoop.BeginWrite(nextReadIndex); 107 | write(instances[nextReadIndex]); 108 | _snoop.EndWrite(nextReadIndex); 109 | RInterlocked.Exchange(ref _readIndex, nextReadIndex); 110 | WaitWhileOccupied(_readIndicator); 111 | _snoop.BeginWrite(readIndex); 112 | write(instances[readIndex]); 113 | _snoop.EndWrite(readIndex); 114 | } 115 | finally 116 | { 117 | RMonitor.Exit(_writersMutex); 118 | } 119 | } 120 | 121 | private static void WaitWhileOccupied(ReadIndicator readIndicator) 122 | { 123 | while (!readIndicator.IsEmpty) ; 124 | } 125 | private static long Toggle(long i) 126 | { 127 | return i ^ 1; 128 | } 129 | } 130 | 131 | private LeftRightLock _lrLock; 132 | private Dictionary[] _instances; 133 | public StarvationLeftRight() 134 | { 135 | ThreadEntries = new List { ReadThread, ReadThread, WriteThread, WriteThread }; 136 | var configList = new List{new SimpleConfig("Seq-cst operations", MemoryOrder.Relaxed, false)}; 137 | _configs = configList.GetEnumerator(); 138 | } 139 | 140 | public void PrepareForIteration() 141 | { 142 | PrepareForNewConfig(); 143 | } 144 | 145 | public void ReadThread() 146 | { 147 | for(int i = 0; i < 5; ++i) 148 | { 149 | string message = null; 150 | bool read = _lrLock.Read(_instances, d => d.TryGetValue(i, out message)); 151 | } 152 | } 153 | 154 | public void WriteThread() 155 | { 156 | for(int i = 0; i < 5; ++i) 157 | { 158 | _lrLock.Write(_instances, d => d[i] = $"Wrote This: {i}"); 159 | } 160 | } 161 | public void OnBegin() 162 | { 163 | } 164 | public void OnFinished() 165 | { 166 | 167 | } 168 | private void SetupActiveConfig() 169 | { 170 | } 171 | 172 | private void PrepareForNewConfig() 173 | { 174 | _lrLock = new LeftRightLock(); 175 | _instances = new Dictionary[2]; 176 | _instances[0] = new Dictionary(); 177 | _instances[1] = new Dictionary(); 178 | } 179 | 180 | public bool SetNextConfiguration() 181 | { 182 | PrepareForNewConfig(); 183 | bool moreConfigurations = _configs.MoveNext(); 184 | if (ActiveConfig != null) 185 | { 186 | SetupActiveConfig(); 187 | } 188 | return moreConfigurations; 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /Examples/Deadlock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using RelaSharp.CLR; 4 | 5 | namespace RelaSharp.Examples 6 | { 7 | public class Deadlock : IRelaExample 8 | { 9 | public IReadOnlyList ThreadEntries { get; private set; } 10 | 11 | public string Name => "Deadlock detection example"; 12 | public string Description => ActiveConfig.Description; 13 | public bool ExpectedToFail => ActiveConfig.ExpectedToFail; 14 | 15 | private IEnumerator _configs; 16 | private Config ActiveConfig => _configs.Current; 17 | 18 | private class Config 19 | { 20 | public readonly string Description; 21 | public readonly int NumThreads; 22 | public readonly bool ExpectedToFail; 23 | 24 | public object[] LockObjects; 25 | public Config(string description, int numThreads, bool expectedToFail) 26 | { 27 | Description = description; 28 | NumThreads = numThreads; 29 | ExpectedToFail = expectedToFail; 30 | LockObjects = new Object[numThreads]; 31 | for(int i = 0; i < numThreads; ++i) 32 | { 33 | LockObjects[i] = new Object(); 34 | } 35 | } 36 | } 37 | 38 | public Deadlock() 39 | { 40 | var configList = new List{new Config("1 thread: deadlock impossible", 1, false), 41 | new Config("2 threads, with possible cyclic wait: deadlock expected", 2, true), 42 | new Config("4 threads, with possible cyclic wait: deadlock expected", 4, true), 43 | new Config("8 threads, with possible cyclic wait: deadlock expected", 8, true) 44 | }; 45 | _configs = configList.GetEnumerator(); 46 | } 47 | 48 | public void PrepareForIteration() 49 | { 50 | PrepareForNewConfig(); 51 | } 52 | 53 | public void OnBegin() 54 | { 55 | } 56 | public void OnFinished() 57 | { 58 | } 59 | 60 | private void PrepareForNewConfig() 61 | { 62 | } 63 | 64 | private Action MakeThread(int idx) 65 | { 66 | return () => LockingThread(idx); 67 | } 68 | 69 | private void LockingThread(int idx) 70 | { 71 | var myLock = ActiveConfig.LockObjects[idx]; 72 | var nextLock = ActiveConfig.LockObjects[(idx + 1) % ActiveConfig.NumThreads]; 73 | RMonitor.Enter(myLock); 74 | RMonitor.Enter(nextLock); 75 | RMonitor.Exit(nextLock); 76 | RMonitor.Exit(myLock); 77 | } 78 | 79 | private void SetupActiveConfig() 80 | { 81 | var threadEntries = new List(); 82 | for(int i = 0; i < ActiveConfig.NumThreads; ++i) 83 | { 84 | threadEntries.Add(MakeThread(i)); 85 | } 86 | ThreadEntries = threadEntries; 87 | } 88 | 89 | public bool SetNextConfiguration() 90 | { 91 | PrepareForNewConfig(); 92 | bool moreConfigurations = _configs.MoveNext(); 93 | if(ActiveConfig != null) 94 | { 95 | SetupActiveConfig(); 96 | } 97 | return moreConfigurations; 98 | } 99 | } 100 | 101 | } -------------------------------------------------------------------------------- /Examples/EntryPoint/Options.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace RelaSharp.EntryPoint 7 | { 8 | class Options 9 | { 10 | public enum SchedulingAlgorithm 11 | { 12 | Random, 13 | Exhaustive 14 | } 15 | private const int DefaultIterations = 10000; 16 | private const SchedulingAlgorithm DefaultScheduling = SchedulingAlgorithm.Random; 17 | private const ulong DefaultLiveLockLimit = 5000; 18 | private const int DefaultYieldLookbackPenalty = 4; 19 | public bool Help; 20 | public readonly bool QuietMode; 21 | public readonly bool SelfTest; 22 | public readonly string TestTag; 23 | public readonly int Iterations; 24 | public readonly bool ListExamples; 25 | public readonly SchedulingAlgorithm Scheduling; 26 | public readonly ulong LiveLockLimit; 27 | public readonly int YieldLookbackPenalty; 28 | 29 | public Options(bool help, bool quietMode, bool selfTest, string testTag, int iterations, bool listExamples, SchedulingAlgorithm scheduling, ulong liveLockLimit, int yieldLookbackPenalty) 30 | { 31 | Help = help; 32 | QuietMode = quietMode; 33 | SelfTest = selfTest; 34 | TestTag = testTag; 35 | Iterations = iterations; 36 | ListExamples = listExamples; 37 | Scheduling = scheduling; 38 | LiveLockLimit = liveLockLimit; 39 | YieldLookbackPenalty = yieldLookbackPenalty; 40 | } 41 | 42 | public static Options GetOptions(string[] args) 43 | { 44 | Func takeTrim = (arg, idx) => arg.Contains('=') ? arg.Split('=')[idx].Trim() : arg.Trim(); 45 | var argMap = args.ToDictionary(a => takeTrim(a, 0), a => takeTrim(a, 1)); 46 | bool quietMode = argMap.ContainsKey("--quiet"); 47 | int iterations = GetOptionValue("--iterations", argMap, Int32.Parse, DefaultIterations, Console.Error); 48 | ulong liveLockLimit = GetOptionValue("--live-lock", argMap, UInt64.Parse, DefaultLiveLockLimit, Console.Error); 49 | int yieldLookbackPenalty = GetOptionValue("--yield-penalty", argMap, Int32.Parse, DefaultYieldLookbackPenalty, Console.Error); 50 | 51 | 52 | bool selfTest = argMap.ContainsKey("--self-test"); 53 | string testTag = GetOptionValue("--tag", argMap, s => s, null, Console.Error); 54 | 55 | bool listExamples = argMap.ContainsKey("--list-examples"); 56 | bool help = argMap.ContainsKey("--help"); 57 | SchedulingAlgorithm scheduling = GetOptionValue("--scheduling", argMap, s => (SchedulingAlgorithm) Enum.Parse(typeof(SchedulingAlgorithm), s, true), DefaultScheduling, Console.Error); 58 | return new Options(help, quietMode, selfTest, testTag, iterations, listExamples, scheduling, liveLockLimit, yieldLookbackPenalty); 59 | } 60 | 61 | private static T GetOptionValue(string option, Dictionary argMap, Func getValue, T defaultValue, TextWriter output) 62 | { 63 | string optionValue; 64 | if (!argMap.TryGetValue(option, out optionValue)) 65 | { 66 | return defaultValue; 67 | } 68 | try 69 | { 70 | return getValue(optionValue); 71 | } 72 | catch (Exception) 73 | { 74 | output.WriteLine($"Error parsing option {option} using {defaultValue} instead."); 75 | return defaultValue; 76 | } 77 | } 78 | 79 | public static string GetHelp() 80 | { 81 | var allOptions = new Dictionary { {"--quiet", "Suppress output of execution logs (defaults to false)"}, 82 | {"--iterations=X", $"For random scheduler only: Run for X iterations (defaults to {DefaultIterations})"}, 83 | {"--self-test", "Run self test mode (suppress all output and only report results that differ from expected results)"}, 84 | {"--tag=X", "Run examples whose name contain the tag (case insensitive, run all examples if unspecified)"}, 85 | {"--scheduling=X", $"Use the specified scheduling algorithm, available options are 'random' and 'exhaustive' (defaults to {DefaultScheduling})"}, 86 | {"--live-lock=X", $"Report executions longer than X as live locks (defaults to {DefaultLiveLockLimit})"}, 87 | {"--list-examples", "List the tags of the available examples with their full names (and then exit)."}, 88 | {"--yield-penalty=X", $"Exhaustive scheduler: Control the chance of a false-divergent execution, larger implies closer to sequential consistency close to scheduler yields (defaults to {DefaultYieldLookbackPenalty})"}, 89 | {"--help", "Print this message and exit"} 90 | }; 91 | var result = String.Join(Environment.NewLine, allOptions.Select(kvp => $"{kvp.Key}\r\t\t\t{kvp.Value}")); 92 | return result; 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /Examples/EntryPoint/RunExamples.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | using System.Linq; 4 | using System.Diagnostics; 5 | using RelaSharp.Examples; 6 | using RelaSharp.Examples.CLR; 7 | using RelaSharp.Scheduling; 8 | 9 | namespace RelaSharp.EntryPoint 10 | { 11 | public class RunExamples 12 | { 13 | public static void Main(string[] args) 14 | { 15 | var options = Options.GetOptions(args); 16 | if(args.Length == 0 || options.Help) 17 | { 18 | Console.WriteLine("Usage:"); 19 | Console.WriteLine(Options.GetHelp()); 20 | return; 21 | } 22 | RelaEngine.Mode = EngineMode.Test; 23 | Func> c = (s, i) => Tuple.Create(s, i); 24 | var examples = new Tuple[]{c("SimpleAcquireRelease", new SimpleAcquireRelease()), 25 | c("StoreLoad", new StoreLoad()), 26 | c("SPSC", new BoundedSPSCQueue()), 27 | c("Petersen", new Petersen()), 28 | c("TotalOrder", new TotalOrder()), 29 | c("TransitiveLastSeen", new TransitiveLastSeen()), 30 | c("SimpleTransitive", new SimpleTransitive()), 31 | c("RelaxedModOrder", new RelaxedModificationOrder()), 32 | c("LiveLock", new LiveLock()), 33 | c("Treiber", new TreiberStack()), 34 | c("MichaelScott", new MichaelScottQueue()), 35 | c("Deadlock", new Deadlock()), 36 | c("LostWakeUp", new LostWakeUp()), 37 | c("CorrectLeftRight", new LeftRight()), 38 | c("BuggyLeftRight", new IncorrectLeftRight()), 39 | c("StarvationLeftRight", new StarvationLeftRight()), 40 | c("MultiWriterCOWList", new COWList()), 41 | c("SimpleReadIndicator", new SingleCounterReadIndicator()), 42 | c("DCLReadIndicator", new DCLReadIndicator()), 43 | c("AsymmetricPetersen", new AsymmetricPetersen()), 44 | c("AsymmetricLock", new AsymmetricLock()), 45 | c("SafeAsymmetricLock", new SafeAsymmetricLock()), 46 | c("MinimalIPI", new MinimalIPI()), 47 | c("IPIReadWriteLock", new IPIReadWriteLock()) }; 48 | if(options.ListExamples) 49 | { 50 | Console.WriteLine("Available examples:"); 51 | Console.WriteLine("-------------------"); 52 | Console.WriteLine(String.Join("\n", examples.Select(e => $"{e.Item1}\r\t\t\t{e.Item2.Name}"))); 53 | return; 54 | } 55 | for(int i = 0; i < examples.Length; ++i) 56 | { 57 | var tag = examples[i].Item1; 58 | var example = examples[i].Item2; 59 | if(options.TestTag == null || Regex.Match(tag, options.TestTag, RegexOptions.IgnoreCase) != Match.Empty) 60 | { 61 | if(!options.SelfTest) 62 | { 63 | Console.WriteLine($"Running example [{i + 1}/{examples.Length}]: {tag}: {example.Name}"); 64 | } 65 | RunExample(tag, example, options); 66 | } 67 | } 68 | } 69 | 70 | private static void RunExample(string exampleTag, IRelaExample example, Options options) 71 | { 72 | var TR = TestRunner.TR; 73 | while (example.SetNextConfiguration()) 74 | { 75 | if(!options.SelfTest) 76 | { 77 | var expectedResult = example.ExpectedToFail ? "fail" : "pass"; 78 | Console.WriteLine($"***** Current configuration for '{example.Name}' is '{example.Description}', this is expected to {expectedResult}"); 79 | } 80 | var sw = new Stopwatch(); 81 | sw.Start(); 82 | int numIterations = 0; 83 | ulong totalOperations = 0; 84 | bool testFailed = false; 85 | IScheduler scheduler; 86 | example.PrepareForIteration(); 87 | int numThreads = example.ThreadEntries.Count; 88 | switch(options.Scheduling) 89 | { 90 | case Options.SchedulingAlgorithm.Random: 91 | scheduler = new NaiveRandomScheduler(numThreads, options.Iterations); 92 | break; 93 | case Options.SchedulingAlgorithm.Exhaustive: 94 | scheduler = new ExhaustiveScheduler(numThreads, options.LiveLockLimit * 2, options.YieldLookbackPenalty); 95 | break; 96 | default: 97 | throw new Exception($"Unsupported scheduling algorithm '{options.Scheduling}'"); 98 | } 99 | while(scheduler.NewIteration() && !testFailed) 100 | { 101 | example.PrepareForIteration(); 102 | TR.RunTest(example, scheduler, options.LiveLockLimit); 103 | testFailed = TR.TestFailed; 104 | totalOperations += TR.ExecutionLength; 105 | ++numIterations; 106 | } 107 | var panic = "*\n*\n*\n*\n*\n*\n*\n*"; 108 | if (TR.TestFailed) 109 | { 110 | if(options.SelfTest) 111 | { 112 | if(!example.ExpectedToFail) 113 | { 114 | Console.WriteLine($"{exampleTag}['{example.Description}'] expected to pass but failed on iteration number {numIterations}."); 115 | } 116 | } 117 | else 118 | { 119 | Console.WriteLine($"Example failed on iteration number: {numIterations}"); 120 | Console.WriteLine(example.ExpectedToFail ? "Not to worry, this failure was expected" : $"{panic}\tUh-oh: This example was expected to pass.\n{panic}"); 121 | } 122 | if(!options.QuietMode && !options.SelfTest) 123 | { 124 | TR.DumpExecutionLog(Console.Out); 125 | } 126 | } 127 | else 128 | { 129 | if(options.SelfTest) 130 | { 131 | if(example.ExpectedToFail) 132 | { 133 | Console.WriteLine($"{exampleTag}['{example.Description}'] expected to fail but survived {numIterations} iterations."); 134 | } 135 | } 136 | else 137 | { 138 | Console.WriteLine($"No failures after {numIterations} iterations"); 139 | Console.WriteLine(example.ExpectedToFail ? $"{panic}\tUh-oh: This example was expected to fail.\n{panic}" : "That's good, this example was expected to pass."); 140 | } 141 | } 142 | if(!options.SelfTest) 143 | { 144 | var elapsed = sw.Elapsed.TotalSeconds; 145 | Console.WriteLine($"Tested {totalOperations / elapsed:F3} operations per second ({numIterations} iterations at {(numIterations) / elapsed:F3} iterations per second) for {elapsed} seconds."); 146 | Console.WriteLine(".........................."); 147 | } 148 | } 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Examples/Examples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exe 9 | netcoreapp2.0 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Examples/IRelaExample.cs: -------------------------------------------------------------------------------- 1 | namespace RelaSharp.Examples 2 | { 3 | interface IRelaExample : IRelaTest 4 | { 5 | string Name { get; } 6 | string Description { get; } 7 | bool ExpectedToFail { get;} 8 | bool SetNextConfiguration(); 9 | void PrepareForIteration(); 10 | } 11 | } -------------------------------------------------------------------------------- /Examples/LiveLock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RelaSharp.Examples 5 | { 6 | public class LiveLock : IRelaExample 7 | { 8 | public IReadOnlyList ThreadEntries { get; private set; } 9 | 10 | public string Name => "Livelock example (fragment of Petersen lock)"; 11 | public string Description => "All operations sequentially consistent"; 12 | public bool ExpectedToFail => true; 13 | private static IRelaEngine RE = RelaEngine.RE; 14 | private Atomic interested0; 15 | private Atomic interested1; 16 | private bool _hasRun; 17 | 18 | public LiveLock() 19 | { 20 | ThreadEntries = new List {Thread1,Thread2}; 21 | } 22 | 23 | public void PrepareForIteration() 24 | { 25 | PrepareForNewConfig(); 26 | } 27 | 28 | public void Thread1() 29 | { 30 | interested0.Store(1, MemoryOrder.SequentiallyConsistent); 31 | while(interested1.Load(MemoryOrder.SequentiallyConsistent) == 1) RE.Yield(); 32 | interested0.Store(0, MemoryOrder.SequentiallyConsistent); 33 | } 34 | 35 | public void Thread2() 36 | { 37 | interested1.Store(1, MemoryOrder.SequentiallyConsistent); 38 | while(interested0.Load(MemoryOrder.SequentiallyConsistent) == 1) RE.Yield(); 39 | interested1.Store(0, MemoryOrder.SequentiallyConsistent); 40 | } 41 | public void OnBegin() 42 | { 43 | } 44 | public void OnFinished() 45 | { 46 | } 47 | 48 | private void PrepareForNewConfig() 49 | { 50 | interested0 = new Atomic(); 51 | interested1 = new Atomic(); 52 | } 53 | 54 | public bool SetNextConfiguration() 55 | { 56 | bool oldHasRun = _hasRun; 57 | PrepareForNewConfig(); 58 | _hasRun = true; 59 | return !oldHasRun; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Examples/LostWakeUp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using RelaSharp.CLR; 4 | 5 | namespace RelaSharp.Examples 6 | { 7 | public class LostWakeUp : IRelaExample 8 | { 9 | public IReadOnlyList ThreadEntries { get; private set; } 10 | 11 | public string Name => "Lost Wake Up example"; 12 | public string Description => ActiveConfig.Description; 13 | public bool ExpectedToFail => ActiveConfig.ExpectedToFail; 14 | 15 | private object _lockObject; 16 | 17 | private IEnumerator _configs; 18 | private Config ActiveConfig => _configs.Current; 19 | 20 | private class Config 21 | { 22 | public readonly string Description; 23 | public readonly int NumPulses; 24 | public readonly int NumWaitingThreads; 25 | public readonly bool ExpectedToFail; 26 | 27 | public Config(string description, int numPulses, int numWaitingThreads, bool expectedToFail) 28 | { 29 | Description = description; 30 | NumPulses = numPulses; 31 | NumWaitingThreads = numWaitingThreads; 32 | ExpectedToFail = expectedToFail; 33 | } 34 | } 35 | 36 | public LostWakeUp() 37 | { 38 | var configList = new List{new Config("Lost Wake Up: 1 Pulse, 1 Waiting Thread", 1, 1, true) 39 | ,new Config("Lost Wake Up: 4 Pulses, 1 Waiting Thread", 4, 1, true) 40 | ,new Config("Lost Wake Up: 4 Pulses, 4 Waiting Thread", 4, 4, true) 41 | }; 42 | _configs = configList.GetEnumerator(); 43 | } 44 | 45 | public void PrepareForIteration() 46 | { 47 | PrepareForNewConfig(); 48 | } 49 | 50 | private Action MakePulsingThread(int numPulses) 51 | { 52 | return () => PulsingThread(numPulses); 53 | } 54 | 55 | private Action MakeWaitingThread() 56 | { 57 | return () => WaitingThread(); 58 | } 59 | 60 | private void PulsingThread(int numPulses) 61 | { 62 | for (int i = 0; i < numPulses; ++i) 63 | { 64 | RMonitor.Enter(_lockObject); 65 | RMonitor.Pulse(_lockObject); 66 | RMonitor.Exit(_lockObject); 67 | } 68 | } 69 | 70 | public void WaitingThread() 71 | { 72 | RMonitor.Enter(_lockObject); 73 | RMonitor.Wait(_lockObject); 74 | RMonitor.Exit(_lockObject); 75 | } 76 | 77 | 78 | public void OnBegin() 79 | { 80 | } 81 | public void OnFinished() 82 | { 83 | } 84 | 85 | private void SetupActiveConfig() 86 | { 87 | var threadEntries = new List() { MakePulsingThread(ActiveConfig.NumPulses) }; 88 | for (int i = 0; i < ActiveConfig.NumWaitingThreads; ++i) 89 | { 90 | threadEntries.Add(MakeWaitingThread()); 91 | } 92 | ThreadEntries = threadEntries; 93 | } 94 | 95 | private void PrepareForNewConfig() 96 | { 97 | _lockObject = new object(); 98 | } 99 | 100 | public bool SetNextConfiguration() 101 | { 102 | PrepareForNewConfig(); 103 | bool moreConfigurations = _configs.MoveNext(); 104 | if (ActiveConfig != null) 105 | { 106 | SetupActiveConfig(); 107 | } 108 | return moreConfigurations; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Examples/MinimalIPI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RelaSharp.Examples 5 | { 6 | public class MinimalIPI : IRelaExample 7 | { 8 | public IReadOnlyList ThreadEntries { get; private set; } 9 | 10 | public string Name => "Minimial Interprocessor Interrupt demonstration"; 11 | public string Description => "Uses Interlocked.MemoryBarrierProcessWide (FlushProcessWriteBuffers / mprotect)"; 12 | public bool ExpectedToFail => false; 13 | private static IRelaEngine RE = RelaEngine.RE; 14 | private Atomic A; 15 | private Atomic B; 16 | private bool _fastStoreDone; 17 | private bool _serializeDone; 18 | private bool _hasRun; 19 | 20 | public MinimalIPI() 21 | { 22 | ThreadEntries = new List {FastThread,SlowThread}; 23 | } 24 | 25 | public void PrepareForIteration() 26 | { 27 | PrepareForNewConfig(); 28 | } 29 | /* 30 | From Dice et al: 31 | 32 | First, we should more precisely state the requirements for SERIALIZE(t). 33 | Lets say the "Fast thread" F executes {ST A ; LD B} and the 34 | "Slow thread" S executes {ST B; MEMBAR; SERIALIZE(F); LD A;}. 35 | Typically, F would act as the BHT or JNI mutator, while 36 | S would act in the role of the bias revoker or garbage collector. 37 | 38 | When Serialize(F) returns, one of the following invariants must hold: 39 | 40 | (A) If F has completed the {ST A} operation, then the value STed by 41 | F into A will be visible to S by the time SERIALIZE(t) returns. 42 | That is, the {LD A} executed by S will observe the value STed 43 | into A by F. 44 | 45 | (B) If F has yet to complete the {ST A} operation, then 46 | when F executes {LD B}, F will observe the value STed 47 | into B by S. 48 | */ 49 | 50 | // It seems to me that a simpler litmus test would just be: For both threads, upon completion, 51 | // if the other thread has completed its store, then that store must be visible to this thread. 52 | public void SlowThread() 53 | { 54 | B.Store(1, MemoryOrder.Relaxed); 55 | Fence.InsertProcessWide(); 56 | _serializeDone = true; 57 | var caseA = _fastStoreDone; // (A) Serialize done and F has completed its store => Must see F's store. 58 | if(caseA) 59 | { 60 | var seen = A.Load(MemoryOrder.Relaxed); 61 | RE.Assert(seen == 1, "Should see value stored by slow thread if it has done its store by the time serialize done."); 62 | } 63 | } 64 | 65 | public void FastThread() 66 | { 67 | var caseB = _serializeDone; // (B) Serialize done and F has yet to complete its store => Must see S's store (because that store was before serialize) 68 | A.Store(1, MemoryOrder.Relaxed); 69 | _fastStoreDone = true; 70 | if(caseB) 71 | { 72 | var seen = B.Load(MemoryOrder.Relaxed); 73 | RE.Assert(seen == 1, "Should see value stored by slow thread if it has stored & serialized before my (FastThread) store."); 74 | } 75 | } 76 | 77 | public void OnBegin() 78 | { 79 | } 80 | public void OnFinished() 81 | { 82 | } 83 | 84 | private void PrepareForNewConfig() 85 | { 86 | A = new Atomic(); 87 | B = new Atomic(); 88 | _fastStoreDone = false; 89 | _serializeDone = false; 90 | } 91 | 92 | public bool SetNextConfiguration() 93 | { 94 | bool oldHasRun = _hasRun; 95 | PrepareForNewConfig(); 96 | _hasRun = true; 97 | return !oldHasRun; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Examples/Petersen.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RelaSharp.Examples 5 | { 6 | class Config 7 | { 8 | public readonly string Description; 9 | public readonly MemoryOrder MemoryOrder; 10 | public readonly bool UseExchange; 11 | public readonly bool ExpectedToFail; 12 | 13 | public Config(string description, MemoryOrder memoryOrder, bool useExchange, bool expectedToFail) 14 | { 15 | Description = description; 16 | MemoryOrder = memoryOrder; 17 | UseExchange = useExchange; 18 | ExpectedToFail = expectedToFail; 19 | } 20 | } 21 | class Petersen : IRelaExample 22 | { 23 | private static IRelaEngine RE = RelaEngine.RE; 24 | public string Name => "Petersen Mutex"; 25 | public string Description => ActiveConfig.Description; 26 | public bool ExpectedToFail => ActiveConfig.ExpectedToFail; 27 | private IEnumerator _configs; 28 | private Config ActiveConfig => _configs.Current; 29 | private Atomic flag0; 30 | private Atomic flag1; 31 | private Atomic victim; 32 | private MemoryOrder MemoryOrder => ActiveConfig.MemoryOrder; 33 | public IReadOnlyList ThreadEntries { get; private set;} 34 | int _threadsPassed; 35 | 36 | public Petersen() 37 | { 38 | ThreadEntries = new List{Thread0,Thread1}; 39 | var configList = new List{new Config("All operations acquire-release", MemoryOrder.AcquireRelease, false, true), 40 | new Config("All operations sequentially consistent", MemoryOrder.SequentiallyConsistent, false, false), 41 | new Config("Relaxed flag entry, Release flag exit, Acquire flag spin, acquire-release exchange on victim.", MemoryOrder.Relaxed, true, false)}; 42 | _configs = configList.GetEnumerator(); 43 | } 44 | 45 | private void Thread0() 46 | { 47 | flag0.Store(1, MemoryOrder); 48 | if(ActiveConfig.UseExchange) 49 | { 50 | victim.Exchange(0, MemoryOrder.AcquireRelease); 51 | } 52 | else 53 | { 54 | victim.Store(0, MemoryOrder); 55 | } 56 | while(flag1.Load(ActiveConfig.UseExchange ? MemoryOrder.Acquire : MemoryOrder) == 1 & victim.Load(MemoryOrder) == 0) RE.Yield(); 57 | ++_threadsPassed; 58 | RE.Assert(_threadsPassed == 1, $"Mutual exclusion not achieved, {_threadsPassed} threads currently in critical section!"); 59 | flag0.Store(0, ActiveConfig.UseExchange ? MemoryOrder.Release : MemoryOrder); 60 | --_threadsPassed; 61 | } 62 | 63 | private void Thread1() 64 | { 65 | flag1.Store(1, MemoryOrder); 66 | if(ActiveConfig.UseExchange) 67 | { 68 | victim.Exchange(1, MemoryOrder.AcquireRelease); 69 | } 70 | else 71 | { 72 | victim.Store(1, MemoryOrder); 73 | } 74 | while(flag0.Load(ActiveConfig.UseExchange ? MemoryOrder.Acquire : MemoryOrder) == 1 && victim.Load(MemoryOrder) == 1) RE.Yield(); 75 | ++_threadsPassed; 76 | RE.Assert(_threadsPassed == 1, $"Mutual exclusion not achieved, {_threadsPassed} threads currently in critical section!"); 77 | flag1.Store(0, ActiveConfig.UseExchange ? MemoryOrder.Release : MemoryOrder); 78 | --_threadsPassed; 79 | } 80 | public void OnBegin() 81 | { 82 | } 83 | public void OnFinished() 84 | { 85 | } 86 | public void PrepareForIteration() 87 | { 88 | PrepareForNewConfig(); 89 | } 90 | 91 | private void PrepareForNewConfig() 92 | { 93 | flag0 = new Atomic(); 94 | flag1 = new Atomic(); 95 | victim = new Atomic(); 96 | _threadsPassed = 0; 97 | } 98 | 99 | public bool SetNextConfiguration() 100 | { 101 | PrepareForNewConfig(); 102 | bool moreConfigurations = _configs.MoveNext(); 103 | return moreConfigurations; 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /Examples/RelaxedModificationOrder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RelaSharp.Examples 5 | { 6 | class RelaxedModificationOrder : IRelaExample 7 | { 8 | private static IRelaEngine RE = RelaEngine.RE; 9 | public string Name => "Single modification order example, even with all operations relaxed"; 10 | public string Description => ActiveConfig.Description; 11 | public bool ExpectedToFail => ActiveConfig.ExpectedToFail; 12 | private IEnumerator _configs; 13 | private SimpleConfig ActiveConfig => _configs.Current; 14 | 15 | private Atomic x; 16 | 17 | public IReadOnlyList ThreadEntries { get; private set;} 18 | 19 | public RelaxedModificationOrder() 20 | { 21 | ThreadEntries = new List {Thread0, Thread1}; 22 | var configList = new List{new SimpleConfig("Relaxed operations", MemoryOrder.AcquireRelease, false)}; 23 | _configs = configList.GetEnumerator(); 24 | } 25 | 26 | public void Thread0() 27 | { 28 | x.Store(1, MemoryOrder.Relaxed); 29 | x.Store(2, MemoryOrder.Relaxed); 30 | x.Store(3, MemoryOrder.Relaxed); 31 | x.Store(4, MemoryOrder.Relaxed); 32 | x.Store(5, MemoryOrder.Relaxed); 33 | } 34 | 35 | public void Thread1() 36 | { 37 | if(x.Load(MemoryOrder.Relaxed) == 3) 38 | { 39 | RE.Assert(x.Load(MemoryOrder.Relaxed) >= 3, "x should be at least 3"); 40 | } 41 | } 42 | 43 | public void Thread2() 44 | { 45 | 46 | } 47 | 48 | public void OnBegin() 49 | { 50 | } 51 | public void OnFinished() 52 | { 53 | } 54 | public void PrepareForIteration() 55 | { 56 | PrepareForNewConfig(); 57 | } 58 | 59 | private void PrepareForNewConfig() 60 | { 61 | x = new Atomic(); 62 | } 63 | public bool SetNextConfiguration() 64 | { 65 | PrepareForNewConfig(); 66 | bool moreConfigurations = _configs.MoveNext(); 67 | return moreConfigurations; 68 | } 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /Examples/SimpleAcquireRelease.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RelaSharp.Examples 5 | { 6 | public class SimpleAcquireRelease : IRelaExample 7 | { 8 | private class Config 9 | { 10 | public readonly string Description; 11 | public readonly MemoryOrder StoreMemoryOrder; 12 | public readonly MemoryOrder LoadMemoryOrder; 13 | public readonly bool ExpectedToFail; 14 | 15 | public Config(string description, MemoryOrder storeMemoryOrder, MemoryOrder loadMemoryOrder, bool expectedToFail) 16 | { 17 | Description = description; 18 | StoreMemoryOrder = storeMemoryOrder; 19 | LoadMemoryOrder = loadMemoryOrder; 20 | ExpectedToFail = expectedToFail; 21 | } 22 | } 23 | public string Name => "Simple demonstration of acquire and release semantics."; 24 | public string Description => ActiveConfig.Description; 25 | public bool ExpectedToFail => ActiveConfig.ExpectedToFail; 26 | public IReadOnlyList ThreadEntries { get; private set;} 27 | private static IRelaEngine RE = RelaEngine.RE; 28 | private Atomic _flag; 29 | private Atomic _x; 30 | private IEnumerator _configs; 31 | private Config ActiveConfig => _configs.Current; 32 | 33 | public SimpleAcquireRelease() 34 | { 35 | ThreadEntries = new List { ReleaseThread, AcquireThread}; 36 | var configList = new List{new Config("Final store relaxed, result-load relaxed", MemoryOrder.Relaxed, MemoryOrder.Relaxed, true), 37 | new Config("Final store release, result-load relaxed.", MemoryOrder.Release, MemoryOrder.Relaxed, true), 38 | new Config("Final store release, result-load acquire.", MemoryOrder.Release, MemoryOrder.Acquire, false), 39 | new Config("Final store sequentially consistent, result-load sequentially consistent.", MemoryOrder.SequentiallyConsistent, MemoryOrder.SequentiallyConsistent, false) 40 | }; 41 | _configs = configList.GetEnumerator(); 42 | } 43 | 44 | public void PrepareForIteration() 45 | { 46 | PrepareForNewConfig(); 47 | } 48 | 49 | private void ReleaseThread() 50 | { 51 | _x.Store(23, MemoryOrder.Relaxed); 52 | _x.Store(22, MemoryOrder.Relaxed); 53 | _x.Store(21, MemoryOrder.Relaxed); 54 | _x.Store(2, MemoryOrder.Relaxed); 55 | _flag.Store(0, MemoryOrder.Relaxed); 56 | _flag.Store(1, ActiveConfig.StoreMemoryOrder); 57 | } 58 | 59 | private void AcquireThread() 60 | { 61 | while(_flag.Load(ActiveConfig.LoadMemoryOrder) == 0) 62 | { 63 | RE.Yield(); 64 | } 65 | int result = _x.Load(MemoryOrder.Relaxed); 66 | RE.Assert(result == 2, $"Expected to load 2 into result, but loaded {result} instead!"); 67 | } 68 | 69 | public void OnBegin() 70 | { 71 | 72 | } 73 | public void OnFinished() 74 | { 75 | } 76 | private void PrepareForNewConfig() 77 | { 78 | _flag = new Atomic(); 79 | _x = new Atomic(); 80 | } 81 | public bool SetNextConfiguration() 82 | { 83 | PrepareForNewConfig(); 84 | bool moreConfigurations = _configs.MoveNext(); 85 | return moreConfigurations; 86 | } 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /Examples/SimpleConfig.cs: -------------------------------------------------------------------------------- 1 | namespace RelaSharp.Examples 2 | { 3 | class SimpleConfig 4 | { 5 | public readonly string Description; 6 | public readonly MemoryOrder MemoryOrder; 7 | 8 | public readonly bool ExpectedToFail; 9 | 10 | public SimpleConfig(string description, MemoryOrder memoryOrder, bool expectedToFail) 11 | { 12 | Description = description; 13 | MemoryOrder = memoryOrder; 14 | ExpectedToFail = expectedToFail; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Examples/SimpleTransitive.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RelaSharp.Examples 5 | { 6 | class SimpleTransitive : IRelaExample 7 | { 8 | private static IRelaEngine RE = RelaEngine.RE; 9 | public string Name => "Acquire/Release transitive visibility example"; 10 | public string Description => ActiveConfig.Description; 11 | public bool ExpectedToFail => ActiveConfig.ExpectedToFail; 12 | private IEnumerator _configs; 13 | private SimpleConfig ActiveConfig => _configs.Current; 14 | 15 | private Atomic x, y, z; 16 | 17 | public IReadOnlyList ThreadEntries { get; private set;} 18 | 19 | public SimpleTransitive() 20 | { 21 | ThreadEntries = new List {Thread0, Thread1, Thread2}; 22 | var configList = new List{new SimpleConfig("Mixed operations", MemoryOrder.AcquireRelease, false)}; 23 | _configs = configList.GetEnumerator(); 24 | } 25 | 26 | public void Thread0() 27 | { 28 | x.Store(1, MemoryOrder.Release); 29 | x.Store(2, MemoryOrder.Release); 30 | } 31 | 32 | public void Thread1() 33 | { 34 | if(x.Load(MemoryOrder.Acquire) == 2) 35 | { 36 | y.Store(1, MemoryOrder.Release); 37 | } 38 | } 39 | 40 | public void Thread2() 41 | { 42 | if(y.Load(MemoryOrder.Acquire) == 1) 43 | { 44 | RE.Assert(x.Load(MemoryOrder.Relaxed) == 2, "x should be 2"); 45 | } 46 | } 47 | 48 | public void OnBegin() 49 | { 50 | } 51 | public void OnFinished() 52 | { 53 | } 54 | public void PrepareForIteration() 55 | { 56 | PrepareForNewConfig(); 57 | } 58 | 59 | private void PrepareForNewConfig() 60 | { 61 | x = new Atomic(); 62 | y = new Atomic(); 63 | z = new Atomic(); 64 | } 65 | public bool SetNextConfiguration() 66 | { 67 | PrepareForNewConfig(); 68 | bool moreConfigurations = _configs.MoveNext(); 69 | return moreConfigurations; 70 | } 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /Examples/StoreLoad.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RelaSharp.Examples 5 | { 6 | public class StoreLoad : IRelaExample 7 | { 8 | public IReadOnlyList ThreadEntries { get; private set; } 9 | 10 | public string Name => "Store Load Re-ordering example"; 11 | public string Description => ActiveConfig.Description; 12 | public bool ExpectedToFail => ActiveConfig.ExpectedToFail; 13 | private static IRelaEngine RE = RelaEngine.RE; 14 | private Atomic x0, x1; 15 | private int y0, y1; 16 | 17 | private IEnumerator _configs; 18 | private SimpleConfig ActiveConfig => _configs.Current; 19 | 20 | public StoreLoad() 21 | { 22 | ThreadEntries = new List {Thread1,Thread2}; 23 | var configList = new List{new SimpleConfig("All operations relaxed", MemoryOrder.Relaxed, true), 24 | new SimpleConfig("All operations acquire-release", MemoryOrder.AcquireRelease, true), 25 | new SimpleConfig("All operations sequentially consistent", MemoryOrder.SequentiallyConsistent, false)}; 26 | _configs = configList.GetEnumerator(); 27 | } 28 | 29 | public void PrepareForIteration() 30 | { 31 | PrepareForNewConfig(); 32 | } 33 | 34 | public void Thread1() 35 | { 36 | x0.Store(1, ActiveConfig.MemoryOrder); 37 | y0 = x1.Load(ActiveConfig.MemoryOrder); 38 | } 39 | 40 | public void Thread2() 41 | { 42 | x1.Store(1, ActiveConfig.MemoryOrder); 43 | y1 = x0.Load(ActiveConfig.MemoryOrder); 44 | } 45 | public void OnBegin() 46 | { 47 | } 48 | public void OnFinished() 49 | { 50 | RE.Assert(y0 != 0 || y1 != 0, "Both of y0 and y1 are zero! (store load reordering!)"); 51 | } 52 | 53 | private void PrepareForNewConfig() 54 | { 55 | x0 = new Atomic(); 56 | x1 = new Atomic(); 57 | } 58 | 59 | public bool SetNextConfiguration() 60 | { 61 | PrepareForNewConfig(); 62 | bool moreConfigurations = _configs.MoveNext(); 63 | return moreConfigurations; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Examples/TotalOrder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RelaSharp.Examples 5 | { 6 | class TotalOrder : IRelaExample 7 | { 8 | private static IRelaEngine RE = RelaEngine.RE; 9 | public string Name => "Total order test (multiple-copy atomicity / write synchronization test)"; 10 | public string Description => ActiveConfig.Description; 11 | public bool ExpectedToFail => ActiveConfig.ExpectedToFail; 12 | private IEnumerator _configs; 13 | private SimpleConfig ActiveConfig => _configs.Current; 14 | 15 | private Atomic a, b; 16 | private int c, d; 17 | 18 | public IReadOnlyList ThreadEntries { get; private set;} 19 | 20 | public TotalOrder() 21 | { 22 | ThreadEntries = new List {Thread0, Thread1, Thread2, Thread3}; 23 | var configList = new List{new SimpleConfig("All operations acquire-release", MemoryOrder.AcquireRelease, true), 24 | new SimpleConfig("All operations sequentially consistent", MemoryOrder.SequentiallyConsistent, false)}; 25 | _configs = configList.GetEnumerator(); 26 | } 27 | 28 | public void Thread0() 29 | { 30 | a.Store(1, ActiveConfig.MemoryOrder); 31 | } 32 | 33 | public void Thread1() 34 | { 35 | b.Store(1, ActiveConfig.MemoryOrder); 36 | } 37 | 38 | public void Thread2() 39 | { 40 | if(a.Load(ActiveConfig.MemoryOrder) == 1 && b.Load(ActiveConfig.MemoryOrder) == 0) 41 | { 42 | c = 1; 43 | } 44 | } 45 | 46 | public void Thread3() 47 | { 48 | if(b.Load(ActiveConfig.MemoryOrder) == 1 && a.Load(ActiveConfig.MemoryOrder) == 0) 49 | { 50 | d = 1; 51 | } 52 | } 53 | public void OnBegin() 54 | { 55 | 56 | } 57 | public void OnFinished() 58 | { 59 | RE.Assert(c + d != 2, $"c + d == {c + d} ; neither of Thread0 or Thread1 ran first!"); 60 | } 61 | public void PrepareForIteration() 62 | { 63 | PrepareForNewConfig(); 64 | } 65 | 66 | private void PrepareForNewConfig() 67 | { 68 | a = new Atomic(); 69 | b = new Atomic(); 70 | c = d = 0; 71 | } 72 | public bool SetNextConfiguration() 73 | { 74 | PrepareForNewConfig(); 75 | bool moreConfigurations = _configs.MoveNext(); 76 | return moreConfigurations; 77 | } 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /Examples/TransitiveLastSeen.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RelaSharp.Examples 5 | { 6 | class TransitiveLastSeen : IRelaExample 7 | { 8 | private static IRelaEngine RE = RelaEngine.RE; 9 | public string Name => "Relaxed transitive visibility (last seen) example"; 10 | public string Description => ActiveConfig.Description; 11 | public bool ExpectedToFail => ActiveConfig.ExpectedToFail; 12 | private IEnumerator _configs; 13 | private SimpleConfig ActiveConfig => _configs.Current; 14 | 15 | private Atomic x, y, z; 16 | 17 | public IReadOnlyList ThreadEntries { get; private set;} 18 | 19 | public TransitiveLastSeen() 20 | { 21 | ThreadEntries = new List {Thread0, Thread1, Thread2}; 22 | var configList = new List{new SimpleConfig("Mixed operations", MemoryOrder.AcquireRelease, false)}; 23 | _configs = configList.GetEnumerator(); 24 | } 25 | 26 | public void Thread0() 27 | { 28 | x.Store(1, MemoryOrder.Relaxed); 29 | x.Store(2, MemoryOrder.Relaxed); 30 | } 31 | 32 | public void Thread1() 33 | { 34 | if(x.Load(MemoryOrder.Relaxed) == 2) 35 | { 36 | y.Store(1, MemoryOrder.Release); 37 | } 38 | } 39 | 40 | public void Thread2() 41 | { 42 | if(y.Load(MemoryOrder.Acquire) == 1) 43 | { 44 | RE.Assert(x.Load(MemoryOrder.Relaxed) == 2, "x should be 2"); 45 | } 46 | } 47 | 48 | public void OnBegin() 49 | { 50 | } 51 | public void OnFinished() 52 | { 53 | } 54 | public void PrepareForIteration() 55 | { 56 | PrepareForNewConfig(); 57 | } 58 | 59 | private void PrepareForNewConfig() 60 | { 61 | x = new Atomic(); 62 | y = new Atomic(); 63 | z = new Atomic(); 64 | } 65 | public bool SetNextConfiguration() 66 | { 67 | PrepareForNewConfig(); 68 | bool moreConfigurations = _configs.MoveNext(); 69 | return moreConfigurations; 70 | } 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /Examples/TreiberStack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RelaSharp.Examples 5 | { 6 | public class TreiberStack : IRelaExample 7 | { 8 | private class Config 9 | { 10 | public readonly string Description; 11 | public readonly int NumPushingThreads; 12 | public readonly int NumPushedPerThread; 13 | public readonly int NumPoppingThreads; 14 | public readonly bool PushAllBeforePop; 15 | public readonly Action PostCondition; 16 | public readonly bool ExpectedToFail; 17 | 18 | public Config(string description, int numPushing, int numPushedPerThread, int numPopping, bool pushAllBeforePop, Action postCondition, bool expectedToFail) 19 | { 20 | Description = description; 21 | NumPushingThreads = numPushing; 22 | NumPushedPerThread = numPushedPerThread; 23 | NumPoppingThreads = numPopping; 24 | PushAllBeforePop = pushAllBeforePop; 25 | PostCondition = postCondition; 26 | ExpectedToFail = expectedToFail; 27 | } 28 | } 29 | 30 | private class Node 31 | { 32 | public readonly int Value; 33 | public Atomic Next; 34 | public Node(int v) 35 | { 36 | Value = v; 37 | Next = new Atomic(); 38 | } 39 | 40 | public override string ToString() 41 | { 42 | return $"{Value}"; 43 | } 44 | } 45 | 46 | private class NaiveLockFreeStack 47 | { 48 | private Atomic _head; 49 | 50 | public NaiveLockFreeStack() 51 | { 52 | _head = new Atomic(); 53 | } 54 | 55 | public void Push(int n) 56 | { 57 | Node currentHead; 58 | Node newHead = new Node(n); 59 | do 60 | { 61 | currentHead = _head.Load(MemoryOrder.Acquire); // TODO, add tests with these relaxed 62 | newHead.Next.Store(currentHead, MemoryOrder.Relaxed); 63 | } while(!_head.CompareExchange(newHead, currentHead, MemoryOrder.AcquireRelease)); 64 | } 65 | 66 | public int? Pop() 67 | { 68 | Node currentHead; 69 | Node newHead; 70 | int? result; 71 | do 72 | { 73 | currentHead = _head.Load(MemoryOrder.Acquire); // TODO, add tests with this relaxed 74 | result = currentHead?.Value; 75 | newHead = currentHead?.Next.Load(MemoryOrder.Relaxed); // TODO, add tests with this relaxed 76 | } while(!_head.CompareExchange(newHead, currentHead, MemoryOrder.AcquireRelease)); 77 | return result; 78 | } 79 | } 80 | public IReadOnlyList ThreadEntries { get; private set; } 81 | public string Name => "Treiber Stack"; 82 | public string Description => ActiveConfig.Description; 83 | public bool ExpectedToFail => ActiveConfig.ExpectedToFail; 84 | private static IRelaEngine RE = RelaEngine.RE; 85 | private IEnumerator _configs; 86 | private Config ActiveConfig => _configs.Current; 87 | private NaiveLockFreeStack _stack; 88 | private Atomic _pushingThreadFinished; 89 | List _popped; 90 | List _poppedInOrder; 91 | 92 | public TreiberStack() 93 | { 94 | var configList = new List{new Config("3 threads pushing 5 elements each, interleaved with 2 threads popping", 3, 5, 2, false, VerifyAllPushedWerePopped, false), 95 | new Config("1 thread pushing 20 elements each, interleaved with 10 threads popping", 1, 20, 10, false, VerifyAllPushedWerePopped, false), 96 | new Config("1 thread pushing 30 elements, then 5 threads popping, all pushes before any pops.", 1, 30, 5, true, VerifyAllPoppedInReverseOrder, false) 97 | }; 98 | _configs = configList.GetEnumerator(); 99 | } 100 | 101 | public void PrepareForIteration() 102 | { 103 | PrepareForNewConfig(); 104 | } 105 | 106 | private void VerifyAllPushedWerePopped() 107 | { 108 | var distinctPopped = new HashSet(_popped); 109 | RE.Assert(distinctPopped.Count == _popped.Count, "Duplicates popped!"); 110 | for(int i = 0; i < ActiveConfig.NumPushingThreads * ActiveConfig.NumPushedPerThread; ++i) 111 | { 112 | RE.Assert(distinctPopped.Remove(i), $"Couldn't find {i} in data popped from stack"); 113 | } 114 | RE.Assert(distinctPopped.Count == 0, "More data popped than pushed!"); 115 | } 116 | 117 | private void VerifyAllPoppedInReverseOrder() 118 | { 119 | int expectedNumPushed = ActiveConfig.NumPushingThreads * ActiveConfig.NumPushedPerThread; 120 | RE.Assert(_poppedInOrder.Count == expectedNumPushed, $"Too much data popped: expected {expectedNumPushed} but found {_poppedInOrder.Count}"); 121 | for(int i = 0; i < expectedNumPushed; ++i) 122 | { 123 | int expected = expectedNumPushed - i - 1; 124 | RE.Assert(_poppedInOrder[i] == expected, $"Expected to find {expected} in popped list but found {_poppedInOrder[i]}."); 125 | } 126 | } 127 | public void OnBegin() 128 | { 129 | 130 | } 131 | public void OnFinished() 132 | { 133 | ActiveConfig.PostCondition(); 134 | } 135 | 136 | private Action MakePushingThread(int threadIndex, int numPushedPerThread) 137 | { 138 | return () => PushingThread(threadIndex, numPushedPerThread); 139 | } 140 | 141 | private Action MakePoppingThread() 142 | { 143 | return PoppingThread; 144 | } 145 | 146 | private void PushingThread(int threadIndex, int numPushedPerThread) 147 | { 148 | for (int i = 0; i < numPushedPerThread; ++i) 149 | { 150 | _stack.Push(numPushedPerThread * threadIndex + i); 151 | } 152 | _pushingThreadFinished.Store(true, MemoryOrder.Release); 153 | } 154 | 155 | private void PoppingThread() 156 | { 157 | if(ActiveConfig.PushAllBeforePop) 158 | { 159 | while(!_pushingThreadFinished.Load(MemoryOrder.Acquire)) ; 160 | } 161 | while (_popped.Count < ActiveConfig.NumPushingThreads * ActiveConfig.NumPushedPerThread) 162 | { 163 | int? x = _stack.Pop(); 164 | if (x.HasValue) 165 | { 166 | _popped.Add(x.Value); 167 | _poppedInOrder.Add(x.Value); 168 | } 169 | } 170 | } 171 | 172 | private void PrepareForNewConfig() 173 | { 174 | _stack = new NaiveLockFreeStack(); 175 | _popped = new List(); 176 | _poppedInOrder = new List(); 177 | _pushingThreadFinished = new Atomic(); 178 | } 179 | 180 | private void SetupActiveConfig() 181 | { 182 | var threadEntries = new List(); 183 | for(int i = 0; i < ActiveConfig.NumPushingThreads; ++i) 184 | { 185 | threadEntries.Add(MakePushingThread(i, ActiveConfig.NumPushedPerThread)); 186 | } 187 | for(int i = 0; i < ActiveConfig.NumPoppingThreads; ++i) 188 | { 189 | threadEntries.Add(MakePoppingThread()); 190 | } 191 | ThreadEntries = threadEntries; 192 | } 193 | 194 | public bool SetNextConfiguration() 195 | { 196 | PrepareForNewConfig(); 197 | bool moreConfigurations = _configs.MoveNext(); 198 | if(ActiveConfig != null) 199 | { 200 | SetupActiveConfig(); 201 | } 202 | return moreConfigurations; 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Nick Nash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /RelaSharp.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Examples", "Examples\Examples.csproj", "{7AB64CAC-0944-4E30-B6BB-16C10DA0E6DB}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine", "Engine\Engine.csproj", "{C466C76A-C99D-4E8F-9111-11808C6EA1F4}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {7AB64CAC-0944-4E30-B6BB-16C10DA0E6DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {7AB64CAC-0944-4E30-B6BB-16C10DA0E6DB}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {7AB64CAC-0944-4E30-B6BB-16C10DA0E6DB}.Debug|x64.ActiveCfg = Debug|x64 26 | {7AB64CAC-0944-4E30-B6BB-16C10DA0E6DB}.Debug|x64.Build.0 = Debug|x64 27 | {7AB64CAC-0944-4E30-B6BB-16C10DA0E6DB}.Debug|x86.ActiveCfg = Debug|x86 28 | {7AB64CAC-0944-4E30-B6BB-16C10DA0E6DB}.Debug|x86.Build.0 = Debug|x86 29 | {7AB64CAC-0944-4E30-B6BB-16C10DA0E6DB}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {7AB64CAC-0944-4E30-B6BB-16C10DA0E6DB}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {7AB64CAC-0944-4E30-B6BB-16C10DA0E6DB}.Release|x64.ActiveCfg = Release|x64 32 | {7AB64CAC-0944-4E30-B6BB-16C10DA0E6DB}.Release|x64.Build.0 = Release|x64 33 | {7AB64CAC-0944-4E30-B6BB-16C10DA0E6DB}.Release|x86.ActiveCfg = Release|x86 34 | {7AB64CAC-0944-4E30-B6BB-16C10DA0E6DB}.Release|x86.Build.0 = Release|x86 35 | {C466C76A-C99D-4E8F-9111-11808C6EA1F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {C466C76A-C99D-4E8F-9111-11808C6EA1F4}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {C466C76A-C99D-4E8F-9111-11808C6EA1F4}.Debug|x64.ActiveCfg = Debug|x64 38 | {C466C76A-C99D-4E8F-9111-11808C6EA1F4}.Debug|x64.Build.0 = Debug|x64 39 | {C466C76A-C99D-4E8F-9111-11808C6EA1F4}.Debug|x86.ActiveCfg = Debug|x86 40 | {C466C76A-C99D-4E8F-9111-11808C6EA1F4}.Debug|x86.Build.0 = Debug|x86 41 | {C466C76A-C99D-4E8F-9111-11808C6EA1F4}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {C466C76A-C99D-4E8F-9111-11808C6EA1F4}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {C466C76A-C99D-4E8F-9111-11808C6EA1F4}.Release|x64.ActiveCfg = Release|x64 44 | {C466C76A-C99D-4E8F-9111-11808C6EA1F4}.Release|x64.Build.0 = Release|x64 45 | {C466C76A-C99D-4E8F-9111-11808C6EA1F4}.Release|x86.ActiveCfg = Release|x86 46 | {C466C76A-C99D-4E8F-9111-11808C6EA1F4}.Release|x86.Build.0 = Release|x86 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | * Multi-threaded test runner: This is a bit of a silly limitation, e.g. breaks xUnit with combination of Live/Sim tests. 2 | * Investigate adding a race-checked mode or having race-checking enabled by default. 3 | * Place OnBegin/OnFinished on dedicated threads with pre-emption disabled (with seq cst fences). The current situation 4 | where they logically run on a given test-thread is awkward and inefficient (see e.g. the hoops that the COWList Example 5 | has to jump through). 6 | 7 | * Event log tags? 8 | 9 | * Exhaustive scheduler: Finish fair/demonic scheduling (need to deal with thread wake-ups) 10 | * Exhaustive scheduler: eliminate wasteful switches / perform DPOR 11 | 12 | * PCT Scheduler / randomized context bounding. 13 | * Left/Right RelaExample: Interesting invariants / verifications? 14 | * Add MaybeSwitchAssert ? 15 | * Finish RMonitor 16 | * Finish RInterlocked 17 | * Lift execution order limitation via promises (see e.g. CDSChecker or POPL'2017 paper "A promising semantics for relaxed-memory concurrency") 18 | * ExtendedReflection : GC-pinning / transparent API 19 | * Implement a CLR+x86-TSO mode? 20 | * Review how RMW ops + Relaxed (especially, but other MOs too) are specified in C++11 vs. how my impl behaves. 21 | * Progress / interruptibility. 22 | * Review Seq cst fence 23 | * More Example algorithms: Michael-Scott two-lock queue, bounded SPMC/MPMC, CRQ/LCRQ, Fetch-Add Queue. 24 | * Add Invariant() feature 25 | * Perf. optimizations. 26 | * Replay optimization: Find failing execution and replay with event logging on, otherwise always have event logging off. 27 | * Interesting statistics? --------------------------------------------------------------------------------