Skip to content

Commit

Permalink
Harden code against System.AppDomainUnloadedException - Attempted t…
Browse files Browse the repository at this point in the history
…o access an unloaded AppDomain. (#47)

`AppDomainUnloadedException` thrown from `Server.Host.ServerHost.UnloadAllServers()` if an AppDomain has already been unloaded / disposed.

Failure mode:
```
TestCleanup method Tests.ClusterTests.TestCleanup threw exception. System.AppDomainUnloadedException:
System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain..
   at System.AppDomain.get_FriendlyName()
   at Server.Host.ServerHost.UnloadAllServers() in C:\projects\serverhost\ServerHost\ServerHost.cs:line 132
   at Tests.ClusterTests.TestCleanup() in E:\Depot\Internal\DSoAP\Testing\DSoAP.Tests\ClusterTests.cs:line 56
```

Fixes bug #46

Bump package version to `1.1.12`
  • Loading branch information
jthelin authored Oct 3, 2018
1 parent fa47c11 commit a0cbb1d
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 12 deletions.
35 changes: 28 additions & 7 deletions ServerHost/ServerHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static class ServerHost
/// <returns><c>ServerHostHandle</c> object containing metadata about the loaded server instance.</returns>
/// <remarks>
/// The <c>serverName</c> will be used for the name of the newly created AppDomain to host this new server instance.
/// Multiple copies of a server can be created if they arer each given a different server name.
/// Multiple copies of a server can be created if they are each given a different server name.
/// </remarks>
public static ServerHostHandle<TServer> LoadServerInNewAppDomain<TServer>(
string serverName)
Expand Down Expand Up @@ -105,15 +105,23 @@ public static void UnloadServerInAppDomain(AppDomain appDomain)
{
if (appDomain == null) return;

string appDomainName = "UNKNOWN";

try
{
appDomainName = appDomain.FriendlyName;

appDomain.UnhandledException -= ReportUnobservedException;

AppDomain.Unload(appDomain);
}
catch (CannotUnloadAppDomainException exc)
{
Log.Warn($"Unable to unload AppDomain {appDomainName} - " + exc.Message, exc);
}
catch (Exception exc)
{
Log.Warn($"Ignoring error unloading AppDomain {appDomain.FriendlyName} - {exc}");
Log.Warn($"Ignoring error unloading AppDomain {appDomainName} - " + exc.Message, exc);
}
}

Expand All @@ -122,16 +130,29 @@ public static void UnloadServerInAppDomain(AppDomain appDomain)
/// </summary>
public static void UnloadAllServers()
{
foreach (string serverName in LoadedAppDomains.Keys.ToArray()) // Take working copy
string[] serverNames = LoadedAppDomains.Keys.ToArray(); // Take working copy

foreach (string serverName in serverNames)
{
AppDomain appDomain;
if (LoadedAppDomains.TryRemove(serverName, out appDomain))
if (LoadedAppDomains.TryRemove(serverName, out AppDomain appDomain))
{
if (appDomain == null) continue;

Log.Info($"Unloading Server {serverName} in AppDomain {appDomain.FriendlyName}");
string appDomainName = null;
try
{
// We may get AppDomainUnloadedException thrown here - X-ref: #46.

appDomainName = appDomain.FriendlyName;

Log.Info($"Unloading Server {serverName} in AppDomain {appDomainName}");

UnloadServerInAppDomain(appDomain);
UnloadServerInAppDomain(appDomain);
}
catch (Exception exc)
{
Log.Warn($"Ignoring error unloading AppDomain {appDomainName ?? serverName}. " + exc.Message, exc);
}
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions ServerHost/ServerHost.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<PropertyGroup>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.1.11</Version>
<Version>1.1.12</Version>
<Authors>Jorgen Thelin</Authors>
<Copyright>Copyright © Jorgen Thelin 2015-2018</Copyright>
<Description>ServerHost - A .NET Server Hosting utility library, including in-process server host testing.</Description>
Expand All @@ -23,13 +23,13 @@
<RepositoryUrl>https://github.com/jthelin/ServerHost.git</RepositoryUrl>
</PropertyGroup>

<PropertyGroup Condition="'$(TRAVIS)' == 'true'" >
<PropertyGroup Condition="'$(TRAVIS)' == 'true'">
<!-- Disable SourceLink when running Travis-CI build, due to bug. -->
<!-- https://github.com/dotnet/sourcelink/issues/119 -->
<EnableSourceLink>false</EnableSourceLink>
</PropertyGroup>

<PropertyGroup Condition="'$(TRAVIS)' != 'true'" >
<PropertyGroup Condition="'$(TRAVIS)' != 'true'">

<!-- SourceLink settings: https://github.com/dotnet/sourcelink#using-sourcelink -->

Expand Down
17 changes: 15 additions & 2 deletions Tests/Tests.Net46/ServerHostTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,30 @@ public async Task UnloadServerInAppDomain()
ServerHost.LoadServerInNewAppDomain<TestServer.Server>("Two");
ServerHost.LoadServerInNewAppDomain<TestServer.Server>("Three");

var unload = Enumerable.Range(1, 4).Select(i =>
var unload = Enumerable.Range(1, 10).Select(i =>
{
return Task.Run(async () =>
{
await Task.Delay(100 - i);
await Task.Delay(10 - i);
ServerHost.UnloadAllServers();
});
});
await Task.WhenAll(unload);
}

[Fact]
[Trait("Category", "BVT")]
public void UnloadAllAppDomainsTwice()
{
ServerHost.LoadServerInNewAppDomain<TestServer.Server>("Four");
ServerHost.LoadServerInNewAppDomain<TestServer.Server>("Five");
ServerHost.LoadServerInNewAppDomain<TestServer.Server>("Six");

ServerHost.UnloadAllServers();

ServerHost.UnloadAllServers();
}

[Fact]
[Trait("Category", "BVT")]
public void ServerHost_Version()
Expand Down

0 comments on commit a0cbb1d

Please sign in to comment.