In a previous post, we introduced the UnobservedTaskException event, saying that it would be useful for host-plugin scenarios where a host application should continue to execute in the presence of exceptions thrown by buggy plugins.  A typical example is an Internet browser; should the entire application crash if some rich content plugin crashes?  Today, we will describe how to achieve this scenario by creating a TPL sandbox using this feature in conjunction with application domains.

Application domains (represented by AppDomain objects) were introduced to .NET to provide isolation, unloading, and security boundaries in a single process.  For example:

// Create a PermissionSet for the sandbox AppDomain

PermissionSet permSet = new PermissionSet(PermissionState.None);

...

 

// Create an AppDomainSetup.

AppDomainSetup setup = new AppDomainSetup();

...

 

// Create the sandbox AppDomain

AppDomain sandbox = AppDomain.CreateDomain(

    "Sandbox",

    null,

    setup,

    permSet,

    CreateStrongName(Assembly.GetExecutingAssembly()));

 

// Execute the BuggyPlugin assembly in the sandbox AppDomain.

try

{

    sandbox.ExecuteAssembly("BuggyPlugin.exe");

}

catch (Exception e)

{

    LogError(e);

}

AppDomain.Unload(sandbox);

 

In the above host code, the BuggyPlugin assembly is executed within a sandbox AppDomain.  The restricted permissions of the AppDomain prevent the plugin from stepping out of line, and the try/catch block around the call to ExecuteAssembly handles any synchronous exceptions that propagate from the plugin.  However, the key word is “synchronous”.  Asynchronous exceptions, like those thrown within Task bodies, will not propagate through this try/catch block.  If left unobserved, they will be treated as unhandled exceptions, tearing down the process by default.

The UnobservedTaskException event was added to continue to support this scenario.  The following code demonstrates how to subscribe to this event by specifying an initializer delegate for the plugin AppDomain.

AppDomainSetup setup = new AppDomainSetup();

setup.AppDomainInitializer = _ =>

{

    // Subscribe to the TPL exception event. Unobserved

    // Task exceptions will be logged and marked as

    // "observed" (suppressing the usual exception escalation

    // behavior which would crash the process).

    TaskScheduler.UnobservedTaskException +=

        (object sender, 
         UnobservedTaskExceptionEventArgs exceptionArgs) =>

    {

        LogError(exceptionArgs.Exception);

        exceptionArgs.SetObserved();

    };

};


With this addition, all unobserved Task exceptions that originate from the plugin code will be logged and marked as observed.  It’s important to note that there is one of these events per AppDomain.  This makes it possible to selectively squash Task exceptions from the plugin; exceptions that originate from the host will be treated normally (as serious errors that should stop the application’s execution).

Attached is the full code used in this post.  Enjoy!