Question

Im not sure this is the correct forum for this type of question, but Im currently trying to find a bug I cant reproduce in a web service using a memory dump and I think I have a specific question I need help with, that I think someone might have some input on.

Analyzing a memory dump using WinDbg I find aprox 75000 ThreadAbortExceptions in memory, and they all originate from here:

at System.Threading.WaitHandle.WaitOne(Int64 timeout  Boolean exitContext)
at MyNameSpace.CustomThreadPool.Run()

They are all created in a very short period of time, when the application is trying to unload its appdomain (IIS is closing down).

What I cant figure out right now is how its possible to raise so many ThreadAbortExceptions? If a thread quits, is there any way it may raise more than one? If anyone can give any hint as to why so many exceptions of this type can exist? From what I can see there's about 20 threads max is this process, and the threadpool itself has only one(!) thread when this occurs.

The CustomThreadPool class comes from this article: http://msdn.microsoft.com/en-us/magazine/cc163851.aspx

public sealed class CustomThreadPool : IDisposable
{
    private Semaphore _workWaiting;
    private Queue<WaitQueueItem> _queue;
    private List<Thread> _threads;

    public CustomThreadPool(int numThreads)
    {
        if (numThreads <= 0) 
            throw new ArgumentOutOfRangeException("numThreads");

        _threads = new List<Thread>(numThreads);
        _queue = new Queue<WaitQueueItem>();
        _workWaiting = new Semaphore(0, int.MaxValue);

        for (int i = 0; i < numThreads; i++)
        {
            Thread t = new Thread(Run);
            t.IsBackground = true;
            _threads.Add(t);
            t.Start;
        }
    }

    public void Dispose()
    {
        if (_threads != null)
        {
            _threads.ForEach(delegate(Thread t) { t.Interrupt(); });
            _threads = null;
        }
    }

    public void QueueUserWorkItem(WaitCallback callback, object state)
    {
        if (_threads == null) 
            throw new ObjectDisposedException(GetType().Name);
        if (callback == null) throw new ArgumentNullException("callback");

        WaitQueueItem item = new WaitQueueItem();
        item.Callback = callback;
        item.State = state;
        item.Context = ExecutionContext.Capture();

        lock(_queue) _queue.Enqueue(item);
        _workWaiting.Release();
    }

    private void Run()
    {
        try
        {
            while (true)
            {
                _workWaiting.WaitOne();
                WaitQueueItem item;
                lock(_queue) item = _queue.Dequeue();
                ExecutionContext.Run(item.Context, 
                    new ContextCallback(item.Callback), item.State);
            }
        }
        catch(ThreadInterruptedException){}
    }

    private class WaitQueueItem
    {
        public WaitCallback Callback;
        public object State;
        public ExecutionContext Context;
    }
}
Was it helpful?

Solution

It's possible to catch and then reset a ThreadAbortException using Thread.ResetAbort. So a single thread could actually have many such exceptions thrown on it.

For example, if you call Response.Redirect(url, true) in ASP.NET, it will abort the current thread and then cancel the abort higher up.

I'm not sure this quite explains your situation, but it's worth looking at. Alternatively, is something trying to recreate the thread pool when it "crashes" due to the app domain being unloaded?

EDIT: To respond to your comment: as per the AppDomain.Unload documentation:

The threads in domain are terminated using the Abort method, which throws a ThreadAbortException in the thread. Although the thread should terminate promptly, it can continue executing for an unpredictable amount of time in a finally clause.

Basically the threads are being aborted precisely because your appdomain is being unloaded.

OTHER TIPS

Doing a Response.Redirect("~/Somewhere.aspx") sometimes causes a ThreadAbortException if the current (default) thread hasn't finished execution yet.

You can prevent this by using the overloaded redirect method.

Response.Redirect("~/Somewhere.aspx", false);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top