NUnit async test calling AppDomainUnloadedException

I have a WCF.NET 4.5 service with asynchronous operations. I have integration tests that build a service host using NetNamedPipeBinding and handle the operation through the client.

However, each such test always forces NUnit to report the following:

System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain. This can happen if the test(s) started a thread but did not stop it. Make sure that all the threads started by the test(s) are stopped before completion. 

Everything looks good to me. Can anyone see what could be causing this? I have a complete sample code on GitHub: https://github.com/devlife/codesamples

+6
source share
1 answer

I have the same problem. The problem seems to be related to the soft threads of the termination ports (in ThreadPool) that WCF used to handle async IO.

When ServiceHost.Close() , it will signal all those threads that work, but they will not disappear immediately, that is, they can survive the end of the ServiceHost.Close() operation. Thus, the β€œshutdown” procedure is performed with the actual unloading of the AppDomain caused by NUnit due to the end of the test run.

Basically, a simple Thread.Sleep(<a couple of seconds>) after ServiceHost.Close() "fixes" this Thread.Sleep(<a couple of seconds>)

After a long search on the Internet, I could not find a reliable solution for this problem (to select similar problems, not all due to the same reason, though, google "unit test appdomainunloadedexception") somehow suppress this warning.

I tried different bindings and transports (including NullTransport ), but to no avail.

In the end, I decided on this "solution":

 static void PreventPrematureAppDomainUnloadHack() { // // When NUnit unloads the test AppDomain, the WCF started IO completion port threads might // not have exited yet. // That leads to AppDomainUnloadedExceptions being raised after all is said and done. // While native NUnit, ReSharper oder TestDriven.NET runners don't show these, VSTest (and // TFS-Build) does. Resulting in very annoying noise in the form of build/test warnings. // // The following code _attempts_ to wait for all completion port threads to end. This is not // an exact thing one can do, however we mitigate the risk of going wrong by several factors: // (1) This code is only used during Unit-Tests and not for production code. // (2) It is only called when the AppDomain in question is about to go away anway. // So the risk of someone starting new IO threads while we're waiting is very // low. // (3) Finally, we have a timeout in place so that we don't wait forever if something // goes wrong. // if (AppDomain.CurrentDomain.FriendlyName.StartsWith("test-domain-", StringComparison.Ordinal)) { Console.WriteLine("AppDomainUnloadHack: enabled (use DbgView.exe for details)."); Trace.WriteLine(string.Format("AppDomainUnloadHack: enabled for domain '{0}'.", AppDomain.CurrentDomain.FriendlyName)); AppDomain.CurrentDomain.DomainUnload += (sender, args) => { int activeIo; var sw = Stopwatch.StartNew(); var timeout = TimeSpan.FromSeconds(3); do { if (sw.Elapsed > timeout) { Trace.WriteLine("AppDomainUnloadHack: timeout waiting for threads to complete."); sw.Stop(); break; } Thread.Sleep(5); int maxWorkers; int availWorkers; int maxIo; int availIo; ThreadPool.GetMaxThreads(out maxWorkers, out maxIo); ThreadPool.GetAvailableThreads(out availWorkers, out availIo); activeIo = maxIo - availIo; Trace.WriteLine(string.Format("AppDomainUnloadHack: active completion port threads: {0}", activeIo)); } while (activeIo > 0); Trace.WriteLine(string.Format("AppDomainUnloadHack: complete after {0}", sw.Elapsed)); }; } } 

A timeout of 3 seconds is absolutely arbitrary, as well as a 5 ms wait between each attempt. Sometimes I get a timeout, but most of the time it works.

I am sure that this code is called once for each test assembly (i.e. via a static ctor of reference type).

As usual in such cases, YMMV.

+3
source

All Articles