I'm not exactly sure how to address this issue. I have a mutex that is declared as such:
public class MyNamedLock
{
private Mutex mtx;
private string _strLkName;
public MyNamedLock(string strLockName)
{
_strLkName = strLockName;
//...
mtx = new Mutex(false, _strLkName, out bCreatedNew, mSec);
}
public bool enterLockWithTimeout(int nmsWait = 30 * 1000)
{
_nmsWaitLock = nmsWait;
//Wait
return mtx.WaitOne(nmsWait);
}
public void leaveLock()
{
_nmsWaitLock = 0;
//Release it
mtx.ReleaseMutex();
}
}
Then it is used in an ASP.NET page as such:
public class MyClass
{
private MyNamedLock gl;
public MyClass()
{
gl = new MyNamedLock("lock name");
}
public void funct()
{
try
{
//Enter lock
if (gl.enterLockWithTimeout())
{
//Do work
}
else
throw new Exception("Failed to enter lock");
}
finally
{
//Leave lock
gl.leaveLock();
}
}
}
This code doesn't give me any trouble in my dev environment but in the production it sometimes throws this exception:
Object synchronization method was called from an unsynchronized block
of code.
The description is kinda vague, but just doing the trace I found out that the exception is raised at the mtx.ReleaseMutex(); part. What does it mean and how to fix it?
You have some issues on your class, and on the way you use it.
You must release the mutex only if you have previous locked (and this is your error)
You need to Close and Dispose your opened mutex
Also is better to create it just before you going to use it and not when you create you class MyClass.
So I suggest at first look to change your class as:
public class MyNamedLock
{
private Mutex mtx = null;
private string _strLkName;
// to know if finally we get lock
bool cNeedToBeRelease = false;
public MyNamedLock(string strLockName)
{
_strLkName = strLockName;
//...
mtx = new Mutex(false, _strLkName, out bCreatedNew, mSec);
}
public bool enterLockWithTimeout(int nmsWait = 30 * 1000)
{
_nmsWaitLock = nmsWait;
bool cLock = false;
try
{
cLock = mtx.WaitOne(nmsWait, false);
cNeedToBeRelease = cLock;
}
catch (AbandonedMutexException)
{
// http://stackoverflow.com/questions/654166/wanted-cross-process-synch-that-doesnt-suffer-from-abandonedmutexexception
// http://msdn.microsoft.com/en-us/library/system.threading.abandonedmutexexception.aspx
cNeedToBeRelease = true;
}
catch (Exception x)
{
// log the error
Debug.Fail("Check the reason of fail:" + x.ToString());
}
return cLock;
}
public void leaveLock()
{
_nmsWaitLock = 0;
if (mtx != null)
{
if (cNeedToBeRelease)
{
try
{
mtx.ReleaseMutex();
cNeedToBeRelease = false;
}
catch (Exception x)
{
Debug.Fail("Check the reason of fail:" + x.ToString());
}
}
mtx.Close();
mtx.Dispose();
mtx = null;
}
}
}
This the way you must call that class:
public class MyClass
{
public MyClass()
{
}
public void funct()
{
var gl = new MyNamedLock("lock name");
try
{
//Enter lock
if (gl.enterLockWithTimeout())
{
//Do work
}
else
throw new Exception("Failed to enter lock");
}
finally
{
//Leave lock
gl.leaveLock();
}
}
}
In your finally block you're releasing the mutex regardless of whether you actually acquired it in your try block.
In
try
{
//Enter lock
if (gl.enterLockWithTimeout())
{
//Do work
}
else throw new Exception("Failed to enter lock");
}
finally
{
//Leave lock
gl.leaveLock();
}
if gl.enterLockWithTimeout returns false, you will throw an exception but then try to release the lock in the finally block.
Related
I am trying to debug some old code in a product running on a Windows Embedded Compact 7 and .NET CF 3.5. I do suspect a deadlock scenario but can't figure out the reason. The originating code seems to be a class (code below) encapsulating a thread and an AutoResetEvent. It uses lock and Monitor.Enter to acquire lock for thread safety.
Is there something obvious in the following code potentially causing a deadlock that i would be missing?
//constructor
public SequentialThreadWorker(
string threadName,
ThreadPriority threadPriority)
{
_workerThread = new Thread(OnThreadStart)
{
Name = threadName,
Priority = threadPriority,
IsBackground = true
};
_waitHandle = new AutoResetEvent(false);
_actionsQueue = new Queue<Action>();
_workerThread.Start();
}
//external multi-threaded code can enqueue task using the following method
public void Enqueue(Action action)
{
lock (_lock)
{
_actionsQueue.Enqueue(action);
_waitHandle.Set();
}
}
//ThreadStart code depiling action from the queue action to execute
private void OnThreadStart()
{
while (_run)
{
Action actionData = null;
Monitor.Enter(_lock);
try
{
if (_actionsQueue.Count > 0)
{
actionData = _actionsQueue.Dequeue();
if (_actionsQueue.Count > 0)
_waitHandle.Set();
else
_waitHandle.Reset();
}
}
catch (Exception e)
{
//do log
}
finally
{
Monitor.Exit(_lock);
}
if (actionData != null)
{
try
{
actionData();
}
catch (Exception e)
{
//do log
}
}
_waitHandle.WaitOne();
}
}
}
}
Suppose I have a Singleton that loads resources into memory when created, and performs operation on the data when callings its methods.
Now suppose, that I want to have the ability to tell the Singleton to release those resources, as I don't expect to be using them in the near future, but also be able to load those resources back in, when the time comes. And I want it all to be thread safe.
What would be the best way to aproach this problem?
Would this example work?:
// Singleton implementation
...
private IDisposable resource;
private bool loadingResources;
private IDisposable Resource {
get => resource ?? throw new CustomException();
}
// Method A
public void A() {
var resource = Resource; // Throws CustomException if resource is null
// Do stuff
}
// Method B
public void B() {
var resource = Resource;
// Do stuff
}
public void ReleaseResources() {
if (resource != null)
lock (thislock) {
//resource.Dispose();
resource = null;
}
}
public void LoadResources() {
if (!loadingResources && resource == null)
lock (thislock)
if (!loadingResources && resource == null)
{
loadingResources = true;
// Load resources
resource = CreateResource();
loadingResources = false;
}
}
I would suggest separating the resource handling from the actual usage. Assuming the resource requires disposal this could look something like:
public class DisposableWrapper<T> where T : IDisposable
{
private readonly Func<T> resourceFactory;
private T resource;
private bool constructed;
private object lockObj = new object();
private int currentUsers = 0;
public DisposableWrapper(Func<T> resourceFactory)
{
this.resourceFactory = resourceFactory;
}
public O Run<O>(Func<T, O> func)
{
lock (lockObj)
{
if (!constructed)
{
resource = resourceFactory();
constructed = true;
}
currentUsers++;
}
try
{
return func(resource);
}
catch
{
return default;
}
finally
{
Interlocked.Decrement(ref currentUsers);
}
}
public void Run(Action<T> action)
{
lock (lockObj)
{
if (!constructed)
{
resource = resourceFactory();
constructed = true;
}
currentUsers++;
}
try
{
action(resource);
}
finally
{
Interlocked.Decrement(ref currentUsers);
}
}
public bool TryRelease()
{
lock (lockObj)
{
if (currentUsers == 0 && constructed)
{
constructed = false;
resource.Dispose();
resource = default;
return true;
}
return false;
}
}
}
If the resource does not require disposal I would suggest to instead use lazy<T>. Releasing resources would simply mean replacing the existing lazy object with a new one. Letting the old object be cleaned up by the garbage collector.
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;
}
}
}
try
{
using (response = (HttpWebResponse)request.GetResponse())
// Exception is not caught by outer try!
}
catch (Exception ex)
{
// Log
}
EDIT:
// Code for binding IP address:
ServicePoint servicePoint = ServicePointManager.FindServicePoint(uri);
servicePoint.BindIPEndPointDelegate = new BindIPEndPoint(Bind);
//
private IPEndPoint Bind(ServicePoint servicePoint, IPEndPoint remoteEndPoint, int retryCount)
{
IPAddress address;
if (retryCount < 3)
address = IPAddress.Parse("IPAddressHere");
else
{
address = IPAddress.Any;
throw new Exception("IP is not available,"); // This exception is not caught
}
return new IPEndPoint(address, 0);
}
I could imagine this can happen if you are creating a separate thread within the using block. If an exception is thrown there, be sure to handle it there as well. Otherwise, the outer catch block in this case won't be able to handle it.
class TestClass : IDisposable
{
public void GetTest()
{
throw new Exception("Something bad happened"); // handle this
}
public void Dispose()
{
}
}
class Program
{
static void Main(string[] args)
{
try
{
using (TestClass t = new TestClass())
{
Thread ts = new Thread(new ThreadStart(t.GetTest));
ts.Start();
}
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
}
}
Do you have more code after the using? The using needs one statement or a block { } after the using statement. In the example below any exception inside the using statement will be caught with the try..catch block.
try
{
using (response = (HttpWebResponse)request.GetResponse())
{
....
}
}
catch (Exception ex)
{
}
This works fine. You'll see an exception getting printed by the Console.WriteLine()
class Program
{
static void Main(string[] args)
{
Foo foo = new Foo();
try
{
using (Bar bar = foo.CreateBar())
{
}
}
catch(Exception exception)
{
Console.WriteLine(exception.Message);
}
}
}
public class Foo
{
public Bar CreateBar()
{
throw new ApplicationException("Something went wrong.");
}
}
public class Bar : IDisposable
{
public void Dispose()
{
}
}
And if you meant that the exception gets thrown inside the using, this works fine to. This will also generate a Console statement:
class Program
{
static void Main(string[] args)
{
Foo foo = new Foo();
try
{
using (Bar bar = foo.CreateBar())
{
throw new ApplicationException("Something wrong inside the using.");
}
}
catch(Exception exception)
{
Console.WriteLine(exception.Message);
}
}
}
public class Foo
{
public Bar CreateBar()
{
return new Bar();
// throw new ApplicationException("Something went wrong.");
}
}
public class Bar : IDisposable
{
public void Dispose()
{
}
}
The using keyword is the same as try-catch-finally, http://msdn.microsoft.com/en-us/library/yh598w02.aspx. Basically, you have a try-catch-finally nested inside of a try-catch which is why you're probably so confused.
You could just do this instead...
class Program
{
static void Main(string[] args)
{
HttpWebResponse response = new HttpWebResponse();
try
{
response.GetResponse();
}
catch (Exception ex)
{
//do something with the exception
}
finally
{
response.Dispose();
}
}
}
My objective is a convention for thread-safe functionality and exception handling within my application. I'm relatively new to the concept of thread management/multithreading. I am using .NET 3.5
I wrote the following helper method to wrap all my locked actions after reading this article http://blogs.msdn.com/b/ericlippert/archive/2009/03/06/locks-and-exceptions-do-not-mix.aspx, which was linked in response to this question, Monitor vs lock.
My thought is that if I use this convention consistently in my application, it will be easier to write thread-safe code and to handle errors within thread safe code without corrupting the state.
public static class Locking
{
private static readonly Dictionary<object,bool> CorruptionStateDictionary = new Dictionary<object, bool>();
private static readonly object CorruptionLock = new object();
public static bool TryLockedAction(object lockObject, Action action, out Exception exception)
{
if (IsCorrupt(lockObject))
{
exception = new LockingException("Cannot execute locked action on a corrupt object.");
return false;
}
exception = null;
Monitor.Enter(lockObject);
try
{
action.Invoke();
}
catch (Exception ex)
{
exception = ex;
}
finally
{
lock (CorruptionLock) // I don't want to release the lockObject until its corruption-state is updated.
// As long as the calling class locks the lockObject via TryLockedAction(), this should work
{
Monitor.Exit(lockObject);
if (exception != null)
{
if (CorruptionStateDictionary.ContainsKey(lockObject))
{
CorruptionStateDictionary[lockObject] = true;
}
else
{
CorruptionStateDictionary.Add(lockObject, true);
}
}
}
}
return exception == null;
}
public static void Uncorrupt(object corruptLockObject)
{
if (IsCorrupt(corruptLockObject))
{
lock (CorruptionLock)
{
CorruptionStateDictionary[corruptLockObject] = false;
}
}
else
{
if(!CorruptionStateDictionary.ContainsKey(corruptLockObject))
{
throw new LockingException("Uncorrupt() is not valid on object that have not been corrupted.");
}
else
{
// The object has previously been uncorrupted.
// My thought is to ignore the call.
}
}
}
public static bool IsCorrupt(object lockObject)
{
lock(CorruptionLock)
{
return CorruptionStateDictionary.ContainsKey(lockObject) && CorruptionStateDictionary[lockObject];
}
}
}
I use a LockingException class for ease of debugging.
public class LockingException : Exception
{
public LockingException(string message) : base(message) { }
}
Here is an example usage class to show how I intend to use this.
public class ExampleUsage
{
private readonly object ExampleLock = new object();
public void ExecuteLockedMethod()
{
Exception exception;
bool valid = Locking.TryLockedAction(ExampleLock, ExecuteMethod, out exception);
if (!valid)
{
bool revalidated = EnsureValidState();
if (revalidated)
{
Locking.Uncorrupt(ExampleLock);
}
}
}
private void ExecuteMethod()
{
//does something, maybe throws an exception
}
public bool EnsureValidState()
{
// code to make sure the state is valid
// if there is an exception returns false,
return true;
}
}
Your solution seems to add nothing but complexity due to a race in the TryLockedAction:
if (IsCorrupt(lockObject))
{
exception = new LockingException("Cannot execute locked action on a corrupt object.");
return false;
}
exception = null;
Monitor.Enter(lockObject);
The lockObject might become "corrupted" while we are still waiting on the Monitor.Enter, so there is no protection.
I'm not sure what behaviour you'd like to achieve, but probably it would help to separate locking and state managing:
class StateManager
{
public bool IsCorrupted
{
get;
set;
}
public void Execute(Action body, Func fixState)
{
if (this.IsCorrupted)
{
// use some Exception-derived class here.
throw new Exception("Cannot execute action on a corrupted object.");
}
try
{
body();
}
catch (Exception)
{
this.IsCorrupted = true;
if (fixState())
{
this.IsCorrupted = false;
}
throw;
}
}
}
public class ExampleUsage
{
private readonly object ExampleLock = new object();
private readonly StateManager stateManager = new StateManager();
public void ExecuteLockedMethod()
{
lock (ExampleLock)
{
stateManager.Execute(ExecuteMethod, EnsureValidState);
}
}
private void ExecuteMethod()
{
//does something, maybe throws an exception
}
public bool EnsureValidState()
{
// code to make sure the state is valid
// if there is an exception returns false,
return true;
}
}
Also, as far as I understand, the point of the article is that state management is harder in presence of concurrency. However, it's still just your object state correctness issue which is orthogonal to the locking and probably you need to use completely different approach to ensuring correctness. E.g. instead of changing some complex state withing locked code region, create a new one and if it succeeded, just switch to the new state in a single and simple reference assignment:
public class ExampleUsage
{
private ExampleUsageState state = new ExampleUsageState();
public void ExecuteLockedMethod()
{
var newState = this.state.ExecuteMethod();
this.state = newState;
}
}
public class ExampleUsageState
{
public ExampleUsageState ExecuteMethod()
{
//does something, maybe throws an exception
}
}
Personally, I always tend to think that manual locking is hard-enough to treat each case when you need it individually (so there is no much need in generic state-management solutions) and low-lelvel-enough tool to use it really sparingly.
Though it looks reliable, I have three concerns:
1) The performance cost of Invoke() on every locked action could be severe.
2) What if the action (the method) requires parameters? A more complex solution will be necessary.
3) Does the CorruptionStateDictionary grow endlessly? I think the uncorrupt() method should problem remove the object rather than set the data false.
Move the IsCorrupt test and the Monitor.Enter inside
the Try
Move the corruption set
handling out of finally and into the Catch block (this should
only execute if an exception has
been thrown)
Don't release the primary lock until after the
corruption flag has been set (leave
it in the finaly block)
Don't restrict the execption to the calling thread; either rethow
it or add it to the coruption
dictionary by replacing the bool
with the custom execption, and
return it with the IsCorrupt Check
For Uncorrupt simply remove the
item
There are some issues with the locking sequencing (see below)
That should cover all the bases
public static class Locking
{
private static readonly Dictionary<object, Exception> CorruptionStateDictionary = new Dictionary<object, Exception>();
private static readonly object CorruptionLock = new object();
public static bool TryLockedAction(object lockObject, Action action, out Exception exception)
{
var lockTaken = false;
exception = null;
try
{
Monitor.Enter(lockObject, ref lockTaken);
if (IsCorrupt(lockObject))
{
exception = new LockingException("Cannot execute locked action on a corrupt object.");
return false;
}
action.Invoke();
}
catch (Exception ex)
{
var corruptionLockTaken = false;
exception = ex;
try
{
Monitor.Enter(CorruptionLock, ref corruptionLockTaken);
if (CorruptionStateDictionary.ContainsKey(lockObject))
{
CorruptionStateDictionary[lockObject] = ex;
}
else
{
CorruptionStateDictionary.Add(lockObject, ex);
}
}
finally
{
if (corruptionLockTaken)
{
Monitor.Exit(CorruptionLock);
}
}
}
finally
{
if (lockTaken)
{
Monitor.Exit(lockObject);
}
}
return exception == null;
}
public static void Uncorrupt(object corruptLockObject)
{
var lockTaken = false;
try
{
Monitor.Enter(CorruptionLock, ref lockTaken);
if (IsCorrupt(corruptLockObject))
{
{ CorruptionStateDictionary.Remove(corruptLockObject); }
}
}
finally
{
if (lockTaken)
{
Monitor.Exit(CorruptionLock);
}
}
}
public static bool IsCorrupt(object lockObject)
{
Exception ex = null;
return IsCorrupt(lockObject, out ex);
}
public static bool IsCorrupt(object lockObject, out Exception ex)
{
var lockTaken = false;
ex = null;
try
{
Monitor.Enter(CorruptionLock, ref lockTaken);
if (CorruptionStateDictionary.ContainsKey(lockObject))
{
ex = CorruptionStateDictionary[lockObject];
}
return CorruptionStateDictionary.ContainsKey(lockObject);
}
finally
{
if (lockTaken)
{
Monitor.Exit(CorruptionLock);
}
}
}
}
The approach I would suggest would be to have a lock-state-manager object, with an "inDangerState" field. An application that needs to access a protected resource starts by using the lock-manager-object to acquire the lock; the manager will acquire the lock on behalf of the application and check the inDangerState flag. If it's set, the manager will throw an exception and release the lock while unwinding the stack. Otherwise the manager will return an IDisposable to the application which will release the lock on Dispose, but which can also manipulate the danger state flag. Before putting the locked resource into a bad state, one should call a method on the IDisposable which will set inDangerState and return a token that can be used to re-clear it once the locked resource is restored to a safe state. If the IDisposable is Dispose'd before the inDangerState flag is re-cleared, the resource will be 'stuck' in 'danger' state.
An exception handler which can restore the locked resource to a safe state should use the token to clear the inDangerState flag before returning or propagating the exception. If the exception handler cannot restore the locked resource to a safe state, it should propagate the exception while inDangerState is set.
That pattern seems simpler than what you suggest, but seems much better than assuming either that all exceptions will corrupt the locked resource, or that none will.