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 ?

np-hard
  • 5,725
  • 6
  • 52
  • 76
  • 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. – Hans Passant Oct 26 '11 at 17:29
  • 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 crashes – np-hard Oct 26 '11 at 18:30
  • You may have missed the "dead thread" part of the comment. You must catch the SEH exception. The exception code is 0xe0434f4d. http://msdn.microsoft.com/en-us/library/s58ftw19%28v=VS.100%29.aspx – Hans Passant Nov 04 '11 at 12:00

4 Answers4

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

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
1

Looks like adding following together with SetDefaultAction resolves the crash:

clrPolicyManager->SetUnhandledExceptionPolicy(EClrUnhandledException::eHostDeterminedPolicy);
  • 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." – np-hard Oct 30 '11 at 22:11
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

Polity
  • 14,734
  • 2
  • 40
  • 40
  • 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... – np-hard Nov 03 '11 at 19:49
  • @np-hard - 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." ( http://msdn.microsoft.com/en-us/library/system.appdomain.aspx ) – Polity Nov 04 '11 at 03:27
  • @np-hard - Please read: http://ikickandibite.blogspot.com/2010/04/appdomains-and-true-isolation.html 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 exception – Polity Nov 04 '11 at 03:34
  • 1
    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: http://msdn.microsoft.com/en-us/library/ms164394.aspx – Abel Jan 06 '13 at 13:42
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/

ChrisF
  • 134,786
  • 31
  • 255
  • 325
Artak
  • 2,819
  • 20
  • 31
  • 3
    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. – ChrisF Nov 04 '11 at 11:43