Skip to content

Instantly share code, notes, and snippets.

@eocron
Last active October 28, 2017 14:06
Show Gist options
  • Save eocron/5c5f3f235c60eb47f646e448de1b1bfa to your computer and use it in GitHub Desktop.
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..
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