views:

70

answers:

2

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;
    }
}
+1  A: 

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.

Jon Skeet
Thanks alot for that input, I did not know about Thread.Abort!However, I can not find any usage of that in the code I have control over. Is there any reason the framework may do that itself?I have looked at Respose.Redirect, Response.End and Server.Transfer, but I can not find any usages there either.The recreation is something im going to look into, thanks.
MatteS
If the pool was recreated every x minutes for instance, and thereby a new thread, isnt there a max of threads that can exist, or is it possible to have as many as 75000 threads? Im currently looking at some code that may suggest recreations at timed intervals.
MatteS
@MatteS: See my edit. I doubt that you'd have 75000 threads - my suspicion is that something is responding to the thread aborting by starting it up again, and then AppDomain.Unload is re-aborting it etc.
Jon Skeet
Thanks a ton for that input! That sounds very very reasonable. Now I can dive deep into the code base with some new ideas in mind.
MatteS
Although Im not sure iv fixed the bug yet, I got my specic question answered. How a single thread may throw multiple ThreadAbortExceptions. Using reflector, iv found there is code in the ASP.NET framework that does this in several scenarios. I havnt found any other part of the framework that does this.
MatteS
A: 

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);
Damien Dennehy