return ResolveFactory<THostBuilder>(assembly, CreateHostBuilder);
}
- public static Func<string[], object>? ResolveHostFactory(Assembly assembly, TimeSpan? waitTimeout = null, bool stopApplication = true, Action<object>? configureHostBuilder = null)
+ // This helpers encapsulates all of the complex logic required to:
+ // 1. Execute the entry point of the specified assembly in a different thread.
+ // 2. Wait for the diagnostic source events to fire
+ // 3. Give the caller a chance to execute logic to mutate the IHostBuilder
+ // 4. Resolve the instance of the applications's IHost
+ // 5. Allow the caller to determine if the entry point has completed
+ public static Func<string[], object>? ResolveHostFactory(Assembly assembly,
+ TimeSpan? waitTimeout = null,
+ bool stopApplication = true,
+ Action<object>? configureHostBuilder = null,
+ Action<Exception?>? entrypointCompleted = null)
{
if (assembly.EntryPoint is null)
{
try
{
// Attempt to load hosting and check the version to make sure the events
- // even have a change of firing (they were adding in .NET >= 6)
+ // even have a chance of firing (they were added in .NET >= 6)
var hostingAssembly = Assembly.Load("Microsoft.Extensions.Hosting");
if (hostingAssembly.GetName().Version is Version version && version.Major < 6)
{
return null;
}
- return args => new HostingListener(args, assembly.EntryPoint, waitTimeout ?? s_defaultWaitTimeout, stopApplication, configureHostBuilder).CreateHost();
+ return args => new HostingListener(args, assembly.EntryPoint, waitTimeout ?? s_defaultWaitTimeout, stopApplication, configureHostBuilder, entrypointCompleted).CreateHost();
}
private static Func<string[], T>? ResolveFactory<T>(Assembly assembly, string name)
private readonly TaskCompletionSource<object> _hostTcs = new();
private IDisposable? _disposable;
private Action<object>? _configure;
+ private Action<Exception?>? _entrypointCompleted;
- public HostingListener(string[] args, MethodInfo entryPoint, TimeSpan waitTimeout, bool stopApplication, Action<object>? configure)
+ public HostingListener(string[] args, MethodInfo entryPoint, TimeSpan waitTimeout, bool stopApplication, Action<object>? configure, Action<Exception?>? entrypointCompleted)
{
_args = args;
_entryPoint = entryPoint;
_waitTimeout = waitTimeout;
_stopApplication = stopApplication;
_configure = configure;
+ _entrypointCompleted = entrypointCompleted;
}
public object CreateHost()
// in case we need to timeout the execution
var thread = new Thread(() =>
{
+ Exception? exception = null;
+
try
{
var parameters = _entryPoint.GetParameters();
}
catch (TargetInvocationException tie)
{
+ exception = tie.InnerException ?? tie;
+
// Another exception happened, propagate that to the caller
- _hostTcs.TrySetException(tie.InnerException ?? tie);
+ _hostTcs.TrySetException(exception);
}
catch (Exception ex)
{
+ exception = ex;
+
// Another exception happened, propagate that to the caller
_hostTcs.TrySetException(ex);
}
+ finally
+ {
+ // Signal that the entry point is completed
+ _entrypointCompleted?.Invoke(exception);
+ }
})
{
// Make sure this doesn't hang the process
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
+using System.Threading;
using Microsoft.DotNet.RemoteExecutor;
using Xunit;
}
[ConditionalFact(nameof(RequirementsMet))]
+ [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPattern.Program))]
+ public void NoSpecialEntryPointPatternHostBuilderConfigureHostBuilderCallbackIsCalled()
+ {
+ using var _ = RemoteExecutor.Invoke(() =>
+ {
+ bool called = false;
+ void ConfigureHostBuilder(object hostBuilder)
+ {
+ Assert.IsAssignableFrom<IHostBuilder>(hostBuilder);
+ called = true;
+ }
+
+ var factory = HostFactoryResolver.ResolveHostFactory(typeof(NoSpecialEntryPointPattern.Program).Assembly, waitTimeout: s_WaitTimeout, configureHostBuilder: ConfigureHostBuilder);
+
+ Assert.NotNull(factory);
+ Assert.IsAssignableFrom<IHost>(factory(Array.Empty<string>()));
+ Assert.True(called);
+ });
+ }
+
+ [ConditionalFact(nameof(RequirementsMet))]
+ [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPattern.Program))]
+ public void NoSpecialEntryPointPatternBuildsThenThrowsCallsEntryPointCompletedCallback()
+ {
+ using var _ = RemoteExecutor.Invoke(() =>
+ {
+ var wait = new ManualResetEventSlim(false);
+ Exception? entryPointException = null;
+ void EntryPointCompleted(Exception? exception)
+ {
+ entryPointException = exception;
+ wait.Set();
+ }
+
+ var factory = HostFactoryResolver.ResolveHostFactory(typeof(NoSpecialEntryPointPattern.Program).Assembly, waitTimeout: s_WaitTimeout, stopApplication: false, entrypointCompleted: EntryPointCompleted);
+
+ Assert.NotNull(factory);
+ Assert.IsAssignableFrom<IHost>(factory(Array.Empty<string>()));
+ Assert.True(wait.Wait(s_WaitTimeout));
+ Assert.Null(entryPointException);
+ });
+ }
+
+ [ConditionalFact(nameof(RequirementsMet))]
+ [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternBuildsThenThrows.Program))]
+ public void NoSpecialEntryPointPatternBuildsThenThrowsCallsEntryPointCompletedCallbackWithException()
+ {
+ using var _ = RemoteExecutor.Invoke(() =>
+ {
+ var wait = new ManualResetEventSlim(false);
+ Exception? entryPointException = null;
+ void EntryPointCompleted(Exception? exception)
+ {
+ entryPointException = exception;
+ wait.Set();
+ }
+
+ var factory = HostFactoryResolver.ResolveHostFactory(typeof(NoSpecialEntryPointPatternBuildsThenThrows.Program).Assembly, waitTimeout: s_WaitTimeout, stopApplication: false, entrypointCompleted: EntryPointCompleted);
+
+ Assert.NotNull(factory);
+ Assert.IsAssignableFrom<IHost>(factory(Array.Empty<string>()));
+ Assert.True(wait.Wait(s_WaitTimeout));
+ Assert.NotNull(entryPointException);
+ });
+ }
+
+ [ConditionalFact(nameof(RequirementsMet))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternThrows.Program))]
public void NoSpecialEntryPointPatternThrows()
{