Skip to content

Commit

Permalink
AppDomainSetupUtils (#56)
Browse files Browse the repository at this point in the history
- Separate AppDomain setup utility code from main ServerHost class.

- Minor cleanup of logging code in TestServer.
  • Loading branch information
jthelin authored Oct 17, 2018
1 parent f2540f8 commit d07e76f
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 45 deletions.
95 changes: 95 additions & 0 deletions ServerHost/AppDomainSetupUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Remoting;

namespace Server.Host
{
public static class AppDomainSetupUtils
{
private static readonly object[] NoArgs = new object[0];

/// <summary>
/// Create a new instance of the trace listener in the specified app domain.
/// </summary>
/// <typeparam name="T"> The type of object to be created. </typeparam>
/// <param name="appDomain"> The AppDomain where the "far" tracer object should be created. </param>
/// <returns> An instance of the remote trace instance. </returns>
[MethodImpl(MethodImplOptions.NoInlining)]
internal static T CreateObjectInstanceInAppDomain<T>(
AppDomain appDomain,
object[] args = null)
where T : class
{
string appDomainName = appDomain.FriendlyName;
string className = typeof(T).GetTypeInfo().FullName;
string assemblyFilePath = typeof(T).GetTypeInfo().Assembly.Location;

if (string.IsNullOrEmpty(className))
{
throw new ArgumentNullException("Cannot determine class name for " + typeof(T));
}
if (string.IsNullOrEmpty(assemblyFilePath))
{
throw new ArgumentNullException("Cannot determine assembly location for " + typeof(T));
}

// Create instance of the class in the "far" app domain.

ObjectHandle remoteObjRef = appDomain.CreateInstanceFrom(
assemblyFilePath,
className,
false,
BindingFlags.Default,
null,
args ?? NoArgs,
CultureInfo.CurrentCulture,
NoArgs);

object remoteObj = remoteObjRef.Unwrap();

if (remoteObj == null)
{
throw new ArgumentNullException(
$"Could not create remote object instance {className}"
+ $" from assembly {assemblyFilePath}"
+ $" in AppDomain {appDomainName}");
}

T remoteTracer = remoteObj as T;

if (remoteTracer == null)
{
Type type1 = remoteObj.GetType();
Type type2 = typeof(T);
string codebase1 = type1.GetTypeInfo().Assembly.CodeBase;
string codebase2 = type2.GetTypeInfo().Assembly.CodeBase;

throw new InvalidCastException(
$"Cannot cast server object {type1} from assembly {codebase1} to type {type2} from assembly {codebase2}");
}

return remoteTracer;
}

/// <summary>
/// Construct AppDomain configuration metadata based on the current execution environment.
/// </summary>
/// <returns>AppDomainSetup info for creating an new child AppDomain.</returns>
public static AppDomainSetup GetAppDomainSetupInfo()
{
AppDomain currentAppDomain = AppDomain.CurrentDomain;

return new AppDomainSetup
{
ApplicationBase = Directory.GetCurrentDirectory(),
ConfigurationFile = currentAppDomain.SetupInformation.ConfigurationFile,
ShadowCopyFiles = currentAppDomain.SetupInformation.ShadowCopyFiles,
ShadowCopyDirectories = currentAppDomain.SetupInformation.ShadowCopyDirectories,
CachePath = currentAppDomain.SetupInformation.CachePath
};
}
}
}
41 changes: 3 additions & 38 deletions ServerHost/ServerHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -54,33 +53,17 @@ public static ServerHostHandle<TServer> LoadServerInNewAppDomain<TServer>(
}
}

AppDomainSetup setup = GetAppDomainSetupInfo();
AppDomainSetup setup = AppDomainSetupUtils.GetAppDomainSetupInfo();

AppDomain appDomain = AppDomain.CreateDomain(serverName, null, setup);
LoadedAppDomains[serverName] = appDomain;

// The server class must have a public constructor which
// accepts single parameter of server name.
var args = new object[] { serverName };
var noActivationAttributes = new object[0];

object serverObj = appDomain.CreateInstanceFromAndUnwrap(
serverAssembly, serverTypeName, false,
BindingFlags.Default, null, args, CultureInfo.CurrentCulture,
noActivationAttributes);

TServer server = serverObj as TServer;

if (server == null)
{
Type type1 = serverObj.GetType();
Type type2 = typeof(TServer);
string codebase1 = type1.GetTypeInfo().Assembly.CodeBase;
string codebase2 = serverType.GetTypeInfo().Assembly.CodeBase;

throw new InvalidCastException(
$"Cannot cast server object {type1} from assembly {codebase1} to type {type2} from assembly {codebase2}");
}
TServer server = AppDomainSetupUtils
.CreateObjectInstanceInAppDomain<TServer>(appDomain, args);

appDomain.UnhandledException += ReportUnobservedException;

Expand Down Expand Up @@ -160,24 +143,6 @@ public static void UnloadAllServers()
}
}

