hosting clr and catching threading exceptions
Asked Answered
P

4

13

I am trying to write an plugin system that can load managed plugins. The host should be able to unload the plugins if there are any exceptions. for my poc I have a sample code library in C# that throws an exception like this ...

 public static int StartUp(string arguments)
 {
       Console.WriteLine("Started exception thrower with args {0}", arguments);
       Thread workerThread = new Thread(() => 
            {
                Console.WriteLine("Starting a thread, doing some important work");
                Thread.Sleep(1000);
                throw new ApplicationException();
            }
         );
         workerThread.Start();
         workerThread.Join();
         Console.WriteLine("this should never print");
        return 11;
    }

then i have native win32 console app like this ..

int _tmain(int argc, _TCHAR* argv[])
{
    ICLRMetaHost *pMetaHost       = NULL;
    HRESULT hr; 
    ICLRRuntimeInfo *runtimeInfo = NULL;    
    __try
    {
        hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pMetaHost);
        hr = pMetaHost->GetRuntime(L"v4.0.30319",IID_ICLRRuntimeInfo,(LPVOID*)&runtimeInfo);
        ICLRRuntimeHost *runtimeHost  = NULL;
        hr = runtimeInfo->GetInterface(CLSID_CLRRuntimeHost,IID_ICLRRuntimeHost, (LPVOID*)&runtimeHost);    
        ICLRControl* clrControl = NULL;
        hr = runtimeHost->GetCLRControl(&clrControl);
        ICLRPolicyManager *clrPolicyManager = NULL;
        clrControl->GetCLRManager(IID_ICLRPolicyManager, (LPVOID*)&clrPolicyManager);
        clrPolicyManager->SetDefaultAction(OPR_ThreadAbort,eUnloadAppDomain);   
        hr = runtimeHost->Start();
        DWORD returnVal = NULL;         
        hr = runtimeHost->ExecuteInDefaultAppDomain(L"ExceptionThrower.dll",L"ExceptionThrower.MainExceptionThrower",L"StartUp",L"test",&returnVal);        
        runtimeHost->Release();
    }
    __except(1)
    {
        wprintf(L"\n Error thrown %d",e);
    }
    return 0;
}

Issue is that if i use the above code, the host would complete running the managed code (the line "this should never print" would end up printing) If i remove the clrPolicyManager->SetUnhandledExceptionPolicy(eHostDeterminedPolicy), then the host process would crash.

can anything be done in the unmanaged host that it could gracefully remove the errant app from runtime and continue working ?

Practicable answered 25/10, 2011 at 21:35 Comment(4)
Your code enabled the .NET 1.x exception handling policy. Which just terminates the thread. Not what you want, you'll need to also call ICLRPolicyManager::SetDefaultAction() to tell it to unload the app domain on a thread abort. You still have a dead thread somewhere, use __try/__catch to catch the exception.Abroad
I added following line clrPolicyManager->SetDefaultAction(OPR_ThreadAbort,eUnloadAppDomain); to the code, i have updated the code , but the effect is same, the host process still crashesPracticable
You may have missed the "dead thread" part of the comment. You must catch the SEH exception. The exception code is 0xe0434f4d. msdn.microsoft.com/en-us/library/s58ftw19%28v=VS.100%29.aspxAbroad
@HansPassant - If you are still around [heck, its only been 13 years since the previous comment... That link is (of course) gone... working with some crazy code that must use __try __except and must detect CLR exceptions and differentiate them [I already have a differentiation between C++ throw and SEH via RaiseException and others...] but 0xe0434f4d does not seem right (0xe0434352 is what I have in one test case)Clapperclaw
C
1

You can start a new AppDomain specifically for each given plugin and launch it inside. See http://msdn.microsoft.com/en-us/library/ms164323.aspx

Each AppDomain is an isolated environment where code can execute. Exceptions occuring in one AppDomain can be isolated from th rest. See: http://msdn.microsoft.com/en-us/library/system.appdomain(v=VS.100).aspx

Carpic answered 3/11, 2011 at 10:26 Comment(4)
domains do provide a memory/security sandbox, but they dont provide thread isolation, that is threads are created at CLR level, and they can execute in any domain, so if an unhandled exception happens on a thread, the whole CLR crashes...Practicable
@Practicable - see msdn: "Use application domains to isolate tasks that might bring down a process. If the state of the AppDomain that's executing a task becomes unstable, the AppDomain can be unloaded without affecting the process. This is important when a process must run for long periods without restarting. You can also use application domains to isolate tasks that should not share data." ( msdn.microsoft.com/en-us/library/system.appdomain.aspx )Carpic
@Practicable - Please read: ikickandibite.blogspot.com/2010/04/… which deals with exactly your problem. I'm not sure if we can replicate this using the CLR-Hosting API. If not, you can develop a managed bootstrapper for the plugin-dll which gracefully handles an unhandeled exceptionCarpic
There are exceptions that corrupt the state of the whole process, like StackOverflowException. Since .NET 2.0, these bring down the whole process, regardless whether they're in a separate appdomain. The only way to prevent this is through ICLRPolicyManager, from a CLR Hosting application, see: msdn.microsoft.com/en-us/library/ms164394.aspxRadioelement
C
3

First of all, if you want to prevent application crash with the code above, you'll need to use SetUnhandledExceptionFilter, like this:

LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionInfo)
{
    // do something useful
    return EXCEPTION_EXECUTE_HANDLER; // prevent crash
}

