Does this implementation of Redis Connection Pool seem correct? - c#

I implemented a version of Redis Connection Pool manager (built on top of others' work) that is instantiated by a (client) static method in a static class. Currently, I am facing tons of connectionException errors in AWS.
I am concerned that the connection pool may not be working as intended. Therefore, I am asking for a code review of RedisCacheConnectionPoolManager implementation to identify any obvious problems with it. Thanks in advance.
public class RedisCacheConnectionPoolManager : IRedisCacheConnectionPoolManager
{
private static ConcurrentBag<Lazy<ConnectionMultiplexer>> _connections;
private readonly ConfigurationOptions _configurationOptions;
private readonly int _poolSize;
private const int MAX_CONNECTIONS = 10000;
private const int MIN_CONNECTIONS = 1;
private bool _disposed = false;
public RedisCacheConnectionPoolManager(int poolSize, string connectionString) : this(poolSize, ConfigurationOptions.Parse(connectionString))
{
}
public RedisCacheConnectionPoolManager(int poolSize, ConfigurationOptions configurationOptions)
{
if (poolSize < MIN_CONNECTIONS || poolSize > MAX_CONNECTIONS) throw new ArgumentException(string.Format("Pool size parameter must be within {0} and {1}.", MIN_CONNECTIONS.ToString(), MAX_CONNECTIONS.ToString()));
if (configurationOptions == null) throw new ArgumentNullException(string.Format("Configuration Options parameter cannot be null."));
_poolSize = poolSize;
_configurationOptions = configurationOptions;
Initialize();
}
// Destructor
~RedisCacheConnectionPoolManager()
{
Dispose(false);
}
public IConnectionMultiplexer GetConnection()
{
var loadedLazies = _connections.Where(x => x.IsValueCreated);
if (loadedLazies.Count() == _connections.Count)
{
return _connections.OrderBy(x => x.Value.GetCounters().TotalOutstanding).First().Value;
}
return _connections.First(x => !x.IsValueCreated).Value;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Initialize()
{
_connections = new ConcurrentBag<Lazy<ConnectionMultiplexer>>();
for (int i = 0; i < _poolSize; i++)
{
_connections.Add(new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(_configurationOptions)));
}
}
protected virtual void Dispose(bool disposing)
{
try
{
if (!_disposed)
{
if (disposing)
{
// free all managed resource
var activeConnections = _connections.Where(lazy => lazy.IsValueCreated).ToList();
activeConnections.ForEach(connection => connection.Value.Dispose());
while (!_connections.IsEmpty)
{
_connections.TryTake(out var taken);
}
}
}
// free all unmanaged resources here
}
finally
{
_disposed = true;
}
}
}
public static class RedisClient()
{
private static IRedisCacheConnectionPoolManager _connectionPoolManager;
public static IConnectionMultiplexer GetConnnection()
{
return _connectionPoolManager.GetConnection();
} ​
​ private static IRedisCacheConnectionPoolManager GetConnectionPoolManager()
​ {
​if (_connectionPoolManager == null)
​{
​lock (_lock)
​{
​if (_connectionPoolManager == null
​{
try
​​ {
​​_connectionPoolManager = new RedisCacheConnectionPoolManager(config.PoolSize, config.Configuration);
​​}
​​catch (Exception ex)
​​{
​​// handle error and log
​​}
​​}
​​}
​​}
​​return _connectionPoolManager;
​​}

Related

Transaction scope similar functionality

I am looking to setup something very similar to transaction scope which creates a version on a service and will delete/commit at the end of scope. Every SQL statement ran inside the transaction scope internally looks at some connection pool / transaction storage to determine if its in the scope and reacts appropriately. The caller doesn't need to pass in the transaction to every call. I am looking for this functionality.
Here is a little more about it: https://blogs.msdn.microsoft.com/florinlazar/2005/04/19/transaction-current-and-ambient-transactions/
Here is the basic disposable class:
public sealed class VersionScope : IDisposable
{
private readonly GeodatabaseVersion _version;
private readonly VersionManager _versionManager;
public VersionScope(Configuration config)
{
_versionManager = new VersionManager(config);
_version = _versionManager.GenerateTempVersion();
_versionManager.Create(_version);
_versionManager.VerifyValidVersion(_version);
_versionManager.ServiceReconcilePull();
_versionManager.ReconcilePull(_version);
}
public void Dispose()
{
_versionManager.Delete(_version);
}
public void Complete()
{
_versionManager.ReconcilePush(_version);
}
}
I want the ability for all the code I've written thus far to not have any concept of being in a version. I just want to include a simple
Version = GetCurrentVersionWithinScope()
at the lowest level of the code.
What is the safest way of implementing something like this with little risk of using the wrong version if there are multiple instances in memory simultaneously running.
My very naive approach would be find if there is a unique identifier for a block of memory a process is running in. Then store the current working version to a global array or concurrent dictionary. Then in the code where I need the current version, I use its block of memory identifier and it maps to the version that was created.
Edit:
Example of usage:
using (var scope = new VersionScope(_config))
{
AddFeature(); // This has no concept of scope passed to it, and could error out forcing a dispose() without a complete()
scope.Complete();
}
The most straightforward approach would be to use ThreadStatic or ThreadLocal to store current version in thread local storage. That way multiple threads will not interfere with each other. For example suppose we version class:
public class Version {
public Version(int number) {
Number = number;
}
public int Number { get; }
public override string ToString() {
return "Version " + Number;
}
}
Then implementation of VersionScope can go like this:
public sealed class VersionScope : IDisposable {
private bool _isCompleted;
private bool _isDisposed;
// note ThreadStatic attribute
[ThreadStatic] private static Version _currentVersion;
public static Version CurrentVersion => _currentVersion;
public VersionScope(int version) {
_currentVersion = new Version(version);
}
public void Dispose() {
if (_isCompleted || _isDisposed)
return;
var v = _currentVersion;
if (v != null) {
DeleteVersion(v);
}
_currentVersion = null;
_isDisposed = true;
}
public void Complete() {
if (_isCompleted || _isDisposed)
return;
var v = _currentVersion;
if (v != null) {
PushVersion(v);
}
_currentVersion = null;
_isCompleted = true;
}
private void DeleteVersion(Version version) {
Console.WriteLine($"Version {version} deleted");
}
private void PushVersion(Version version) {
Console.WriteLine($"Version {version} pushed");
}
}
It will work, but it will not support nested scopes, which is not good, so to fix we need to store previous scope when starting new one, and restore it on Complete or Dispose:
public sealed class VersionScope : IDisposable {
private bool _isCompleted;
private bool _isDisposed;
private static readonly ThreadLocal<VersionChain> _versions = new ThreadLocal<VersionChain>();
public static Version CurrentVersion => _versions.Value?.Current;
public VersionScope(int version) {
var cur = _versions.Value;
// remember previous versions if any
_versions.Value = new VersionChain(new Version(version), cur);
}
public void Dispose() {
if (_isCompleted || _isDisposed)
return;
var cur = _versions.Value;
if (cur != null) {
DeleteVersion(cur.Current);
// restore previous
_versions.Value = cur.Previous;
}
_isDisposed = true;
}
public void Complete() {
if (_isCompleted || _isDisposed)
return;
var cur = _versions.Value;
if (cur != null) {
PushVersion(cur.Current);
// restore previous
_versions.Value = cur.Previous;
}
_isCompleted = true;
}
private void DeleteVersion(Version version) {
Console.WriteLine($"Version {version} deleted");
}
private void PushVersion(Version version) {
Console.WriteLine($"Version {version} pushed");
}
// just a class to store previous versions
private class VersionChain {
public VersionChain(Version current, VersionChain previous) {
Current = current;
Previous = previous;
}
public Version Current { get; }
public VersionChain Previous { get; }
}
}
That's already something you can work with. Sample usage (I use single thread, but if there were multiple threads doing this separately - they will not interfere with each other):
static void Main(string[] args) {
PrintCurrentVersion(); // no version
using (var s1 = new VersionScope(1)) {
PrintCurrentVersion(); // version 1
s1.Complete();
PrintCurrentVersion(); // no version, 1 is already completed
using (var s2 = new VersionScope(2)) {
using (var s3 = new VersionScope(3)) {
PrintCurrentVersion(); // version 3
} // version 3 deleted
PrintCurrentVersion(); // back to version 2
s2.Complete();
}
PrintCurrentVersion(); // no version, all completed or deleted
}
Console.ReadKey();
}
private static void PrintCurrentVersion() {
Console.WriteLine("Current version: " + VersionScope.CurrentVersion);
}
This however will not work when you are using async calls, because ThreadLocal is tied to a thread, but async method can span multiple threads. However, there is similar construct named AsyncLocal, which value will flow through asynchronous calls. So we can add constructor parameter to VersionScope indicating if we need async flow or not. Transaction scope works in a similar way - there is TransactionScopeAsyncFlowOption you pass into TransactionScope constructor indicating if it will flow through async calls.
Modified version looks like this:
public sealed class VersionScope : IDisposable {
private bool _isCompleted;
private bool _isDisposed;
private readonly bool _asyncFlow;
// thread local versions
private static readonly ThreadLocal<VersionChain> _tlVersions = new ThreadLocal<VersionChain>();
// async local versions
private static readonly AsyncLocal<VersionChain> _alVersions = new AsyncLocal<VersionChain>();
// to get current version, first check async local storage, then thread local
public static Version CurrentVersion => _alVersions.Value?.Current ?? _tlVersions.Value?.Current;
// helper method
private VersionChain CurrentVersionChain => _asyncFlow ? _alVersions.Value : _tlVersions.Value;
public VersionScope(int version, bool asyncFlow = false) {
_asyncFlow = asyncFlow;
var cur = CurrentVersionChain;
// remember previous versions if any
if (asyncFlow) {
_alVersions.Value = new VersionChain(new Version(version), cur);
}
else {
_tlVersions.Value = new VersionChain(new Version(version), cur);
}
}
public void Dispose() {
if (_isCompleted || _isDisposed)
return;
var cur = CurrentVersionChain;
if (cur != null) {
DeleteVersion(cur.Current);
// restore previous
if (_asyncFlow) {
_alVersions.Value = cur.Previous;
}
else {
_tlVersions.Value = cur.Previous;
}
}
_isDisposed = true;
}
public void Complete() {
if (_isCompleted || _isDisposed)
return;
var cur = CurrentVersionChain;
if (cur != null) {
PushVersion(cur.Current);
// restore previous
if (_asyncFlow) {
_alVersions.Value = cur.Previous;
}
else {
_tlVersions.Value = cur.Previous;
}
}
_isCompleted = true;
}
private void DeleteVersion(Version version) {
Console.WriteLine($"Version {version} deleted");
}
private void PushVersion(Version version) {
Console.WriteLine($"Version {version} pushed");
}
// just a class to store previous versions
private class VersionChain {
public VersionChain(Version current, VersionChain previous) {
Current = current;
Previous = previous;
}
public Version Current { get; }
public VersionChain Previous { get; }
}
}
Sample usage of scopes with async flow:
static void Main(string[] args) {
Test();
Console.ReadKey();
}
static async void Test() {
PrintCurrentVersion(); // no version
using (var s1 = new VersionScope(1, asyncFlow: true)) {
await Task.Delay(100);
PrintCurrentVersion(); // version 1
await Task.Delay(100);
s1.Complete();
await Task.Delay(100);
PrintCurrentVersion(); // no version, 1 is already completed
using (var s2 = new VersionScope(2, asyncFlow: true)) {
using (var s3 = new VersionScope(3, asyncFlow: true)) {
PrintCurrentVersion(); // version 3
} // version 3 deleted
await Task.Delay(100);
PrintCurrentVersion(); // back to version 2
s2.Complete();
}
await Task.Delay(100);
PrintCurrentVersion(); // no version, all completed or deleted
}
}
private static void PrintCurrentVersion() {
Console.WriteLine("Current version: " + VersionScope.CurrentVersion);
}
Use of IDisposable like this is somewhat questionable. (See Is it abusive to use IDisposable and "using" as a means for getting "scoped behavior" for exception safety?)
I, myself find it useful for some things. This is a pattern I use:
class LevelContext
{
private int _level;
public int CurrentLevel
{
get { return _level; }
set { _level = value < 0 ? 0 : value; }
}
public ILevel NewLevel(int depth = 1)
{
return new Level(this, depth);
}
/// <summary>
/// Provides an interface that calling code can use to handle level objects.
/// </summary>
public interface ILevel : IDisposable
{
LevelContext Owner { get; }
int Depth { get; }
void Close();
}
/// <summary>
/// Private class that provides an easy way to scope levels by allowing
/// them to participate in the "using" construct. Creation of a Level results in an
/// increase in owner's level, while disposal returns owner's level to what it was before.
/// </summary>
class Level : ILevel
{
public Level(LevelContext owner, int depth)
{
Owner = owner;
Depth = depth;
PreviousLevel = owner.CurrentLevel;
Owner.CurrentLevel += Depth;
}
public LevelContext Owner { get; private set; }
public int Depth { get; private set; }
public int PreviousLevel { get; private set; }
public void Close()
{
if (Owner != null)
{
Owner.CurrentLevel = PreviousLevel;
Owner = null;
}
}
void IDisposable.Dispose()
{
Close();
}
}
Then the calling code looks like this:
static void Main(string[] args)
{
var lc = new LevelContext();
Console.WriteLine(lc.CurrentLevel);
using (lc.NewLevel())
Console.WriteLine(lc.CurrentLevel);
Console.WriteLine(lc.CurrentLevel);
}
So in your case, you are correct - you need to create something that tracks the current version. That something should get updated when VersionScopes are created and disposed.

UWP Custom Video Effect

I'm developing an application for detecting motion within webcam frames.
For this, I'm using IBasicVideoEffect for extracting frames one by one from MediaCapture. I have created class CustomEffect which inherits IBasicVideoEffect. I have used OpenCV for motion detection, it is working fine. It is also giving me motion detection level. I want to raise event from CustomEffect if motion level is greater than threshold.
But for videoDefination code is:
var videoDefinition = new VideoEffectDefinition(typeof(CustomEffect).ToString());
Here for videoDefinition constructor it is asking for ClassID,
How can i get event from CustomEffect object.
I want to raise custom event from CustomEffect (eg.: MotionDetectedEvent )
Here is my CustomEffect class:
public sealed class CustomEffect : IBasicVideoEffect
{
private OpenCVHelper _helper;
private IPropertySet _configuration;
internal event EventHandler<EventArgs> MotionDetected;
public void SetProperties(IPropertySet configuration)
{
_configuration = configuration;
}
public void SetEncodingProperties(VideoEncodingProperties encodingProperties,
IDirect3DDevice device)
{
}
private bool IsToDetectMotion
{
get
{
object val;
if (_configuration != null &&
_configuration.TryGetValue("IsToDetectMotion", out val))
return (bool) val;
return false;
}
}
public void ProcessFrame(ProcessVideoFrameContext context)
{
var tempBitmap = context.OutputFrame.SoftwareBitmap;
context.InputFrame.SoftwareBitmap.CopyTo(tempBitmap);
var originalBitmap = SoftwareBitmap.Convert(tempBitmap, BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Straight);
var outputBitmap = new SoftwareBitmap(BitmapPixelFormat.Bgra8,
originalBitmap.PixelWidth, originalBitmap.PixelHeight,
BitmapAlphaMode.Straight);
if (!IsToDetectMotion)
{
context.InputFrame.SoftwareBitmap.CopyTo(context.OutputFrame.SoftwareBitmap);
return;
}
if (_helper == null)
_helper = new OpenCVHelper();
var level = _helper.MotionDetector(tempBitmap, outputBitmap);
RaiseMotionDetectedEvent();
Debug.WriteLine(level.ToString());
outputBitmap.CopyTo(context.OutputFrame.SoftwareBitmap);
}
private void RaiseMotionDetectedEvent()
{
if (MotionDetected != null)
MotionDetected(this, new EventArgs());
}
public void Close(MediaEffectClosedReason reason)
{
}
public void DiscardQueuedFrames()
{
}
public bool IsReadOnly { get; }
public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties
{
get
{
var encodingProperties = new VideoEncodingProperties();
encodingProperties.Subtype = "ARGB32";
return new List<VideoEncodingProperties> {encodingProperties};
// If the list is empty, the encoding type will be ARGB32.
// return new List<VideoEncodingProperties>();
}
}
public MediaMemoryTypes SupportedMemoryTypes { get; }
public bool TimeIndependent { get; }
}
//in Windows Runtime Component
public sealed class FrameArgs
{
public FrameArgs(int frameCount)
{
FrameCount = frameCount;
}
public int FrameCount
{ get; }
}
public sealed partial class CustomEffect
{
#region ProcessFrameCompleted
public EventHandler<Object> ProcessFrameCompleted
{
get
{
object val;
if (configuration != null && configuration.TryGetValue(nameof(ProcessFrameCompleted), out val))
{
return (EventHandler<Object>)val;
}
return null;
}
}
public void RaiseProcessFrameCompleted(FrameArgs args)
{
ProcessFrameCompleted?.Invoke(null, (Object)args);
}
#endregion
//call as necessary
//RaiseProcessFrameCompleted(new FrameArgs(frameCount));
}
//in your app
public static async Task<IMediaExtension> AddCustomEffect(MediaCapture mediaCapture, EventHandler<FrameArgs> callBack)
{
if (mediaCapture == null)
{
throw new ArgumentException("Parameter cannot be null", nameof(mediaCapture));
}
var videoEffectDefinition =
// ReSharper disable once AssignNullToNotNullAttribute
new VideoEffectDefinition(typeof(CustomEffect).FullName);
var videoEffect =
await mediaCapture.AddVideoEffectAsync(videoEffectDefinition, MediaStreamType.VideoPreview);
videoEffect.SetProperties(
new PropertySet()
{
{
"ProcessFrameCompleted",
new EventHandler<object>((sender, e) =>
{
var args = (FrameArgs)e;
int frameCount = args.FrameCount;
callBack?.Invoke(sender, args);
})
}
});
return videoEffect;
}

cleaning up unused locks in a NamedReaderWriterLocker

For doing asynchronous file IO I have made a class that lets me lock based upon a string key to prevent doing multiple writes at the same time to the same file or prevent having both writes and reads be done at the same time. The problem I face however is that its possible to grow the _lockDict by sending a different key each time. The old unused locks are not cleaned up but I do not know how I could do this in a threadsafe manner. This could potentially lead to a very big memory consumption.
The class that gives back a instance of a lock based upon a key:
public class AsyncNamedReaderWriterLocker
{
private readonly object _mutex = new object();
private readonly Dictionary<string, AsyncReaderWriterLock> _lockDict = new Dictionary<string, AsyncReaderWriterLock>();
public Task<IDisposable> EnterReaderLockAsync(string name)
{
var locker = GetLock(name);
return locker.EnterReaderLockAsync();
}
public Task<IDisposable> EnterWriterLockAsync(string name)
{
var locker = GetLock(name);
return locker.EnterWriterLockAsync();
}
private AsyncReaderWriterLock GetLock(string name)
{
lock (_mutex)
{
if (!_lockDict.TryGetValue(name, out AsyncReaderWriterLock locker))
{
locker = new AsyncReaderWriterLock();
_lockDict.Add(name, locker);
}
return locker;
}
}
And the lock itself (idea from: https://blogs.msdn.microsoft.com/pfxteam/2012/02/12/building-async-coordination-primitives-part-7-asyncreaderwriterlock/):
public class AsyncReaderWriterLock
{
private readonly Queue<TaskCompletionSource<IDisposable>> _writerQueue = new Queue<TaskCompletionSource<IDisposable>>();
private readonly Queue<TaskCompletionSource<IDisposable>> _readerQueue = new Queue<TaskCompletionSource<IDisposable>>();
private readonly WriterLocker _writerLocker;
private readonly ReaderLocker _readerLocker;
private readonly object _mutex = new object();
private int _locksHeld;
public AsyncReaderWriterLock()
{
_writerLocker = new WriterLocker(this);
_readerLocker = new ReaderLocker(this);
}
public Task<IDisposable> EnterReaderLockAsync()
{
lock (_mutex)
{
if (_locksHeld >= 0 && _writerQueue.Count == 0)
{
_locksHeld++;
return Task.FromResult<IDisposable>(_readerLocker);
}
var tcs = new TaskCompletionSource<IDisposable>();
_readerQueue.Enqueue(tcs);
return tcs.Task;
}
}
public Task<IDisposable> EnterWriterLockAsync()
{
lock (_mutex)
{
if (_locksHeld == 0)
{
_locksHeld = -1;
return Task.FromResult<IDisposable>(_writerLocker);
}
var tcs = new TaskCompletionSource<IDisposable>();
_writerQueue.Enqueue(tcs);
return tcs.Task;
}
}
private void ReleaseLocks()
{
if (_locksHeld != 0)
return;
// Give priority to writers.
if (_writerQueue.Count != 0)
{
_locksHeld = -1;
var tcs = _writerQueue.Dequeue();
tcs.TrySetResult(_writerLocker);
return;
}
// Then to readers.
while (_readerQueue.Count != 0)
{
var tcs = _readerQueue.Dequeue();
tcs.TrySetResult(_readerLocker);
++_locksHeld;
}
}
private void ReleaseReaderLock()
{
lock (_mutex)
{
_locksHeld--;
ReleaseLocks();
}
}
private void ReleaseWriterLock()
{
lock (_mutex)
{
_locksHeld = 0;
ReleaseLocks();
}
}
private class ReaderLocker : IDisposable
{
private readonly AsyncReaderWriterLock _asyncReaderWriterLock;
internal ReaderLocker(AsyncReaderWriterLock asyncReaderWriterLock)
{
_asyncReaderWriterLock = asyncReaderWriterLock;
}
public void Dispose()
{
_asyncReaderWriterLock.ReleaseReaderLock();
}
}
private class WriterLocker : IDisposable
{
private readonly AsyncReaderWriterLock _asyncReaderWriterLock;
internal WriterLocker(AsyncReaderWriterLock asyncReaderWriterLock)
{
_asyncReaderWriterLock = asyncReaderWriterLock;
}
public void Dispose()
{
_asyncReaderWriterLock.ReleaseWriterLock();
}
}
}
Example of using the AsyncNamedReaderWriterLocker class:
public class AsyncFileIO
{
private static readonly AsyncNamedReaderWriterLocker AsyncNamedReaderWriterLocker = new AsyncNamedReaderWriterLocker();
public async Task CreateFile(string filename, byte[] data)
{
using (await AsyncNamedReaderWriterLocker.EnterWriterLockAsync(filename))
{
var directory = Path.GetDirectoryName(filename);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
using (var stream = File.Create(filename))
{
await stream.WriteAsync(data, 0, data.Length);
}
}
}
public async Task<byte[]> ReadFile(string filename)
{
using (await AsyncNamedReaderWriterLocker.EnterReaderLockAsync(filename))
{
if (!File.Exists(filename)) return null;
using (var stream = File.OpenRead(filename))
{
var data = new byte[stream.Length];
await stream.ReadAsync(data, 0, 0);
return data;
}
}
}
public async Task DeleteFile(string filename)
{
using (await AsyncNamedReaderWriterLocker.EnterWriterLockAsync(filename))
{
var directoryName = Path.GetDirectoryName(filename);
if (!Directory.Exists(directoryName)) return;
File.Delete(filename);
}
}
}
Note iam mostly interested in this for learning purposes and fully realize that this is overkill for most applications.
At first I was running into deadlocks when trying to synchronize the async named lock in order to remove the unused locks. This was because not all locks were released in the same order. Fixing this needed a specialized AsyncReaderWriter lock just for use with this async named lock. It now looks like this:
public class NamedAsyncReaderWriterLockController<TKey>
{
private readonly object _mutex = new object();
private readonly Dictionary<TKey, NamedAsyncReaderWriterLock> _lockDict = new Dictionary<TKey, NamedAsyncReaderWriterLock>();
public Task<IDisposable> EnterReaderLockAsync(TKey name)
{
lock (_mutex)
{
var locker = GetLock(name);
return locker.EnterReaderLockAsync();
}
}
public Task<IDisposable> EnterWriterLockAsync(TKey name)
{
lock (_mutex)
{
var locker = GetLock(name);
return locker.EnterWriterLockAsync();
}
}
private NamedAsyncReaderWriterLock GetLock(TKey name)
{
NamedAsyncReaderWriterLock locker;
if (!_lockDict.TryGetValue(name, out locker))
{
locker = new NamedAsyncReaderWriterLock(this, name, _mutex);
_lockDict.Add(name, locker);
}
return locker;
}
private void RemoveLock(TKey name)
{
_lockDict.Remove(name);
}
private class NamedAsyncReaderWriterLock
{
private readonly TKey _name;
private readonly NamedAsyncReaderWriterLockController<TKey> _namedAsyncReaderWriterLockController;
private readonly Queue<TaskCompletionSource<IDisposable>> _writerQueue = new Queue<TaskCompletionSource<IDisposable>>();
private readonly Queue<TaskCompletionSource<IDisposable>> _readerQueue = new Queue<TaskCompletionSource<IDisposable>>();
private readonly NamedWriterLock _namedWriterLock;
private readonly NamedReaderLock _namedReaderLock;
private readonly object _mutex = new object();
private readonly object _releaseMutex;
private int _locksHeld;
public NamedAsyncReaderWriterLock(NamedAsyncReaderWriterLockController<TKey> namedAsyncReaderWriterLockController, TKey name, object releaseMutex)
{
_namedWriterLock = new NamedWriterLock(this);
_namedReaderLock = new NamedReaderLock(this);
_releaseMutex = releaseMutex;
_name = name;
_namedAsyncReaderWriterLockController = namedAsyncReaderWriterLockController;
}
public Task<IDisposable> EnterReaderLockAsync()
{
lock (_mutex)
{
if (_locksHeld >= 0 && _writerQueue.Count == 0)
{
_locksHeld++;
return Task.FromResult<IDisposable>(_namedReaderLock);
}
var tcs = new TaskCompletionSource<IDisposable>();
_readerQueue.Enqueue(tcs);
return tcs.Task;
}
}
public Task<IDisposable> EnterWriterLockAsync()
{
lock (_mutex)
{
if (_locksHeld == 0)
{
_locksHeld = -1;
return Task.FromResult<IDisposable>(_namedWriterLock);
}
var tcs = new TaskCompletionSource<IDisposable>();
_writerQueue.Enqueue(tcs);
return tcs.Task;
}
}
private void ReleaseLocks()
{
if (_locksHeld != 0)
return;
// Give priority to writers.
if (_writerQueue.Count != 0)
{
_locksHeld = -1;
var tcs = _writerQueue.Dequeue();
tcs.TrySetResult(_namedWriterLock);
return;
}
// Then to readers.
while (_readerQueue.Count != 0)
{
var tcs = _readerQueue.Dequeue();
tcs.TrySetResult(_namedReaderLock);
++_locksHeld;
}
if (_locksHeld == 0) _namedAsyncReaderWriterLockController.RemoveLock(_name);
}
private void ReleaseReaderLock()
{
lock (_releaseMutex)
{
lock (_mutex)
{
_locksHeld--;
ReleaseLocks();
}
}
}
private void ReleaseWriterLock()
{
lock (_releaseMutex)
{
lock (_mutex)
{
_locksHeld = 0;
ReleaseLocks();
}
}
}
private class NamedReaderLock : IDisposable
{
private readonly NamedAsyncReaderWriterLock _namedAsyncReaderWriterLock;
internal NamedReaderLock(NamedAsyncReaderWriterLock namedAsyncReaderWriterLock)
{
_namedAsyncReaderWriterLock = namedAsyncReaderWriterLock;
}
public void Dispose()
{
_namedAsyncReaderWriterLock.ReleaseReaderLock();
}
}
private class NamedWriterLock : IDisposable
{
private readonly NamedAsyncReaderWriterLock _namedAsyncReaderWriterLock;
internal NamedWriterLock(NamedAsyncReaderWriterLock namedAsyncReaderWriterLock)
{
_namedAsyncReaderWriterLock = namedAsyncReaderWriterLock;
}
public void Dispose()
{
_namedAsyncReaderWriterLock.ReleaseWriterLock();
}
}
}

Best structure for Process Host of a WorkflowApplication - WF 4.5

I'm using Windows Workflow and am hosting a Process host in a normal C# Class file which is used by WCF classes (hosted in IIS). The host is not hosted as a service, it is a normal class. I've read lots of articles and looked at many example and created my class accordingly. It seems to work perfectly in a single user test situation but fails occassionally in multi-user situations. Please can you advise if I'm on the right track or doing something fundamentally wrong? Thanks.
Below is my code:
public sealed class PurchaseRequisitionProcessHost : IPurchaseRequisitionProcessHost
{
public event PurchaseRequisitionWfIdleEventHandler PurchaseRequisitionWf_Idle;
public delegate void PurchaseRequisitionWfIdleEventHandler(PurchaseRequisitionWorkflowApplicationIdleEventArgs e);
private static volatile PurchaseRequisitionProcessHost _instance = null;
private static object syncRoot = new Object();
private AutoResetEvent instanceUnloaded = null;
public static PurchaseRequisitionProcessHost Instance
{
get
{
if(_instance == null)
{
lock(syncRoot)
{
if(_instance == null)
{
_instance = new PurchaseRequisitionProcessHost();
}
}
}
return _instance;
}
}
private PurchaseRequisitionProcessHost()
{
}
protected void OnPurchaseRequisitionWf_Idle(PurchaseRequisitionWorkflowApplicationIdleEventArgs e)
{
// Make a temporary copy of the event to avoid possibility of
// a race condition if the last subscriber unsubscribes
// immediately after the null check and before the event is raised.
PurchaseRequisitionWfIdleEventHandler handler = PurchaseRequisitionWf_Idle;
// Event will be null if there are no subscribers
if(handler != null)
{
// Use the () operator to raise the event.
handler(e);
}
}
// executed when instance goes idle
public void OnIdle(WorkflowApplicationIdleEventArgs e)
{
PurchaseRequisitionWorkflowApplicationIdleEventArgs args = new PurchaseRequisitionWorkflowApplicationIdleEventArgs();
if(e.Bookmarks != null && e.Bookmarks.Any())
{
args.BookMarkName = e.Bookmarks[0].BookmarkName;
string prNumber = args.BookMarkName.Substring(args.BookMarkName.IndexOf("_") + 1);
prNumber = prNumber.Substring(0, prNumber.IndexOf("_"));
args.PrNumber = prNumber;
}
args.InstanceId = e.InstanceId;
PurchaseRequisitionWf_Idle(args);
}
public PersistableIdleAction OnIdleAndPersistable(WorkflowApplicationIdleEventArgs e)
{
return PersistableIdleAction.Unload;
}
public void OnWorkFlowUnload(WorkflowApplicationEventArgs e)
{
CleanUpSubscribedEvents();
instanceUnloaded.Set();
}
//// executed when instance has completed
public void OnWorkflowCompleted(WorkflowApplicationCompletedEventArgs e)
{
CleanUpSubscribedEvents();
instanceUnloaded.Set();
}
//// executed when instance has aborted
public void OnWorkflowAborted(WorkflowApplicationAbortedEventArgs e)
{
CleanUpSubscribedEvents();
instanceUnloaded.Set();
}
// executed when unhandled exception
public UnhandledExceptionAction OnWorkflowUnhandledException(WorkflowApplicationUnhandledExceptionEventArgs e)
{
Helpers.Common.Common.logger.Error(string.Format("Unhandled WorkflowException for instance {0}", e.InstanceId), e.UnhandledException);
CleanUpSubscribedEvents();
instanceUnloaded.Set();
return UnhandledExceptionAction.Terminate;
}
private void CleanUpSubscribedEvents()
{
if(PurchaseRequisitionWf_Idle != null)
{
Delegate[] clientList = PurchaseRequisitionWf_Idle.GetInvocationList();
if(clientList != null && clientList.Any())
{
foreach(Delegate d in clientList)
{
PurchaseRequisitionWf_Idle -= (d as PurchaseRequisitionWfIdleEventHandler);
}
}
}
}
#region IPurchaseRequisitionProcessHost Members
public WorkflowApplication CreateAndRun(PurchaseRequisition pr, string uploadsPath)
{
return CreateAndRun(pr, uploadsPath, null);
}
// creates a workflow application, binds parameters, links extensions and run it
public WorkflowApplication CreateAndRun(PurchaseRequisition pr, string uploadsPath, PurchaseRequisitionWfIdleEventHandler idleDelegate)
{
WorkflowApplication instance = null;
try
{
instanceUnloaded = new AutoResetEvent(false);
using(var instanceStore = new DisposableStore())
{
// input parameters for the WF program
IDictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("PurchaseRequisition", pr);
inputs.Add("UploadsPath", uploadsPath);
instance = new WorkflowApplication(new PurchaseRequisitionProcessWorkflow(), inputs);
instance.InstanceStore = instanceStore.Store;
// Add the custom tracking participant
instance.Extensions.Add(new CustomTrackingParticipant());
instance.PersistableIdle += OnIdleAndPersistable;
instance.Completed += OnWorkflowCompleted;
instance.Unloaded += OnWorkFlowUnload;
instance.Aborted += OnWorkflowAborted;
instance.OnUnhandledException += OnWorkflowUnhandledException;
instance.Idle += OnIdle;
if(idleDelegate != null)
{
PurchaseRequisitionWf_Idle -= idleDelegate;
PurchaseRequisitionWf_Idle += idleDelegate;
}
// continue executing this instance
instance.Run();
instanceUnloaded.WaitOne();
}
}
catch(Exception ex)
{
Helpers.Common.Common.logger.Error(ex.Message, ex);
}
return instance;
}
public BookmarkResumptionResult SubmitApprovalDecision(Guid instanceId, string prNumber, string approvalSource, bool approved)
{
return SubmitApprovalDecision(instanceId, prNumber, approvalSource, approved, null);
}
public BookmarkResumptionResult SubmitApprovalDecision(Guid instanceId, string prNumber, string approvalSource, bool approved, PurchaseRequisitionWfIdleEventHandler idleDelegate)
{
BookmarkResumptionResult brr = BookmarkResumptionResult.NotReady;
WorkflowApplication instance = this.LoadInstance(instanceId, idleDelegate);
string bookmarkName = string.Format("Pr_{0}_waitingForApprovalFrom_{1}", prNumber, approvalSource);
if(instance != null && instance.GetBookmarks().Count > 0)
{
brr = instance.ResumeBookmark(bookmarkName, approved);
}
instanceUnloaded.WaitOne();
return brr;
}
public BookmarkResumptionResult ReceiptPrGoods(Guid instanceId, PurchaseRequisitionReceipt pr)
{
return ReceiptPrGoods(instanceId, pr, null);
}
public BookmarkResumptionResult ReceiptPrGoods(Guid instanceId, PurchaseRequisitionReceipt pr, PurchaseRequisitionWfIdleEventHandler idleDelegate)
{
BookmarkResumptionResult brr = BookmarkResumptionResult.NotReady;
WorkflowApplication instance = this.LoadInstance(instanceId, idleDelegate);
string bookmarkName = string.Format("Pr_{0}_waitingForReceiptOfGoods", pr.PrNumber);
if(instance != null && instance.GetBookmarks().Count > 0)
{
brr = instance.ResumeBookmark(bookmarkName, pr);
}
instanceUnloaded.WaitOne();
return brr;
}
public void UnsubscribePurchaseRequisitionWfIdleEvent(PurchaseRequisitionWfIdleEventHandler idleDelegate)
{
PurchaseRequisitionWf_Idle -= idleDelegate;
}
#endregion
#region IProcessHost Members
// returns true if the instance is waiting (has pending bookmarks)
public bool IsInstanceWaiting(Guid instanceId)
{
bool isWaiting = false;
WorkflowApplication instance = LoadInstance(instanceId);
if(instance != null)
{
isWaiting = instance.GetBookmarks().Count > 0;
}
return isWaiting;
}
public WorkflowApplication LoadInstance(Guid instanceId)
{
return LoadInstance(instanceId, null);
}
// load and resume a workflow instance. If the instance is in memory,
// will return the version from memory. If not, will load it from the
// persistent store
public WorkflowApplication LoadInstance(Guid instanceId, PurchaseRequisitionWfIdleEventHandler idleDelegate)
{
WorkflowApplication instance = null;
try
{
instanceUnloaded = new AutoResetEvent(false);
using(var instanceStore = new DisposableStore())
{
instance = new WorkflowApplication(new PurchaseRequisitionProcessWorkflow());
WorkflowApplicationInstance wfAppInstance = WorkflowApplication.GetInstance(instanceId, instanceStore.Store);
// Add the custom tracking participant
instance.Extensions.Add(new CustomTrackingParticipant());
instance.PersistableIdle += OnIdleAndPersistable;
instance.Completed += OnWorkflowCompleted;
instance.Unloaded += OnWorkFlowUnload;
instance.Aborted += OnWorkflowAborted;
instance.OnUnhandledException += OnWorkflowUnhandledException;
instance.Idle += OnIdle;
if(idleDelegate != null)
{
PurchaseRequisitionWf_Idle -= idleDelegate;
PurchaseRequisitionWf_Idle += idleDelegate;
}
try
{
instance.Load(wfAppInstance);
}
catch(InstanceOwnerException)
{
}
}
}
catch(Exception ex)
{
Helpers.Common.Common.logger.Error(ex.Message, ex);
}
return instance;
}
#endregion
}
public class PurchaseRequisitionWorkflowApplicationIdleEventArgs : EventArgs
{
public string PrNumber
{
get;
set;
}
public Guid InstanceId
{
get;
set;
}
public string BookMarkName
{
get;
set;
}
}
public class DisposableStore : IDisposable
{
private string connectionString = Properties.Settings.Default.SqlPersistenceStoreDbConnectionString;
private SqlWorkflowInstanceStore _Store = null;
private InstanceHandle _handle = null;
public SqlWorkflowInstanceStore Store
{
get
{
if(_Store == null)
{
_Store = new SqlWorkflowInstanceStore(connectionString);
_Store.HostLockRenewalPeriod = TimeSpan.FromSeconds(5);
_Store.MaxConnectionRetries = 25;
_Store.RunnableInstancesDetectionPeriod = TimeSpan.FromSeconds(10);
_Store.InstanceCompletionAction = InstanceCompletionAction.DeleteAll;
_Store.InstanceLockedExceptionAction = InstanceLockedExceptionAction.BasicRetry;
_handle = _Store.CreateInstanceHandle();
CreateWorkflowOwnerCommand createOwnerCmd = new CreateWorkflowOwnerCommand();
InstanceView view = _Store.Execute(_handle, createOwnerCmd, TimeSpan.FromSeconds(5));
_Store.DefaultInstanceOwner = view.InstanceOwner;
}
return _Store;
}
}
public void Dispose()
{
_handle.Free();
_handle = _Store.CreateInstanceHandle(_Store.DefaultInstanceOwner);
try
{
_Store.Execute(_handle, new DeleteWorkflowOwnerCommand(), TimeSpan.FromSeconds(10));
}
catch(InstancePersistenceCommandException ex)
{
Helpers.Common.Common.logger.Info(ex.Message);
}
catch(InstanceOwnerException ex)
{
Helpers.Common.Common.logger.Info(ex.Message);
}
catch(OperationCanceledException ex)
{
Helpers.Common.Common.logger.Info(ex.Message);
}
catch(Exception ex)
{
Helpers.Common.Common.logger.Info(ex.Message);
}
finally
{
_handle.Free();
_Store.DefaultInstanceOwner = null;
_handle = null;
_Store = null;
}
}
}

Deadlock detection ordered lock implementation

I am trying to implement a mechanism where lock ordering is automatically checked and an exception is thrown when locks are acquired out of order at runtime to avoid deadlocks. A reference implementation is below. Please let me know if you see any issues with this implementation. Many thanks.
public class someResource
{
private OrderedLock lock1 = new OrderedLock(1);
private OrderedLock lock2 = new OrderedLock(2);
public void lockInOrder()
{
lock1.AcquireWriteLock();
lock2.AcquireWriteLock();
// do something
lock1.ReleaseWriteLock();
lock2.ReleaseWriteLock();
}
public void lockOutOfOrder()
{
lock2.AcquireReadLock();
lock1.AcquireReadLock(); // throws exception
// read something
lock2.ReleaseReadLock();
lock1.ReleaseReadLock();
}
}
public class OrderedLock : IDisposable
{
private static readonly ConcurrentDictionary<int, object> createdLocks = new ConcurrentDictionary<int, object>();
[ThreadStatic]
private static ISet<int> acquiredLocks;
private readonly ThreadLocal<int> refCount = new ThreadLocal<int>(false);
private readonly ReaderWriterLockSlim locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
private readonly int id;
/// <exception cref="InvalidOperationException">Duplicate identifier detected</exception>
public OrderedLock(int id)
{
if (!createdLocks.TryAdd(id, null))
{
throw new InvalidOperationException("Duplicate identifier detected");
}
this.id = id;
this.refCount.Value = 0;
}
public void AcquireReadLock()
{
this.CheckLockOrder();
this.locker.EnterReadLock();
}
public void AcquireWriteLock()
{
this.CheckLockOrder();
this.locker.EnterWriteLock();
}
public void ReleaseReadLock()
{
this.refCount.Value--;
this.locker.ExitReadLock();
if (this.refCount.Value == 0)
{
acquiredLocks.Remove(this.id);
}
}
public void ReleaseWriteLock()
{
this.refCount.Value--;
this.locker.ExitWriteLock();
if (this.refCount.Value == 0)
{
acquiredLocks.Remove(this.id);
}
}
public void Dispose()
{
while (this.locker.IsWriteLockHeld)
{
this.ReleaseWriteLock();
}
while (this.locker.IsReadLockHeld)
{
ReleaseReadLock();
}
this.locker.Dispose();
this.refCount.Dispose();
GC.SuppressFinalize(this);
}
/// <exception cref="InvalidOperationException">Invalid order of locking detected</exception>
private void CheckLockOrder()
{
if (acquiredLocks == null)
{
acquiredLocks = new HashSet<int>();
}
if (!acquiredLocks.Contains(this.id))
{
if (acquiredLocks.Any() && acquiredLocks.Max() > this.id)
{
throw new InvalidOperationException("Invalid order of locking detected");
}
acquiredLocks.Add(this.id);
}
this.refCount.Value++;
}
}

Categories