Last active
October 28, 2017 14:06
-
-
Save eocron/5c5f3f235c60eb47f646e448de1b1bfa to your computer and use it in GitHub Desktop.
Simple synchronization primitive in concept of 'using' directive. Just aquire read/write scope, execute critical section and don't forget to dispose it after you are done. Also includes dictionary for multiple scopes retrieval at once..
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Concurrent; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Threading; | |
namespace Helpers | |
{ | |
public sealed class LockScope | |
{ | |
private readonly ReaderWriterLockSlim _slim = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); | |
public static readonly TimeSpan Infinity = TimeSpan.FromMilliseconds(-1); | |
public bool IsReadHeld => _slim.IsReadLockHeld; | |
public bool IsWriteHeld => _slim.IsWriteLockHeld; | |
public bool TryGetWriteScope(TimeSpan? timeout, out IDisposable scope) | |
{ | |
scope = default(IDisposable); | |
if (_slim.TryEnterWriteLock(timeout ?? Infinity)) | |
{ | |
scope = new WriteScope(_slim); | |
return true; | |
} | |
return false; | |
} | |
public IDisposable GetWriteScope(TimeSpan? timeout = null) | |
{ | |
IDisposable scope; | |
TryGetWriteScope(timeout, out scope); | |
return scope; | |
} | |
public bool TryGetReadScope(TimeSpan? timeout, out IDisposable scope) | |
{ | |
scope = default(IDisposable); | |
if (_slim.TryEnterReadLock(timeout ?? Infinity)) | |
{ | |
scope = new ReadScope(_slim); | |
return true; | |
} | |
return false; | |
} | |
public IDisposable GetReadScope(TimeSpan? timeout = null) | |
{ | |
IDisposable scope; | |
TryGetReadScope(timeout, out scope); | |
return scope; | |
} | |
private struct ReadScope : IDisposable | |
{ | |
private readonly ReaderWriterLockSlim _slim; | |
public ReadScope(ReaderWriterLockSlim slim) | |
{ | |
_slim = slim; | |
} | |
public void Dispose() | |
{ | |
_slim.ExitReadLock(); | |
} | |
} | |
private struct WriteScope : IDisposable | |
{ | |
private readonly ReaderWriterLockSlim _slim; | |
public WriteScope(ReaderWriterLockSlim slim) | |
{ | |
_slim = slim; | |
} | |
public void Dispose() | |
{ | |
_slim.ExitWriteLock(); | |
} | |
} | |
} | |
public sealed class LockScopeDictionary<TKey> | |
{ | |
private readonly ConcurrentDictionary<TKey, LockScope> _index = new ConcurrentDictionary<TKey, LockScope>(); | |
private readonly LockScope _selfScope = new LockScope(); | |
private LockScope GetByKey(TKey key) | |
{ | |
LockScope tmp; | |
using (_selfScope.GetReadScope()) | |
{ | |
tmp = _index[key]; | |
} | |
return tmp; | |
} | |
public bool IsReadHeld(TKey key) | |
{ | |
return GetByKey(key).IsReadHeld; | |
} | |
public bool IsWriteHeld(TKey key) | |
{ | |
return GetByKey(key).IsWriteHeld; | |
} | |
public bool TryAdd(TKey key) | |
{ | |
using (_selfScope.GetWriteScope()) | |
{ | |
return _index.TryAdd(key, new LockScope()); | |
} | |
} | |
public bool TryRemove(TKey key) | |
{ | |
using (_selfScope.GetWriteScope()) | |
{ | |
LockScope tmp; | |
return _index.TryRemove(key, out tmp); | |
} | |
} | |
public bool TryGetWriteScope(TKey key, TimeSpan timeout, out IDisposable scope) | |
{ | |
return GetByKey(key).TryGetWriteScope(timeout, out scope); | |
} | |
public IDisposable GetWriteScope(TKey key, TimeSpan? timeout = null) | |
{ | |
return GetByKey(key).GetWriteScope(timeout); | |
} | |
public bool TryGetReadScope(TKey key, TimeSpan timeout, out IDisposable scope) | |
{ | |
return GetByKey(key).TryGetReadScope(timeout, out scope); | |
} | |
public IDisposable GetReadScope(TKey key, TimeSpan? timeout = null) | |
{ | |
return GetByKey(key).GetReadScope(timeout); | |
} | |
public bool TryGetScope(IEnumerable<TKey> toRead, IEnumerable<TKey> toWrite, TimeSpan? timeout, out IDisposable scope) | |
{ | |
scope = null; | |
var toReadSet = new HashSet<TKey>(); | |
var toWriteSet = new HashSet<TKey>(); | |
if (toWrite != null) | |
{ | |
foreach (var key in toWrite) | |
{ | |
toWriteSet.Add(key); | |
} | |
} | |
if (toRead != null) | |
{ | |
foreach (var key in toRead) | |
{ | |
if (!toWriteSet.Contains(key)) | |
{ | |
toReadSet.Add(key); | |
} | |
} | |
} | |
//We sort commands to prevent deadlocks between two separate scope retrieval | |
var commandFlow = toReadSet.Select(x => new LockCommand(x, true)) | |
.Union(toWriteSet.Select(x => new LockCommand(x, false))) | |
.OrderBy(x => x.Key) | |
.ToList(); | |
var leftTimeout = timeout ?? LockScope.Infinity; | |
var tmp = new ReadWriteScope(); | |
try | |
{ | |
var sw = new Stopwatch(); | |
foreach (var cmd in commandFlow) | |
{ | |
//calculation of left timeout of overall one | |
leftTimeout = GetLeftTimeout(leftTimeout, sw.Elapsed); | |
sw.Restart(); | |
IDisposable partScope; | |
if (cmd.IsRead) | |
{ | |
if (GetByKey(cmd.Key).TryGetReadScope(leftTimeout, out partScope)) | |
{ | |
tmp.Add(partScope); | |
} | |
else | |
{ | |
//failed to make it in time | |
tmp.Dispose(); | |
return false; | |
} | |
} | |
else | |
{ | |
if (GetByKey(cmd.Key).TryGetWriteScope(leftTimeout, out partScope)) | |
{ | |
tmp.Add(partScope); | |
} | |
else | |
{ | |
//failed to make it in time | |
tmp.Dispose(); | |
return false; | |
} | |
} | |
sw.Stop(); | |
} | |
} | |
catch | |
{ | |
//failed because of some internal error | |
tmp.Dispose(); | |
throw; | |
} | |
scope = tmp; | |
return true; | |
} | |
public IDisposable GetScope(IEnumerable<TKey> toRead, IEnumerable<TKey> toWrite, TimeSpan? timeout = null) | |
{ | |
IDisposable result; | |
TryGetScope(toRead, toWrite, timeout, out result); | |
return result; | |
} | |
private static TimeSpan GetLeftTimeout(TimeSpan timeout, TimeSpan elapsed) | |
{ | |
if (timeout == LockScope.Infinity) | |
return timeout; | |
var result = timeout - elapsed; | |
if (result <= TimeSpan.Zero) | |
{ | |
return TimeSpan.Zero; | |
} | |
return result; | |
} | |
private class ReadWriteScope : IDisposable | |
{ | |
private readonly Stack<IDisposable> _toDispose = new Stack<IDisposable>(); | |
public void Add(IDisposable scope) | |
{ | |
_toDispose.Push(scope); | |
} | |
public void Dispose() | |
{ | |
while (_toDispose.Count > 0) | |
{ | |
var d = _toDispose.Pop(); | |
d.Dispose(); | |
} | |
} | |
} | |
private struct LockCommand | |
{ | |
public readonly bool IsRead; | |
public readonly TKey Key; | |
public LockCommand(TKey x, bool v) : this() | |
{ | |
Key = x; | |
IsRead = v; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment