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.
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: