TL;DR: How do you isolate add-in exceptions from killing the main process?
I want to have a very stable .Net application that runs less stable code in an AppDomain
. This would appear to be one of the prime purposes of the AppDomain
in the first place (well, that and security sandboxing) but it doesn't appear to work.
For instance in AddIn.exe
:
public static class Program
{
public static void Main(string[] args)
{
throw new Exception("test")
}
}
Called in my 'stable' code with:
var domain = AppDomain.CreateDomain("sandbox");
domain.UnhandledException += (sender, e) => {
Console.WriteLine("\r\n ## Unhandled: " + ((Exception) e.ExceptionObject).Message);
};
domain.ExecuteAssemblyByName("AddIn.exe", "arg A", "arg B")
The exception thrown in the AppDomain
gets passed straight to the application that created the domain. I can log these with domain.UnhandledException
and catch them in the wrapper application.
However, there are more problematic exceptions thrown, for instance:
public static class Program
{
public static void Main(string[] args)
{
Stackoverflow(1);
}
static int Stackoverflow(int x)
{
return Stackoverflow(++x);
}
}
This will throw a stackoverflow exception that kills the entire application every time. It doesn't even fire domain.UnhandledException
- it just goes straight to killing the entire application.
In addition calling things like Environment.Exit()
from inside the AppDomain
also kill the parent application, do not pass GO, do not collect £200 and don't run any ~Finialiser
or Dispose()
.
It seems from this that AppDomain
fundamentally doesn't do what it claims (or at lease what it appears to claim) to do, as it just passes all exceptions straight to the parent domain, making it useless for isolation and pretty weak for any kind of security (if I can take out the parent process I can probably compromise the machine). That would be a pretty fundamental failure in .Net, so I must be missing something in my code.
Am I missing something? Is there some way to make AppDomain
actually isolate the code that it's running and unload when something bad happens? Am I using the wrong thing and is there some other .Net feature that does provide exception isolation?
I'll throw on some random thoughts, but what @Will has said is correct regarding permissions, CAS, security transparency, and sandboxing. AppDomains are not quite superman. Regarding exceptions though, an AppDomain is capable of handling most unhandled exceptions. The category of exceptions that they are not is called an asynchronous exception. Finding documentation on such exceptions is a little more difficult now that we have async/await, but it exists, and they come in three common forms:
These exceptions are said to be asynchronous because they can be thrown anywhere, even between CIL opcodes. The first two are about the whole environment dying. The CLR lacks the powers of a Phoenix, it cannot handle these exceptions because the means of doing so are already dead. Note that these rules only exist when the CLR throws them. If you just new-up and instance and throw it yourself, they behave like normal exceptions.
There is a piece of software that is able to handle all of these exceptions. Starting in 2005, SQL has had the ability to run .NET assemblies with a feature called SQLCLR. SQL server is a rather important process, and having a .NET assembly throw an
OutOfMemoryException
and it bringing down the entire SQL process seemed tremendously undesirable, so the SQL team doesn't let that happen.They do this using a .NET 2.0 feature called constrained execution and critical regions. This is where things like
ExecuteCodeWithGuaranteedCleanup
come into play. If you are able to host the CLR yourself, start with native code and spin up the CLR yourself, you are then able to change the escalation policy: from native code you are able to handle those managed exceptions. This is how SQL CLR handles those situations.