Working with non-serializable unhandled exceptions from an AppDomain child application

We use System.AddIn to load add-ons into separate child AppDomains, and we unload the AppDomain application if it has an unhandled exception.

Since the default behavior of .NET from version 2.0 was to tear down the whole process if any AppDomain has an unhandled exception, we do this using the "legacyUnhandledExceptionPolicy" option in our App.config, and then manually terminate the process if unhandled the exception was mainly AppDomain or unloaded the corresponding AppDomain if it was in the add-in.

All this works fine, except for one small problem: unhandled exceptions always go from the child AppDomains to the main one, and if they are not serializable, they cannot successfully cross the border of the AppDomain.

Instead, we get a SerializationException as an UnhandledException in the main AppDomain, causing our application to come off.

I can come up with several possible solutions to this problem:

  • We could not disrupt the process for raw SerializationExceptions (yuck).

  • We could exclude exceptions from distributing the child AppDomain to the main AppDomain.

  • We could replace non-serializable exceptions with serializable ones, possibly using serialization surrogates and serialization bindings. [Edit: see End, why this is not possible with surrogates]

However, the first one is pretty terrible, and I was unsuccessful in determining how to make any of the other options with cross-removing AppDomain.

Can anyone offer any advice? Any help is appreciated.

To play back, create a console application with the following App.config:

<?xml version="1.0"?> <configuration> <runtime> <legacyUnhandledExceptionPolicy enabled="true"/> </runtime> </configuration> 

And the following code:

 class Program { private class NonSerializableException : Exception { } static void Main(string[] args) { AppDomain.CurrentDomain.UnhandledException += MainDomain_UnhandledException; AppDomain childAppDomain = AppDomain.CreateDomain("Child"); childAppDomain.UnhandledException += ChildAppDomain_UnhandledException; childAppDomain.DoCallBack( () => new Thread(delegate() { throw new NonSerializableException(); }).Start()); Console.ReadLine(); } static void MainDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { Console.WriteLine("Main AppDomain Unhandled Exception: " + e.ExceptionObject); Console.WriteLine(); } static void ChildAppDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { Console.WriteLine("Child AppDomain Unhandled Exception: " + e.ExceptionObject); Console.WriteLine(); } } 



EDIT: I used a reflector to find out if there is a way to access the BinaryFormatter used when cross-connecting through the AppDomain, but it gets into this code inside the CrossAppDomainSerializer class:

 internal static void SerializeObject(object obj, MemoryStream stm) { BinaryFormatter formatter = new BinaryFormatter(); RemotingSurrogateSelector selector = new RemotingSurrogateSelector(); formatter.SurrogateSelector = selector; formatter.Context = new StreamingContext(StreamingContextStates.CrossAppDomain); formatter.Serialize(stm, obj, null, false); } 

Therefore, he creates a locator in the method, and there is no way to bind my own surrogate ... I think that makes any efforts in this direction futile.

+6
c # exception serialization appdomain
source share
1 answer

I would handle exceptions in the remote application domain. First, I would create a new assembly with exception handling code, and then upload it to the child domain of the application.

The following code should give some ideas.

 class Program { private class NonSerializableException : Exception { } static void Main(string[] args) { var childAppDomain = AppDomain.CreateDomain("Child"); Console.WriteLine("Created child AppDomain #{0}.", childAppDomain.Id); // I did not create a new assembly for the helper class because I am lazy :) var helperAssemblyLocation = typeof(AppDomainHelper).Assembly.Location; var helper = (AppDomainHelper)childAppDomain.CreateInstanceFromAndUnwrap( helperAssemblyLocation, typeof(AppDomainHelper).FullName); helper.Initialize(UnloadHelper.Instance); childAppDomain.DoCallBack( () => new Thread(delegate() { throw new NonSerializableException(); }).Start()); Console.ReadLine(); } private sealed class UnloadHelper : MarshalByRefObject, IAppDomainUnloader { public static readonly UnloadHelper Instance = new UnloadHelper(); private UnloadHelper() { } public override object InitializeLifetimeService() { return null; } public void RequestUnload(int id) { // Add application domain identified by id into unload queue. Console.WriteLine("AppDomain #{0} requests unload.", id); } } } // These two types could be in another helper assembly public interface IAppDomainUnloader { void RequestUnload(int id); } public sealed class AppDomainHelper : MarshalByRefObject { public void Initialize(IAppDomainUnloader unloader) { AppDomain.CurrentDomain.UnhandledException += (sender, e) => unloader.RequestUnload(AppDomain.CurrentDomain.Id); } } 
+2
source share

All Articles