/// <summary>
/// Construct AppDomain configuration metadata based on the current execution environment.
/// </summary>
/// <returns>AppDomainSetup info for creating an new child AppDomain.</returns>
public static AppDomainSetup GetAppDomainSetupInfo()
{
AppDomain currentAppDomain = AppDomain.CurrentDomain;

return new AppDomainSetup
{
ApplicationBase = Directory.GetCurrentDirectory(),
ConfigurationFile = currentAppDomain.SetupInformation.ConfigurationFile,
ShadowCopyFiles = currentAppDomain.SetupInformation.ShadowCopyFiles,
ShadowCopyDirectories = currentAppDomain.SetupInformation.ShadowCopyDirectories,
CachePath = currentAppDomain.SetupInformation.CachePath
};
}

/// <summary>
/// Event handle method to report any unhandled exceptions in a newly created AppDomain.
/// </summary>
Expand Down
6 changes: 3 additions & 3 deletions ServerHost/ServerHostFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public ServerHostFixture()

_log = LogManager.GetLogger(_className);

_log.InfoFormat("{0} - Initialize", _className);
_log.Info($"{_className} - Initialize");
}

/// <summary>
Expand All @@ -51,7 +51,7 @@ protected virtual void Dispose(bool disposing)
ReleaseUnmanagedResources();
if (disposing)
{
_log.InfoFormat("{0} - Dispose", _className);
_log.Info($"{_className} - Dispose");

// TODO release managed resources here
}
Expand All @@ -68,7 +68,7 @@ protected virtual void Dispose(bool disposing)
[SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Local")]
private void ReleaseUnmanagedResources()
{
_log.InfoFormat("{0} - ReleaseUnmanagedResources", _className);
_log.Info($"{_className} - ReleaseUnmanagedResources");

// TODO release unmanaged resources here
}
Expand Down
23 changes: 23 additions & 0 deletions Tests/TestServer/App.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<log4net>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%-5level [%thread] %logger - %message%newline" />
</layout>
</appender>
<appender name="TraceAppender" type="log4net.Appender.TraceAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<root>
<level value="INFO" />
<appender-ref ref="ConsoleAppender" />
<appender-ref ref="TraceAppender" />
</root>
</log4net>
</configuration>
8 changes: 6 additions & 2 deletions Tests/TestServer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Reflection;
using JetBrains.Annotations;
using log4net;
using log4net.Config;

Expand All @@ -11,6 +12,7 @@

namespace Server.Host.Tests.TestServer
{
[PublicAPI]
public static class Program
{
private static readonly ILog Log = LogManager.GetLogger("TestServer");
Expand All @@ -19,15 +21,17 @@ public static void Main(string[] args)
{
const string serverName = "MyTestServer";

string progName = Assembly.GetEntryAssembly().GetName().Name;

Server server = new Server(serverName);

Log.InfoFormat("Initializing Server {0} with args = {1}", serverName, string.Join(", ", args));
Log.Info($"Initializing Server {serverName} with args = " + string.Join(", ", args));
server.InitServer();

Log.Info("Running Server");
int rc = server.Run();

Log.InfoFormat("{0}.exe finished with rc={1}", Assembly.GetEntryAssembly().GetName().Name, rc);
Log.Info($"{progName}.exe finished with rc={rc}");
Environment.Exit(rc);
}
}
Expand Down
11 changes: 9 additions & 2 deletions Tests/TestServer/Server.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// Licensed with Apache 2.0 https://github.com/jthelin/ServerHost/blob/master/LICENSE

using System;
using System.Diagnostics;
using log4net;
using log4net.Config;

namespace Server.Host.Tests.TestServer
{
Expand All @@ -12,12 +14,17 @@ public class Server : MarshalByRefObject

public Server(string serverName)
{
Log.InfoFormat("Server - {0}", serverName);
// Initialize log4net logging.
XmlConfigurator.Configure();

Log.Info($"Server - {serverName}");
}

public void InitServer()
{
Log.Info("InitServer!");
Log.Info("InitServer - log4net");
Debug.WriteLine("InitServer - Debug.WriteLine");
Trace.TraceInformation("InitServer - Trace.TraceInformation");
}

public int Run()
Expand Down
3 changes: 3 additions & 0 deletions Tests/TestServer/TestServer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2018.2.1">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="log4net" Version="2.0.5" />
</ItemGroup>
</Project>
23 changes: 23 additions & 0 deletions Tests/Tests.Net46/App.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<log4net>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%-5level [%thread] %logger - %message%newline" />
</layout>
</appender>
<appender name="TraceAppender" type="log4net.Appender.TraceAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<root>
<level value="INFO" />
<appender-ref ref="ConsoleAppender" />
<appender-ref ref="TraceAppender" />
</root>
</log4net>
</configuration>

0 comments on commit d07e76f

Please sign in to comment.