ASP.Net Development Server Deadlocking

Go To StackoverFlow.com

2

I have code that is using a win32 api from a web app. I am running into a deadlock when I run this code in the ASP.Net development server ( I cannot reproduce in IIS, but I don't know for a fact that it would not occur under certain scenarios). Below is a class that I have trimmed down that still reproduces the issue:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Runtime.InteropServices;
namespace Web_ShellIconBug
{
    public class IconIndexClass
    {
        [StructLayout(LayoutKind.Sequential)]
        private struct SHFILEINFO
        {
            public IntPtr hIcon;
            public int iIcon;
            public int dwAttributes;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            public string szDisplayName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
            public string szTypeName;
        }

        [DllImport("shell32", CharSet = CharSet.Unicode)]
        private static extern IntPtr SHGetFileInfo(string pszPath, int dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);

        private static object m_lock = new object();

        public int IconIndex(
            string fileName,
            bool tryDisk,
            int iconState
            )
        {
            // On some machines, you might need this to make sure multiple threads are spawned
            //System.Threading.Thread.Sleep(100);
            SHFILEINFO shfi = new SHFILEINFO();
            IntPtr retVal;
            uint shfiSize = (uint)Marshal.SizeOf(shfi.GetType());

            MyLog("Before Lock.");
            lock (m_lock)
            {
                MyLog("Obtained Lock.");
                retVal = SHGetFileInfo(fileName, 0, ref shfi, shfiSize, 0);
            }
            MyLog("Lock released.");
            if (retVal.Equals(IntPtr.Zero))
            {
                MyLog("IntPtr is zero");
                if (tryDisk)
                {
                    if (System.IO.Directory.Exists(fileName))
                        return IconIndex(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), false, iconState);
                    else return IconIndex(fileName, false, iconState);
                }
                else
                    return 0;
            }
            else
            {
                return shfi.iIcon;
            }
        }

        private void MyLog(string val)
        {
            System.Diagnostics.Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss.ffff") + " - Thread:" + System.Threading.Thread.CurrentThread.ManagedThreadId + " - Msg:" + val);
        }
    }
}

I can reproduce the error in a web app using the following code:

protected void Page_Load(object sender, EventArgs e)
{
    Web_ShellIconBug.IconIndexClass ii = new Web_ShellIconBug.IconIndexClass();
    Parallel.ForEach(System.IO.Directory.GetFiles("C:\\Windows"), file =>
    {
        ii.IconIndex(file, false, 0);
    });
    Debug.WriteLine("Done.");
}

I have reproduced this on two different machines both running Win 7 64 bit and VS 2010 SP1. In my output, I will see, something like this:

21:39:01.7812 - Thread:5 - Msg:Before Lock.
21:39:01.7912 - Thread:5 - Msg:Obtained Lock.
21:39:01.8022 - Thread:5 - Msg:Lock released.
21:39:01.8162 - Thread:10 - Msg:Before Lock.
21:39:02.8382 - Thread:11 - Msg:Before Lock.
21:39:03.8172 - Thread:12 - Msg:Before Lock.
21:39:04.3032 - Thread:5 - Msg:Before Lock.
21:39:04.3032 - Thread:5 - Msg:Obtained Lock.
21:39:04.3042 - Thread:5 - Msg:Lock released.
21:39:04.8162 - Thread:13 - Msg:Before Lock.
...

In this case it looks like thread 5 is obtaining the lock, but not releasing it, so all of the other threads are blocked indefinitely.

A few other things to note:

  • Reproducing the deadlock is rather touchy. If I modify any of the recursive calls after the check for the return value equaling IntPtr.Zero, the deadlock seems to go away, but I don't see why that would affect any locking, so I am hesitant to say that modifying that code corrects the problem.
  • If I do a manual Monitor.Enter and Monitor.Exit (instead of the lock), I don't get the deadlock, but again, I am not sure that I have solved the problem or just fixed it for my test case.
  • This code is very trimmed down from the production version of the code, so any code in the class that appears to not do much is probably because I tried to remove as much noise from the problem as possible while still being able to recreate it.

Can anyone provide any insight into what might be causing the deadlocking? I can't seem to put my finger on it.

2012-04-05 01:58
by John Koerner
You need to lock your Parallel.ForEach locking is not performed for you from my experience - M.Babcock 2012-04-05 02:19
@M.Babcock can you elaborate on that? The Parallel.Foreach was a mechanism I used to reproduce the problem of multiple users hitting a web server at the same time. If there is an issue with the foreach, I can fix that, but I don't think that is the underlying problem - John Koerner 2012-04-05 02:50
None of the standard collection objects are completely thread-safe (this is even true of the concurrent collections). You're using pinvoke to use native methods which are more often than not also not thread-safe. You need to include some form of locking to avoid multithreading problems. This is why using Monitor locks works - M.Babcock 2012-04-05 02:54
@JohnKoerner Have you tried changing private static object m_lock = new object(); to private readonly object m_lock = new object();Bryan Crosby 2012-04-05 04:32
@M.Babcock I am locking around the Pinvoke call (which is the lock causing the deadlock). I can change the foreach to not be a parallel foreach and just spawn tasks from the loop string, but the issue persists. I am more looking for why the IconIndex method is deadlocking - John Koerner 2012-04-05 13:27
@BryanCrosby I can try that, however can you explain why that would fix this type of deadlock - John Koerner 2012-04-05 13:28
@JohnKoerner: You also carry a risk with this code that IIS can choose to recycle, thus taking your threads with it. Parallel.ForEach() essentially uses thread pool threads. Is there a measurable performance difference that you need to use it? You might consider offloading it to a service - Bryan Crosby 2012-04-05 15:42


0

It turns out that it was a problem with the StructLayout. We specified Unicode on the function, but didn't specify it on the struct, so it defaulted to ANSI. The correct struct layout would be:

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
2012-04-27 18:45
by John Koerner


1

If I may suggest your best best here is to get a dump of the hanging process & then analyse using windbg.

To help get you started here's an example of using windbg to detect a deadlock scenario

Step 1: fix the symbols path

.symfix c:\sos .reload

Step 2: load sos - just load whatever version of .net you are using

.load C:\Windows\Microsoft.NET\Framework64\v2.0.50727\sos

Step 3: list the loaded modules

.chain

Step 4: check for deadlocks - this will tell you what thread has hung

syncblk

Step 5: switch to that thread number - in this case it is # 7

~7

Step 6: list what the thread was doing at that time

k

Step 7: check for any exceptions

!pe

Step 8: get more detailed info on the thread ~7kL 10

Step 9: just in case check the stack for errors

~* e !clrstack

2012-04-05 12:43
by Johnv2020
I am getting Failed to find runtime DLL (clr.dll) in a few spots. I tried loading it from the same place I loaded SOS, but I still get the error. Any ideas - John Koerner 2012-04-05 14:12
when run run .chain - do you see the clr loaded - Johnv2020 2012-04-05 14:48
yes it shows up in the chain - John Koerner 2012-04-05 15:46
Ads