int _tmain(int argc, _TCHAR* argv[])
{
    SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
        ...
}

But this may not be what you really want. One solution (as proposed by Polity I believe) is to create an intermediary AppDomain that can catch easily all unhandled exceptions. You can do that in C#, like this:

public class PluginVerifier
{
    public static int CheckPlugin(string arguments)
    {
        AppDomain appDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString());
        appDomain.UnhandledException += AppDomainUnhandledException;
        object obj = appDomain.CreateInstanceAndUnwrap("ExceptionThrower", "ExceptionThrower.MainExceptionThrower");
        object ret = obj.GetType().InvokeMember("Startup", BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod, null, obj, new object[] { arguments });
        AppDomain.Unload(appDomain);
        return (int)ret;
    }

    private static void AppDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        AppDomain appDomain = (AppDomain)sender;
        // the following will prevent "this should never print" to happen
        AppDomain.Unload(appDomain);
    }
}

For this to be able to work however, you need to do two changes to your plugin classes:

  • they must derive from MarshalByRefObject
  • the plugin method must not be static (static methods call do not go through AppDomain filter)

So your class would be written like this:

public class MainExceptionThrower: MarshalByRefObject
{
    public int StartUp(string arguments)
    {
    ...
    }
 }

If you do this, you can remove the calls to SetUnhandledExceptionPolicy, SetActionOnFailure, or SetDefaultAction, and just replace the bootstrap code like this:

    hr = runtimeHost->ExecuteInDefaultAppDomain(L"PluginSystem.dll", L"PluginSystem.PluginVerifier", L"CheckPlugin", L"test", &returnVal);        

If you try this with your Startup code above, this call will return hr=0x80131604, which is COR_E_TARGETINVOCATION (TargetInvocationException).

Clino answered 4/11, 2011 at 14:20 Comment(0)
B
1

Looks like adding following together with SetDefaultAction resolves the crash:

clrPolicyManager->SetUnhandledExceptionPolicy(EClrUnhandledException::eHostDeterminedPolicy);
Ballocks answered 30/10, 2011 at 16:46 Comment(1)
as mentioned in the question "Issue is that if i use the above code, the host would complete running the managed code (the line "this should never print" would end up printing) If i remove the clrPolicyManager->SetUnhandledExceptionPolicy(eHostDeterminedPolicy), then the host process would crash."Practicable
C
1

You can start a new AppDomain specifically for each given plugin and launch it inside. See http://msdn.microsoft.com/en-us/library/ms164323.aspx

Each AppDomain is an isolated environment where code can execute. Exceptions occuring in one AppDomain can be isolated from th rest. See: http://msdn.microsoft.com/en-us/library/system.appdomain(v=VS.100).aspx

Carpic answered 3/11, 2011 at 10:26 Comment(4)
domains do provide a memory/security sandbox, but they dont provide thread isolation, that is threads are created at CLR level, and they can execute in any domain, so if an unhandled exception happens on a thread, the whole CLR crashes...Practicable
@Practicable - see msdn: "Use application domains to isolate tasks that might bring down a process. If the state of the AppDomain that's executing a task becomes unstable, the AppDomain can be unloaded without affecting the process. This is important when a process must run for long periods without restarting. You can also use application domains to isolate tasks that should not share data." ( msdn.microsoft.com/en-us/library/system.appdomain.aspx )Carpic
@Practicable - Please read: ikickandibite.blogspot.com/2010/04/… which deals with exactly your problem. I'm not sure if we can replicate this using the CLR-Hosting API. If not, you can develop a managed bootstrapper for the plugin-dll which gracefully handles an unhandeled exceptionCarpic
There are exceptions that corrupt the state of the whole process, like StackOverflowException. Since .NET 2.0, these bring down the whole process, regardless whether they're in a separate appdomain. The only way to prevent this is through ICLRPolicyManager, from a CLR Hosting application, see: msdn.microsoft.com/en-us/library/ms164394.aspxRadioelement
M
0

You brought up a very interesting question, thanks for that.

I suppose this article will be helpful enough: http://etutorials.org/Programming/programming+microsoft+visual+c+sharp+2005/Part+III+More+C+Language/Chapter+9+Exception+Handling/Unhandled+Exceptions/

Mose answered 4/11, 2011 at 10:30 Comment(1)
Naked links don't make for good answers. Please can you summarise the article here. If the linked content ever moves this answer becomes worse than useless. Also there's no need to sign all your answers, they have your flair attached which is your signature.Paxon

© 2022 - 2024 — McMap. All rights reserved.