Skip to content

Commit

Permalink
Fixing ReSharper slowness, closes machine#151
Browse files Browse the repository at this point in the history
We now reuse an AppDomain per assembly. Makes the runner code suck even more. Yay!
  • Loading branch information
agross committed Jul 19, 2013
1 parent 15fbbeb commit 10dcac2
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 132 deletions.
2 changes: 2 additions & 0 deletions Source/Machine.Specifications/Runner/ISpecificationRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ public interface ISpecificationRunner
void RunAssemblies(IEnumerable<Assembly> assemblies);
void RunNamespace(Assembly assembly, string targetNamespace);
void RunMember(Assembly assembly, MemberInfo member);
void StartRun(Assembly assembly);
void EndRun(Assembly assembly);
}
}
204 changes: 90 additions & 114 deletions Source/Machine.Specifications/Runner/Impl/AppDomainRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,198 +4,174 @@
using System.Reflection;
using System.Security;

using Machine.Specifications.Runner.Impl.Listener;
using Machine.Specifications.Utility;

namespace Machine.Specifications.Runner.Impl
{
public class AppDomainRunner : ISpecificationRunner
{
readonly ISpecificationRunListener _listener;
readonly ISpecificationRunListener _internalListener;
readonly RunOptions _options;

public AppDomainRunner(ISpecificationRunListener listener, RunOptions options)
{
_internalListener = listener;
_listener = new RemoteRunListener(listener);
_options = options;
}

[SecuritySafeCritical]
public void RunAssembly(Assembly assembly)
{
_internalListener.OnRunStart();

InternalRunAssembly(assembly);

_internalListener.OnRunEnd();
try
{
StartRun(assembly);
GetOrCreateAppDomainRunner(assembly).Runner.RunAssembly(assembly);
}
finally
{
EndRun(assembly);
}
}

[SecuritySafeCritical]
public void RunAssemblies(IEnumerable<Assembly> assemblies)
{
_internalListener.OnRunStart();

assemblies.Each(InternalRunAssembly);

_internalListener.OnRunEnd();
assemblies.Each(RunAssembly);
}

[SecuritySafeCritical]
public void RunNamespace(Assembly assembly, string targetNamespace)
{
_internalListener.OnRunStart();

var appDomain = CreateAppDomain(assembly);
CreateRunnerAndUnloadAppDomain("Namespace", appDomain, assembly, targetNamespace);

_internalListener.OnRunEnd();
StartRun(assembly);
GetOrCreateAppDomainRunner(assembly).Runner.RunNamespace(assembly, targetNamespace);
}

[SecuritySafeCritical]
public void RunMember(Assembly assembly, MemberInfo member)
{
_internalListener.OnRunStart();
StartRun(assembly);
GetOrCreateAppDomainRunner(assembly).Runner.RunMember(assembly, member);
}

var appDomain = CreateAppDomain(assembly);
CreateRunnerAndUnloadAppDomain("Member", appDomain, assembly, member);
[SecuritySafeCritical]
public void StartRun(Assembly assembly)
{
if (RunnerWasCreated(assembly))
{
return;
}

_internalListener.OnRunEnd();
GetOrCreateAppDomainRunner(assembly).Runner.StartRun(assembly);
}

[SecuritySafeCritical]
void InternalRunAssembly(Assembly assembly)
public void EndRun(Assembly assembly)
{
if (!RunnerWasCreated(assembly))
{
return;
}

var appDomainRunner = GetOrCreateAppDomainRunner(assembly);
RemoveEntryFor(assembly);
try
{
appDomainRunner.Runner.EndRun(assembly);
}
finally
{
AppDomain.Unload(appDomainRunner.AppDomain);
RemoveEntryFor(assembly);
}
}

void RemoveEntryFor(Assembly assembly)
{
var appDomain = CreateAppDomain(assembly);
CreateRunnerAndUnloadAppDomain("Assembly", appDomain, assembly);
_appDomains.Remove(assembly);
}

[SecuritySafeCritical]
void CreateRunnerAndUnloadAppDomain(string runMethod, AppDomain appDomain, Assembly assembly, params object[] args)
DefaultRunner CreateRunnerInSeparateAppDomain(AppDomain appDomain, Assembly assembly)
{
var mspecAssemblyFilename = Path.Combine(Path.GetDirectoryName(assembly.Location), "Machine.Specifications.dll");

var mspecAssemblyName = AssemblyName.GetAssemblyName(mspecAssemblyFilename);

var constructorArgs = new object[args.Length + 3];
var constructorArgs = new object[2];
constructorArgs[0] = _listener;
constructorArgs[1] = assembly;
constructorArgs[2] = _options;
Array.Copy(args, 0, constructorArgs, 3, args.Length);
constructorArgs[1] = _options;

using (new SpecAssemblyResolver(assembly))
{
try
{
appDomain.CreateInstanceAndUnwrap(mspecAssemblyName.FullName,
"Machine.Specifications.Runner.Impl.AppDomainRunner+" + runMethod + "Runner",
false,
0,
null,
constructorArgs,
null,
null,
null);
return (DefaultRunner) appDomain.CreateInstanceAndUnwrap(mspecAssemblyName.FullName,
"Machine.Specifications.Runner.Impl.DefaultRunner",
false,
0,
null,
constructorArgs,
null,
null,
null);
}
catch (Exception err)
{
Console.Error.WriteLine("Runner failure: " + err);
throw;
}
finally
{
AppDomain.Unload(appDomain);
}
}
}

static AppDomain CreateAppDomain(Assembly assembly)
{
var appDomainSetup = new AppDomainSetup();
appDomainSetup.ApplicationBase = Path.GetDirectoryName(assembly.Location);
appDomainSetup.ApplicationName = Guid.NewGuid().ToString();
appDomainSetup.ConfigurationFile = GetConfigFile(assembly);
readonly Dictionary<Assembly, AppDomainAndRunner> _appDomains = new Dictionary<Assembly, AppDomainAndRunner>();

return AppDomain.CreateDomain(appDomainSetup.ApplicationName, null, appDomainSetup);
}

static string GetConfigFile(Assembly assembly)
AppDomainAndRunner GetOrCreateAppDomainRunner(Assembly assembly)
{
var configFile = assembly.Location + ".config";

if (File.Exists(configFile))
AppDomainAndRunner appDomainAndRunner;
if (_appDomains.TryGetValue(assembly, out appDomainAndRunner))
{
return configFile;
return appDomainAndRunner;
}

return null;
var appDomainSetup = new AppDomainSetup
{
ApplicationBase = Path.GetDirectoryName(assembly.Location),
ApplicationName = Guid.NewGuid().ToString(),
ConfigurationFile = GetConfigFile(assembly)
};

var appDomain = AppDomain.CreateDomain(appDomainSetup.ApplicationName, null, appDomainSetup);
var runner = CreateRunnerInSeparateAppDomain(appDomain, assembly);

_appDomains.Add(assembly, new AppDomainAndRunner
{
AppDomain = appDomain,
Runner = runner
});

return GetOrCreateAppDomainRunner(assembly);
}

public class AssemblyRunner : MarshalByRefObject
bool RunnerWasCreated(Assembly assembly)
{
public AssemblyRunner(ISpecificationRunListener listener, Assembly assembly, RunOptions options)
{
try
{
var runner = new DefaultRunner(listener, options);
runner.RunAssembly(assembly);
}
catch (Exception err)
{
listener.OnFatalError(new ExceptionResult(err));
}
}

[SecurityCritical]
public override object InitializeLifetimeService()
{
return null;
}
return _appDomains.ContainsKey(assembly);
}

public class NamespaceRunner : MarshalByRefObject
static string GetConfigFile(Assembly assembly)
{
public NamespaceRunner(ISpecificationRunListener listener, Assembly assembly, RunOptions options, string targetNamespace)
{
try
{
var runner = new DefaultRunner(listener, options);
runner.RunNamespace(assembly, targetNamespace);
}
catch (Exception err)
{
listener.OnFatalError(new ExceptionResult(err));
}
}
var configFile = assembly.Location + ".config";

[SecurityCritical]
public override object InitializeLifetimeService()
if (File.Exists(configFile))
{
return null;
return configFile;
}

return null;
}

public class MemberRunner : MarshalByRefObject
class AppDomainAndRunner
{
public MemberRunner(ISpecificationRunListener listener, Assembly assembly, RunOptions options, MemberInfo memberInfo)
{
try
{
var runner = new DefaultRunner(listener, options);
runner.RunMember(assembly, memberInfo);
}
catch (Exception err)
{
listener.OnFatalError(new ExceptionResult(err));
}
}

[SecurityCritical]
public override object InitializeLifetimeService()
{
return null;
}
public AppDomain AppDomain { get; set; }
public DefaultRunner Runner { get; set; }
}
}
}
12 changes: 7 additions & 5 deletions Source/Machine.Specifications/Runner/Impl/AssemblyRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@ public AssemblyRunner(ISpecificationRunListener listener, RunOptions options)

