未验证 提交 b88b34c6 编写于 作者: D David Fowler 提交者: GitHub

Follow up on HostFactoryResolver changes (#54052)

- Cleaned up the comments to explain what ResolveHostFactory does.
- Added entry point completed callback to let callers know when the entry point code is done running.
- Added tests for the various callbacks.
上级 75ede832
......@@ -38,7 +38,17 @@ internal sealed class HostFactoryResolver
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)
{
......@@ -48,7 +58,7 @@ internal sealed class HostFactoryResolver
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)
{
......@@ -64,7 +74,7 @@ internal sealed class HostFactoryResolver
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)
......@@ -169,14 +179,16 @@ private class HostingListener : IObserver<DiagnosticListener>, IObserver<KeyValu
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()
......@@ -187,6 +199,8 @@ public object CreateHost()
// in case we need to timeout the execution
var thread = new Thread(() =>
{
Exception? exception = null;
try
{
var parameters = _entryPoint.GetParameters();
......@@ -209,14 +223,23 @@ public object CreateHost()
}
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
......
......@@ -5,6 +5,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Threading;
using Microsoft.DotNet.RemoteExecutor;
using Xunit;
......@@ -151,6 +152,73 @@ public void NoSpecialEntryPointPattern()
});
}
[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()
......
......@@ -22,6 +22,7 @@
<ProjectReference Include="CreateWebHostBuilderInvalidSignature\CreateWebHostBuilderInvalidSignature.csproj" />
<ProjectReference Include="CreateWebHostBuilderPatternTestSite\CreateWebHostBuilderPatternTestSite.csproj" />
<ProjectReference Include="NoSpecialEntryPointPattern\NoSpecialEntryPointPattern.csproj" />
<ProjectReference Include="NoSpecialEntryPointPatternBuildsThenThrows\NoSpecialEntryPointPatternBuildsThenThrows.csproj" />
<ProjectReference Include="NoSpecialEntryPointPatternThrows\NoSpecialEntryPointPatternThrows.csproj" />
<ProjectReference Include="NoSpecialEntryPointPatternExits\NoSpecialEntryPointPatternExits.csproj" />
<ProjectReference Include="NoSpecialEntryPointPatternHangs\NoSpecialEntryPointPatternHangs.csproj" />
......
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent);net461</TargetFrameworks>
<EnableDefaultItems>true</EnableDefaultItems>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MockHostTypes\MockHostTypes.csproj" />
</ItemGroup>
</Project>
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using Microsoft.Extensions.Hosting;
namespace NoSpecialEntryPointPatternBuildsThenThrows
{
public class Program
{
public static void Main(string[] args)
{
var host = new HostBuilder().Build();
throw new Exception("Main just throws");
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册