Background
It is generally not recommended in .Net to control the application flow by throwing and catching exception. But in some cases people does it, to safely do the thing. In one of the WCF component in my project there is one scenario where the application flow is controlled via exception. The main intention was to keep the exception inside the thread itself and return a wrapper which says there was exception or not. Below is the stripped down code just for understanding.
internal class LongRunningProcessCaller { private delegate ResultWrapper InvokeAsync(bool parameter); public void Invoke() { InvokeAsync invoker = new InvokeAsync(CallInvokerThreadHandler); invoker.BeginInvoke(true, new AsyncCallback(InvokeAsyncCompleted), null); } private ResultWrapper CallInvokerThreadHandler(bool test) { try { LongRunningMethodWhichMayThrowThreadAbortException(); return new ResultWrapper(); } catch (Exception ex)//All the exceptions should be caught and pass via Error property { return new ResultWrapper() { Error = ex }; } } private void InvokeAsyncCompleted(IAsyncResult result) { InvokeAsync asyncDelegate = (result as AsyncResult).AsyncDelegate as InvokeAsync; //This method will hang, if there is any exception in CallInvokerThreadHandler ResultWrapper invokerResult = asyncDelegate.EndInvoke(result); if (invokerResult.Error == null) { Console.WriteLine("Errored"); } else { Console.WriteLine("Worked"); } } private void LongRunningMethodWhichMayThrowThreadAbortException() { System.Threading.Thread.CurrentThread.Abort(); } } class ResultWrapper { public Exception Error { set; get; } }
The problem
It went good for some time in production and slowly we started getting inconsistent issues from production. As it is inconsistent, we were not able to reproduce in dev machines and fix it. If we look at the code we can understand that inconsistent issue may happen, in case the catch inside CallInvokerThreadHandler() didn't eat and convert the exception properly.
The debug process
Since its using async delegate invocation mechanism we started thinking about exception behavior in threads and find nothing much. Then we put instrumentation code in catch block and pushed to environments where this issue is reproducing. This leads to investigation of ThreadAbortException which we were not expecting in normal course.Further google about how to handle ThreadAbortException in IIS hosting environment, gives information only regarding the ASP.Net web sites related to Respose.Redirect. Some sites suggests to join the threads.
One says to resolve this exception, we can just increase maxConnections in web.config.
The Root cause
But our problem is different we have WCF service which calls another WCF service in different thread. The different thread is needed because the second WCF call is time consuming say 2-3 hours. Since the new thread is long running, there are chances for that to be aborted due to various reasons. Our aim should be to handle the failures properly. But what was the problem in the code listed above as it seems to be doing the purpose?
The root cause is, The ThreadAbortException cannot be eaten by the catch block and we were relying on exception eating behavior.
So if the ThreadAbortException occurs the wrapping of the error result for returning to the completed handler will not work. The runtime will re throw the exception at the end of the catch which will make the EndInvoke in InvokeAsyncCompleted() to wait infinitely.
So rewrote the code as follows by avoiding the async completed handler.
internal class LongRunningProcessCaller { private delegate ResultWrapper InvokeAsync(bool parameter); public void Invoke() { InvokeAsync invoker = new InvokeAsync(CallInvokerThreadHandler); invoker.BeginInvoke(true, null, null); } private ResultWrapper CallInvokerThreadHandler(bool test) { try { LongRunningMethodWhichMayThrowThreadAbortException(); Console.WriteLine("Worked"); } //All the exceptions should be processed here. //This is because the runtime rethrows ThreadAbortException from catch automatically. catch (Exception ex) { Console.WriteLine("Errored"); } } private void LongRunningMethodWhichMayThrowThreadAbortException() { System.Threading.Thread.CurrentThread.Abort(); } }
Happy coding...
No comments:
Post a Comment