public void Run(Assembly assembly, IEnumerable<Context> contexts)
{
var hasExecutableSpecifications = contexts.Any(x => x.HasExecutableSpecifications);

var explorer = new AssemblyExplorer();
var globalCleanups = explorer.FindAssemblyWideContextCleanupsIn(assembly).ToList();
var specificationSupplements = explorer.FindSpecificationSupplementsIn(assembly).ToList();
var hasExecutableSpecifications = false;

try
{
hasExecutableSpecifications = contexts.Any(x => x.HasExecutableSpecifications);

var explorer = new AssemblyExplorer();
var globalCleanups = explorer.FindAssemblyWideContextCleanupsIn(assembly).ToList();
var specificationSupplements = explorer.FindSpecificationSupplementsIn(assembly).ToList();

if (hasExecutableSpecifications)
{
_assemblyStart(assembly);
Expand Down
20 changes: 10 additions & 10 deletions Source/Machine.Specifications/Runner/Impl/DefaultRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security;

using Machine.Specifications.Explorers;
using Machine.Specifications.Model;
using Machine.Specifications.Utility;

namespace Machine.Specifications.Runner.Impl
{
public class DefaultRunner : ISpecificationRunner
[Serializable]
public class DefaultRunner : MarshalByRefObject, ISpecificationRunner
{
readonly ISpecificationRunListener _listener;
readonly RunOptions _options;
Expand Down Expand Up @@ -105,20 +107,12 @@ void StartRun(IDictionary<Assembly, IEnumerable<Context>> contextMap)
// TODO: move this filtering to a more sensible place
var contexts = pair.Value.FilteredBy(_options);

if (contexts.Any())
{
StartAssemblyRun(assembly, contexts);
}
_assemblyRunner.Run(assembly, contexts);
}

_runEnd();
}

void StartAssemblyRun(Assembly assembly, IEnumerable<Context> contexts)
{
_assemblyRunner.Run(assembly, contexts.FilteredBy(_options));
}

public void StartRun(Assembly assembly)
{
_runStart = () => {};
Expand All @@ -133,6 +127,12 @@ public void EndRun(Assembly assembly)
_assemblyRunner.EndExplicitRunScope(assembly);
_listener.OnRunEnd();
}

[SecurityCritical]
public override object InitializeLifetimeService()
{
return null;
}
}

public static class TagFilteringExtensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ public RemoteRunListener(ISpecificationRunListener listener)

public void OnRunStart()
{
_listener.OnRunStart();
}

public void OnRunEnd()
{
_listener.OnRunEnd();
}

public void OnAssemblyStart(AssemblyInfo assembly)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ public override void ExecuteRecursive(TaskExecutionNode node)
var task = (RunAssemblyTask) node.RemoteTask;

var contextAssembly = LoadContextAssembly(task);
if (contextAssembly == null)
{
return;
}

var result = VersionCompatibilityChecker.Check(contextAssembly);
if (!result.Success)
Expand Down
Loading

0 comments on commit 10dcac2

Please sign in to comment.