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:
Can anyone provide any insight into what might be causing the deadlocking? I can't seem to put my finger on it.
Monitor
locks works - M.Babcock 2012-04-05 02:54
private static object m_lock = new object();
to private readonly object m_lock = new object();
Bryan Crosby 2012-04-05 04:32
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
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)]
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
Parallel.ForEach
locking is not performed for you from my experience - M.Babcock 2012-04-05 02:19