How an AppDomain can help you isolate your method calls
In one of my latest pet-projects (which with a bit of luck is about to become a real project, knock on wood!) I have been in need of executing code in total isolation form the containing application.
Without giving too many details, the system allows plugins to be uploaded ( as standard .NET assemblies ), stored and eventually executed. However, in order to avoid a rogue plugin to jeopardise the entire application with nasty stuff like infinite loops or random exceptions, I needed a way to isolate their execution.
Being this a new project, I started writing it using .NET Core but had to revert very quickly to the ol’ reliable Framework. Seems that as of now Assemblies can be loaded in memory but there’s no way to unload them.
Just for the sake of documentation, the class responsible of loading is AssemblyLoadContext . Unfortunately loaded assemblies will be kept in memory till the containing process gets closed (more details here).
So what’s the alternative? AppDomains ! As often happens, the MS documentation does a pretty good job explaining what they are and how should be used so I’ll move straight to the point.
I wrote a small example and pushed it to GitHub, you can find it here.
The core is the Isolator class: as you can see from the code it can be instantiated passing the Type of the class you want to isolate and the name of the method you want to run on it. Something like this:
using (var isolatedFoo = new Isolator(typeof(Foo), “Bar”))
isolatedFoo.Run();
Under the hood the Isolator will:
- create a new AppDomain
- load the assembly containing typeof(Foo)
- create an instance of Foo
- execute the “Bar” method on said instance
Also, being a very polite class, it will unload the AppDomain during the Dispose() .
More under the hood, the real magic happens inside the IsolatorRunner class. As you can see from the code it inherits from MarshalByRefObject, this will allow the instance to act as an intermediary between the two AppDomains, with a proxy that will be automatically generated to intercept the calls on the other side.
Being this a quick example, I didn’t focus on the quality of the interfaces. So for example being forced to use a string to denote the method to execute is a little…disappointing.
I’ll work a little more in the next days (weeks?) and try to come up with a nicer API, maybe using Expressions and Funcs 🙂