The issue:
An unhandled exception in a thread entering the CLR from unmanaged code does not trigger the "normal" unhandled exception CLR processing.
In the code below calling CSSimpleObject.GetstringLength()
from C++ with
- "1" throws an exception in the calling thread (non-CLR-created thread),
- "2" throws an exception in a new Thread() (CLR-created thread).
In case "1"
- CurrentDomain_UnhandledException() is never called.
- The Application Domain and the process will stay loaded and running, you will only get a FAILED.
In case "2" (expected behavior)
- CurrentDomain_UnhandledException() is called.
- The proces gets killed.
The Question:
What has to be done to get the "normal" behavior?
Sample code:
The code below is based on the Visual Studio 2010 "CppHostCLR" code sample from the "all interop and fusion samples".
RuntimeHost (C++):
PCWSTR pszStaticMethodName = L"GetStringLength";
PCWSTR pszStringArg = L"1";
//PCWSTR pszStringArg = L"2";
hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(pszAssemblyPath,
pszClassName, pszStaticMethodName, pszStringArg, &dwLengthRet);
if (FAILED(hr))
{
wprintf(L"Failed to call GetStringLength w/hr 0x%08lx\n", hr);
goto Cleanup;
}
Managed Code (C#):
public class CSSimpleObject
{
public CSSimpleObject()
{
}
//------8<----------
public static int GetStringLength(string str)
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
switch (str)
{
case "1":
throw new Exception("exception in non-CLR-created thread");
case "2":
Thread thread = new Thread(new ThreadStart(WorkThreadFunction));
thread.Start();
break;
}
return str.Length;
}
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Console.WriteLine("CurrentDomain_UnhandledException:" + e.ToString());
Console.ReadKey();
}
public static void WorkThreadFunction()
{
throw new Exception(""exception in CLR-created thread"");
}
Research so far:
MSDN initially implies that unhandled exceptions in a non-CLR-created thread should behave more or less "naturally" - see "Exceptions in Managed Threads"
The common language runtime allows most unhandled exceptions in threads to proceed naturally. In most cases this means that the unhandled exception causes the application to terminate."
"Most" meaning that in CLR-created threads internal, thread abort and Application Domain unloaded exceptions are handled differently. In non-CLR threads
"they proceed normally, resulting in termination of the application."
Further research led me to "Unhandled Exception Processing In The CLR" where I found out the following:
"if the exception was not handled ... in the managed method, the exception would exit the CLR but continue to propagate up the stack as a native SEH exception (managed exceptions are represented as native SEH exceptions) ... The OS unhandled exception filter (UEF) mechanism may not always result in triggering the CLR's unhandled exception processing. Under normal circumstances, this will work as expected and the CLR's unhandled exception processing will be triggered. However, in certain instances this may not happen."
What is wrong with the code above or how can it be changed so that the CLR's unhandled exception processing is triggered?
Update (2011-05-31):
I just found an old bug report, "UnhandledExceptionEventHandler is not called when managed code is called from unmanaged and throws an exception - http://tinyurl.com/44j6gvu", where Microsoft confirms this is a "buggy" behavior:
Thank you for taking the time to report this problem. The behavior is indeed a bug caused by the CLR execution engine and the CRT competing for the UnhandledExceptionFilter. The architecture of the CLR has been revised in the 4.0 version supporting this scenario.
Update (2011-06-06):
Why is it important to get this right?
- if you are creating a hosting-environment your developers expect a consistent behavior in the exception handling
- unless there is a way to trigger the "normal CLR exception handling" in a native thread it means you always have to transfer execution to a managed thread (enqueueing in a thread pool for instance)
- there is still that tiny bit of code transfering excecution from native to managed thread ... that has to catch all exceptions and somehow handle that situation differently
Note: changing the CLR behavior through SetActionOnFailure()
makes matters worse, in the sence, that it ends up masking the original exception (i.e. instead of an out of memory you endup seeing threadAborts - without a clue where the original error cam from)!