From 42b36a171f42d51d4faaefb6a05c7dd2497a811a Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 4 Jun 2021 20:45:44 -0700 Subject: [PATCH 01/17] Fire diagnostic source events from IHostBuilder.Build - We want to enable getting access to the IHostBuilder and IHost during the call to IHostBuilder.Build so that we can access the application's IServiceProvider from various tools. Usually, this is enabled by exposing a different entry point from the application but this technique allows us to let the application execute normally and hook important events via a diagnostic source. --- .../src/HostBuilder.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs b/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs index 1cc812f6f8f199..3067a5476ed8f2 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Reflection; using Microsoft.Extensions.Configuration; @@ -127,13 +128,30 @@ public IHost Build() } _hostBuilt = true; + // REVIEW: If we want to raise more events outside of these calls then we will need to + // stash this in a field. + using var diagnosticListener = new DiagnosticListener("Microsoft.Extensions.Hosting"); + const string hostBuildingEventName = "HostBuilding"; + const string hostBuiltEventName = "HostBuilt"; + + if (diagnosticListener.IsEnabled() && diagnosticListener.IsEnabled(hostBuildingEventName)) + { + diagnosticListener.Write(hostBuildingEventName, this); + } + BuildHostConfiguration(); CreateHostingEnvironment(); CreateHostBuilderContext(); BuildAppConfiguration(); CreateServiceProvider(); - return _appServices.GetRequiredService(); + var host = _appServices.GetRequiredService(); + if (diagnosticListener.IsEnabled() && diagnosticListener.IsEnabled(hostBuiltEventName)) + { + diagnosticListener.Write(hostBuiltEventName, host); + } + + return host; } private void BuildHostConfiguration() From ec7831b114111703ec4837be832cd6b2a4594eb8 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 4 Jun 2021 23:46:12 -0700 Subject: [PATCH 02/17] Handle linker warnings --- .../src/HostBuilder.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs b/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs index 3067a5476ed8f2..d7bba60f23d7f9 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection; using Microsoft.Extensions.Configuration; @@ -136,7 +137,7 @@ public IHost Build() if (diagnosticListener.IsEnabled() && diagnosticListener.IsEnabled(hostBuildingEventName)) { - diagnosticListener.Write(hostBuildingEventName, this); + Write(diagnosticListener, hostBuildingEventName, this); } BuildHostConfiguration(); @@ -148,12 +149,22 @@ public IHost Build() var host = _appServices.GetRequiredService(); if (diagnosticListener.IsEnabled() && diagnosticListener.IsEnabled(hostBuiltEventName)) { - diagnosticListener.Write(hostBuiltEventName, host); + Write(diagnosticListener, hostBuiltEventName, host); } return host; } + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", + Justification = "The values being passed into Write are being consumed by the application already.")] + private static void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>( + DiagnosticSource diagnosticSource, + string name, + T value) + { + diagnosticSource.Write(name, value); + } + private void BuildHostConfiguration() { IConfigurationBuilder configBuilder = new ConfigurationBuilder() From d6cded5e9276839e6b5992df2117db42ce691fd0 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 4 Jun 2021 23:47:12 -0700 Subject: [PATCH 03/17] Remove DAM annotations --- src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs b/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs index d7bba60f23d7f9..7ed38f701f6ae6 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs @@ -157,7 +157,7 @@ public IHost Build() [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", Justification = "The values being passed into Write are being consumed by the application already.")] - private static void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>( + private static void Write( DiagnosticSource diagnosticSource, string name, T value) From 1c7b5097f2e749039a555f8b323f615008088ae0 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 5 Jun 2021 20:47:33 -0700 Subject: [PATCH 04/17] Add support for the new pattern in HostFactoryResolver - Added support for resolving an IServiceProvider using the new diagnostics source pattern (which doesn't require a different entrypoint) --- .../src/HostFactoryResolver.cs | 180 ++++++++++++++++++ .../Program.cs | 1 + .../Program.cs | 1 + .../tests/HostFactoryResolverTests.cs | 15 +- ...xtensions.HostFactoryResolver.Tests.csproj | 5 + .../tests/MockHostTypes/Host.cs | 12 -- .../tests/MockHostTypes/HostBuilder.cs | 10 - .../tests/MockHostTypes/IHost.cs | 12 -- .../tests/MockHostTypes/IHostBuilder.cs | 10 - .../tests/MockHostTypes/MockHostTypes.csproj | 4 + .../NoSpecialEntryPointPattern.csproj | 13 ++ .../NoSpecialEntryPointPattern/Program.cs | 16 ++ 12 files changed, 233 insertions(+), 46 deletions(-) delete mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/Host.cs delete mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/HostBuilder.cs delete mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/IHost.cs delete mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/IHostBuilder.cs create mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPattern/NoSpecialEntryPointPattern.csproj create mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPattern/Program.cs diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs index 9b3a30734eb2ab..35a229bc1e76f3 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs @@ -2,7 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; #nullable enable @@ -31,6 +37,16 @@ internal sealed class HostFactoryResolver return ResolveFactory(assembly, CreateHostBuilder); } + public static Func? ResolveHostBuilderFactory(Assembly assembly) + { + if (assembly.EntryPoint is null) + { + return null; + } + + return args => new DeferredHostBuilder(args, assembly); + } + private static Func? ResolveFactory(Assembly assembly, string name) { var programType = assembly?.EntryPoint?.DeclaringType; @@ -93,6 +109,17 @@ private static bool IsFactory(MethodInfo? factory) }; } + var deferredFactory = ResolveHostBuilderFactory(assembly); + if (deferredFactory != null) + { + return args => + { + var hostBuilder = deferredFactory(args); + var host = hostBuilder.Build(); + return host.Services; + }; + } + return null; } @@ -112,5 +139,158 @@ private static bool IsFactory(MethodInfo? factory) var servicesProperty = hostType.GetProperty("Services", DeclaredOnlyLookup); return (IServiceProvider?)servicesProperty?.GetValue(host); } + + // This host builder captures calls to the IHostBuilder then replays them on the application's + // IHostBuilder when the event fires + private class DeferredHostBuilder : IHostBuilder, IObserver, IObserver> + { + public IDictionary Properties { get; } = new Dictionary(); + + private readonly string[] _args; + private readonly Assembly _assembly; + + private readonly TaskCompletionSource _hostTcs = new(); + private IDisposable? _disposable; + + private Action _configure = b => { }; + + // The amount of time we wait for the diagnostic source events to fire + private static readonly TimeSpan _waitTimeout = TimeSpan.FromSeconds(5); + + public DeferredHostBuilder(string[] args, Assembly assembly) + { + _args = args; + _assembly = assembly; + } + + public IHost Build() + { + using var subscription = DiagnosticListener.AllListeners.Subscribe(this); + + // Kick off the entry point on a new thread so we don't block the current one + // in case we need to timeout the execution + var thread = new Thread(() => + { + try + { + _assembly.EntryPoint!.Invoke(null, new object[] { _args }); + + // Try to set an exception if the entrypoint returns gracefully, this will force + // build to throw + _hostTcs.TrySetException(new InvalidOperationException("Unable to build IHost")); + } + catch (TargetInvocationException tie) when (tie.InnerException is StopTheHostException) + { + // The host was stopped by our own logic + } + catch (TargetInvocationException tie) + { + // Another exception happened, propagate that to the caller + _hostTcs.TrySetException(tie.InnerException ?? tie); + } + catch (Exception ex) + { + // Another exception happened, propagate that to the caller + _hostTcs.TrySetException(ex); + } + }) + { + // Make sure this doesn't hang the process + IsBackground = true + }; + + // Start the thread + thread.Start(); + + // Wait before throwing an exception + using var cts = new CancellationTokenSource(_waitTimeout); + _hostTcs.Task.Wait(cts.Token); + + Debug.Assert(_hostTcs.Task.IsCompleted); + + return _hostTcs.Task.Result; + } + + public IHostBuilder ConfigureAppConfiguration(Action configureDelegate) + { + _configure += b => b.ConfigureAppConfiguration(configureDelegate); + return this; + } + + public IHostBuilder ConfigureContainer(Action configureDelegate) + { + _configure += b => b.ConfigureContainer(configureDelegate); + return this; + } + + public IHostBuilder ConfigureHostConfiguration(Action configureDelegate) + { + _configure += b => b.ConfigureHostConfiguration(configureDelegate); + return this; + } + + public IHostBuilder ConfigureServices(Action configureDelegate) + { + _configure += b => b.ConfigureServices(configureDelegate); + return this; + } + + public IHostBuilder UseServiceProviderFactory(IServiceProviderFactory factory) where TContainerBuilder : notnull + { + _configure += b => b.UseServiceProviderFactory(factory); + return this; + } + + public IHostBuilder UseServiceProviderFactory(Func> factory) where TContainerBuilder : notnull + { + _configure += b => b.UseServiceProviderFactory(factory); + return this; + } + + public void OnCompleted() + { + _disposable?.Dispose(); + } + + public void OnError(Exception error) + { + + } + + public void OnNext(DiagnosticListener value) + { + if (value.Name == "Microsoft.Extensions.Hosting") + { + _disposable = value.Subscribe(this); + } + } + + public void OnNext(KeyValuePair value) + { + if (value.Key == "HostBuilding") + { + if (value.Value is IHostBuilder builder) + { + _configure(builder); + } + } + + if (value.Key == "HostBuilt") + { + if (value.Value is IHost host) + { + _hostTcs.TrySetResult(host); + + // Stop the host from running further + throw new StopTheHostException(); + } + } + } + + private class StopTheHostException : Exception + { + + } + } } } diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/CreateHostBuilderInvalidSignature/Program.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/CreateHostBuilderInvalidSignature/Program.cs index b1b233eb95d519..356b9ddcc7f93e 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/CreateHostBuilderInvalidSignature/Program.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/CreateHostBuilderInvalidSignature/Program.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using MockHostTypes; +using Microsoft.Extensions.Hosting; namespace CreateHostBuilderInvalidSignature { diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/CreateHostBuilderPatternTestSite/Program.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/CreateHostBuilderPatternTestSite/Program.cs index 515615731fa09c..55e872f8f34127 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/CreateHostBuilderPatternTestSite/Program.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/CreateHostBuilderPatternTestSite/Program.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using MockHostTypes; +using Microsoft.Extensions.Hosting; namespace CreateHostBuilderPatternTestSite { diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs index 55d53fa668e355..d4aef04f1bb3df 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs @@ -46,7 +46,7 @@ public void BuildWebHostPattern__Invalid_CantFindServiceProvider() { var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(BuildWebHostInvalidSignature.Program).Assembly); - Assert.Null(factory); + Assert.NotNull(factory); } [Fact] @@ -125,7 +125,18 @@ public void CreateHostBuilderPattern__Invalid_CantFindServiceProvider() { var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(CreateHostBuilderInvalidSignature.Program).Assembly); - Assert.Null(factory); + Assert.NotNull(factory); + Assert.Throws(() => factory(Array.Empty())); + } + + [Fact] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPattern.Program))] + public void NoSpecialEntryPointPattern() + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPattern.Program).Assembly); + + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); } } } diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj index 8f5acc14b7f9e7..5574b7ea68bbde 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj @@ -20,5 +20,10 @@ + + + + + diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/Host.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/Host.cs deleted file mode 100644 index 31e4aa91efb7e8..00000000000000 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/Host.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; - -namespace MockHostTypes -{ - public class Host : IHost - { - public IServiceProvider Services { get; } = new ServiceProvider(); - } -} diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/HostBuilder.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/HostBuilder.cs deleted file mode 100644 index 92b7f9b290fbb2..00000000000000 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/HostBuilder.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace MockHostTypes -{ - public class HostBuilder : IHostBuilder - { - public IHost Build() => new Host(); - } -} diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/IHost.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/IHost.cs deleted file mode 100644 index 410e44c2dc283a..00000000000000 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/IHost.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; - -namespace MockHostTypes -{ - public interface IHost - { - IServiceProvider Services { get; } - } -} diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/IHostBuilder.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/IHostBuilder.cs deleted file mode 100644 index ef73665296d748..00000000000000 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/IHostBuilder.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace MockHostTypes -{ - public interface IHostBuilder - { - IHost Build(); - } -} diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/MockHostTypes.csproj b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/MockHostTypes.csproj index 88e03293e6ce4c..ed642b4209adba 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/MockHostTypes.csproj +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/MockHostTypes/MockHostTypes.csproj @@ -5,4 +5,8 @@ true + + + + diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPattern/NoSpecialEntryPointPattern.csproj b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPattern/NoSpecialEntryPointPattern.csproj new file mode 100644 index 00000000000000..716b25a87281da --- /dev/null +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPattern/NoSpecialEntryPointPattern.csproj @@ -0,0 +1,13 @@ + + + + $(NetCoreAppCurrent);net461 + true + Exe + + + + + + + diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPattern/Program.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPattern/Program.cs new file mode 100644 index 00000000000000..c23d8bba01802d --- /dev/null +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPattern/Program.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using MockHostTypes; +using Microsoft.Extensions.Hosting; + +namespace NoSpecialEntryPointPattern +{ + public class Program + { + public static void Main(string[] args) + { + var host = new HostBuilder().Build(); + } + } +} From d2b567834edbd71bcd00ed5aa35480d0a3f4eef5 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 5 Jun 2021 23:18:34 -0700 Subject: [PATCH 05/17] Made fixes and added more tests --- .../src/HostFactoryResolver.cs | 17 +++++++--- .../Program.cs | 2 +- .../tests/HostFactoryResolverTests.cs | 32 ++++++++++++++++++- ...xtensions.HostFactoryResolver.Tests.csproj | 3 ++ .../NoSpecialEntryPointPattern/Program.cs | 1 - .../NoSpecialEntryPointPatternExits.csproj | 13 ++++++++ .../Program.cs | 15 +++++++++ .../NoSpecialEntryPointPatternHangs.csproj | 13 ++++++++ .../Program.cs | 15 +++++++++ .../NoSpecialEntryPointPatternThrows.csproj | 13 ++++++++ .../Program.cs | 15 +++++++++ 11 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternExits/NoSpecialEntryPointPatternExits.csproj create mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternExits/Program.cs create mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternHangs/NoSpecialEntryPointPatternHangs.csproj create mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternHangs/Program.cs create mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternThrows/NoSpecialEntryPointPatternThrows.csproj create mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternThrows/Program.cs diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs index 35a229bc1e76f3..d4175c77947cad 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs @@ -202,13 +202,22 @@ public IHost Build() // Start the thread thread.Start(); - // Wait before throwing an exception - using var cts = new CancellationTokenSource(_waitTimeout); - _hostTcs.Task.Wait(cts.Token); + try + { + // Wait before throwing an exception + if (!_hostTcs.Task.Wait(_waitTimeout)) + { + throw new InvalidOperationException("Unable to build IHost"); + } + } + catch (AggregateException) when (_hostTcs.Task.IsCompleted) + { + // Lets this propogate out of the call to GetAwaiter().GetResult() + } Debug.Assert(_hostTcs.Task.IsCompleted); - return _hostTcs.Task.Result; + return _hostTcs.Task.GetAwaiter().GetResult(); } public IHostBuilder ConfigureAppConfiguration(Action configureDelegate) diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/CreateHostBuilderInvalidSignature/Program.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/CreateHostBuilderInvalidSignature/Program.cs index 356b9ddcc7f93e..f82816eed4537b 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/CreateHostBuilderInvalidSignature/Program.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/CreateHostBuilderInvalidSignature/Program.cs @@ -10,7 +10,7 @@ public class Program { public static void Main(string[] args) { - var webHost = CreateHostBuilder(null, args).Build(); + var webHost = CreateHostBuilder(null, args)?.Build(); } // Extra parameter diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs index d4aef04f1bb3df..5612f7db03801e 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs @@ -126,7 +126,7 @@ public void CreateHostBuilderPattern__Invalid_CantFindServiceProvider() var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(CreateHostBuilderInvalidSignature.Program).Assembly); Assert.NotNull(factory); - Assert.Throws(() => factory(Array.Empty())); + Assert.Throws(() => factory(Array.Empty())); } [Fact] @@ -138,5 +138,35 @@ public void NoSpecialEntryPointPattern() Assert.NotNull(factory); Assert.IsAssignableFrom(factory(Array.Empty())); } + + [Fact] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternThrows.Program))] + public void NoSpecialEntryPointPatternThrows() + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternThrows.Program).Assembly); + + Assert.NotNull(factory); + Assert.Throws(() => factory(Array.Empty())); + } + + [Fact] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternExits.Program))] + public void NoSpecialEntryPointPatternExits() + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternExits.Program).Assembly); + + Assert.NotNull(factory); + Assert.Throws(() => factory(Array.Empty())); + } + + [Fact] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternHangs.Program))] + public void NoSpecialEntryPointPatternHangs() + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternHangs.Program).Assembly); + + Assert.NotNull(factory); + Assert.Throws(() => factory(Array.Empty())); + } } } diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj index 5574b7ea68bbde..3cfd34fbbc1ec7 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj @@ -21,6 +21,9 @@ + + + diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPattern/Program.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPattern/Program.cs index c23d8bba01802d..4dafef644a8c2e 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPattern/Program.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPattern/Program.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using MockHostTypes; using Microsoft.Extensions.Hosting; namespace NoSpecialEntryPointPattern diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternExits/NoSpecialEntryPointPatternExits.csproj b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternExits/NoSpecialEntryPointPatternExits.csproj new file mode 100644 index 00000000000000..716b25a87281da --- /dev/null +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternExits/NoSpecialEntryPointPatternExits.csproj @@ -0,0 +1,13 @@ + + + + $(NetCoreAppCurrent);net461 + true + Exe + + + + + + + diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternExits/Program.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternExits/Program.cs new file mode 100644 index 00000000000000..9176dae239533f --- /dev/null +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternExits/Program.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace NoSpecialEntryPointPatternExits +{ + public class Program + { + public static void Main(string[] args) + { + + } + } +} diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternHangs/NoSpecialEntryPointPatternHangs.csproj b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternHangs/NoSpecialEntryPointPatternHangs.csproj new file mode 100644 index 00000000000000..716b25a87281da --- /dev/null +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternHangs/NoSpecialEntryPointPatternHangs.csproj @@ -0,0 +1,13 @@ + + + + $(NetCoreAppCurrent);net461 + true + Exe + + + + + + + diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternHangs/Program.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternHangs/Program.cs new file mode 100644 index 00000000000000..e8183fce884337 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternHangs/Program.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace NoSpecialEntryPointPatternHangs +{ + public class Program + { + public static void Main(string[] args) + { + Console.ReadLine(); + } + } +} diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternThrows/NoSpecialEntryPointPatternThrows.csproj b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternThrows/NoSpecialEntryPointPatternThrows.csproj new file mode 100644 index 00000000000000..716b25a87281da --- /dev/null +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternThrows/NoSpecialEntryPointPatternThrows.csproj @@ -0,0 +1,13 @@ + + + + $(NetCoreAppCurrent);net461 + true + Exe + + + + + + + diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternThrows/Program.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternThrows/Program.cs new file mode 100644 index 00000000000000..f01cc36661a471 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternThrows/Program.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace NoSpecialEntryPointPatternThrows +{ + public class Program + { + public static void Main(string[] args) + { + throw new Exception("Main just throws"); + } + } +} From 99d2a7786ea436b777116232fc6155218d726524 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 5 Jun 2021 23:41:02 -0700 Subject: [PATCH 06/17] Handle main with no args --- .../src/HostFactoryResolver.cs | 10 +++++++++- .../tests/HostFactoryResolverTests.cs | 10 ++++++++++ ...t.Extensions.HostFactoryResolver.Tests.csproj | 1 + .../NoSpecialEntryPointPatternMainNoArgs.csproj | 13 +++++++++++++ .../Program.cs | 16 ++++++++++++++++ 5 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternMainNoArgs/NoSpecialEntryPointPatternMainNoArgs.csproj create mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternMainNoArgs/Program.cs diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs index d4175c77947cad..444b66be272ccc 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs @@ -173,7 +173,15 @@ public IHost Build() { try { - _assembly.EntryPoint!.Invoke(null, new object[] { _args }); + var parameters = _assembly.EntryPoint!.GetParameters(); + if (parameters.Length == 0) + { + _assembly.EntryPoint!.Invoke(null, Array.Empty()); + } + else + { + _assembly.EntryPoint!.Invoke(null, new object[] { _args }); + } // Try to set an exception if the entrypoint returns gracefully, this will force // build to throw diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs index 5612f7db03801e..15bfe134cac57f 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs @@ -168,5 +168,15 @@ public void NoSpecialEntryPointPatternHangs() Assert.NotNull(factory); Assert.Throws(() => factory(Array.Empty())); } + + [Fact] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternMainNoArgs.Program))] + public void NoSpecialEntryPointPatternMainNoArgs() + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternMainNoArgs.Program).Assembly); + + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); + } } } diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj index 3cfd34fbbc1ec7..d687786d08f7c1 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj @@ -24,6 +24,7 @@ + diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternMainNoArgs/NoSpecialEntryPointPatternMainNoArgs.csproj b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternMainNoArgs/NoSpecialEntryPointPatternMainNoArgs.csproj new file mode 100644 index 00000000000000..716b25a87281da --- /dev/null +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternMainNoArgs/NoSpecialEntryPointPatternMainNoArgs.csproj @@ -0,0 +1,13 @@ + + + + $(NetCoreAppCurrent);net461 + true + Exe + + + + + + + diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternMainNoArgs/Program.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternMainNoArgs/Program.cs new file mode 100644 index 00000000000000..e304864cd82476 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/NoSpecialEntryPointPatternMainNoArgs/Program.cs @@ -0,0 +1,16 @@ +// 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 NoSpecialEntryPointPatternMainNoArgs +{ + public class Program + { + public static void Main() + { + var host = new HostBuilder().Build(); + } + } +} From 250c87dd2a45a776b534a613613e4a1317a3852a Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 5 Jun 2021 23:48:51 -0700 Subject: [PATCH 07/17] Make sure we copy properties over --- .../src/HostFactoryResolver.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs index 444b66be272ccc..52819f4190c01e 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs @@ -152,7 +152,7 @@ private class DeferredHostBuilder : IHostBuilder, IObserver, private readonly TaskCompletionSource _hostTcs = new(); private IDisposable? _disposable; - private Action _configure = b => { }; + private Action _configure; // The amount of time we wait for the diagnostic source events to fire private static readonly TimeSpan _waitTimeout = TimeSpan.FromSeconds(5); @@ -161,6 +161,15 @@ public DeferredHostBuilder(string[] args, Assembly assembly) { _args = args; _assembly = assembly; + _configure = b => + { + // Copy the properties from this builder into the builder + // that we're going to receive + foreach (var pair in Properties) + { + b.Properties[pair.Key] = pair.Value; + } + }; } public IHost Build() From 08729fe074327e554c938e5fa30f4608f74fabed Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 6 Jun 2021 00:01:19 -0700 Subject: [PATCH 08/17] Store the entrypoint directly --- .../src/HostFactoryResolver.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs index 52819f4190c01e..a6e26179767364 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs @@ -44,7 +44,7 @@ internal sealed class HostFactoryResolver return null; } - return args => new DeferredHostBuilder(args, assembly); + return args => new DeferredHostBuilder(args, assembly.EntryPoint); } private static Func? ResolveFactory(Assembly assembly, string name) @@ -147,7 +147,7 @@ private class DeferredHostBuilder : IHostBuilder, IObserver, public IDictionary Properties { get; } = new Dictionary(); private readonly string[] _args; - private readonly Assembly _assembly; + private readonly MethodInfo _entryPoint; private readonly TaskCompletionSource _hostTcs = new(); private IDisposable? _disposable; @@ -157,10 +157,10 @@ private class DeferredHostBuilder : IHostBuilder, IObserver, // The amount of time we wait for the diagnostic source events to fire private static readonly TimeSpan _waitTimeout = TimeSpan.FromSeconds(5); - public DeferredHostBuilder(string[] args, Assembly assembly) + public DeferredHostBuilder(string[] args, MethodInfo entryPoint) { _args = args; - _assembly = assembly; + _entryPoint = entryPoint; _configure = b => { // Copy the properties from this builder into the builder @@ -182,14 +182,14 @@ public IHost Build() { try { - var parameters = _assembly.EntryPoint!.GetParameters(); + var parameters = _entryPoint.GetParameters(); if (parameters.Length == 0) { - _assembly.EntryPoint!.Invoke(null, Array.Empty()); + _entryPoint.Invoke(null, Array.Empty()); } else { - _assembly.EntryPoint!.Invoke(null, new object[] { _args }); + _entryPoint.Invoke(null, new object[] { _args }); } // Try to set an exception if the entrypoint returns gracefully, this will force From e592f10248b5d1d05b500a204af0f9cca16b794c Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 6 Jun 2021 00:15:51 -0700 Subject: [PATCH 09/17] Added tests for top level statements --- .../tests/HostFactoryResolverTests.cs | 11 +++++++++++ ...soft.Extensions.HostFactoryResolver.Tests.csproj | 1 + .../tests/TopLevelStatements/Program.cs | 7 +++++++ .../TopLevelStatements/TopLevelStatements.csproj | 13 +++++++++++++ 4 files changed, 32 insertions(+) create mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/TopLevelStatements/Program.cs create mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/TopLevelStatements/TopLevelStatements.csproj diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs index 15bfe134cac57f..acf98834487569 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs @@ -4,6 +4,7 @@ using MockHostTypes; using System; using System.Diagnostics.CodeAnalysis; +using System.Reflection; using Xunit; namespace Microsoft.Extensions.Hosting.Tests @@ -178,5 +179,15 @@ public void NoSpecialEntryPointPatternMainNoArgs() Assert.NotNull(factory); Assert.IsAssignableFrom(factory(Array.Empty())); } + + [Fact] + public void TopLevelStatements() + { + var assembly = Assembly.Load("TopLevelStatements"); + var factory = HostFactoryResolver.ResolveServiceProviderFactory(assembly); + + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); + } } } diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj index d687786d08f7c1..2730011c5beccf 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj @@ -25,6 +25,7 @@ + diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/TopLevelStatements/Program.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/TopLevelStatements/Program.cs new file mode 100644 index 00000000000000..92f51e33ef543c --- /dev/null +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/TopLevelStatements/Program.cs @@ -0,0 +1,7 @@ +// 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; + +var host = new HostBuilder().Build(); \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/TopLevelStatements/TopLevelStatements.csproj b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/TopLevelStatements/TopLevelStatements.csproj new file mode 100644 index 00000000000000..716b25a87281da --- /dev/null +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/TopLevelStatements/TopLevelStatements.csproj @@ -0,0 +1,13 @@ + + + + $(NetCoreAppCurrent);net461 + true + Exe + + + + + + + From e56785aee058ae296e7cf6de40c8d6a31909c239 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 6 Jun 2021 08:34:41 -0700 Subject: [PATCH 10/17] Mark tests that require threading --- .../tests/HostFactoryResolverTests.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs index acf98834487569..6b4229dca90435 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs @@ -120,7 +120,7 @@ public void CreateHostBuilderPattern__Invalid_CantFindHostBuilder() Assert.Null(factory); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(CreateHostBuilderInvalidSignature.Program))] public void CreateHostBuilderPattern__Invalid_CantFindServiceProvider() { @@ -130,7 +130,7 @@ public void CreateHostBuilderPattern__Invalid_CantFindServiceProvider() Assert.Throws(() => factory(Array.Empty())); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPattern.Program))] public void NoSpecialEntryPointPattern() { @@ -140,7 +140,7 @@ public void NoSpecialEntryPointPattern() Assert.IsAssignableFrom(factory(Array.Empty())); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternThrows.Program))] public void NoSpecialEntryPointPatternThrows() { @@ -150,7 +150,7 @@ public void NoSpecialEntryPointPatternThrows() Assert.Throws(() => factory(Array.Empty())); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternExits.Program))] public void NoSpecialEntryPointPatternExits() { @@ -160,7 +160,7 @@ public void NoSpecialEntryPointPatternExits() Assert.Throws(() => factory(Array.Empty())); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternHangs.Program))] public void NoSpecialEntryPointPatternHangs() { @@ -170,7 +170,7 @@ public void NoSpecialEntryPointPatternHangs() Assert.Throws(() => factory(Array.Empty())); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternMainNoArgs.Program))] public void NoSpecialEntryPointPatternMainNoArgs() { @@ -180,7 +180,7 @@ public void NoSpecialEntryPointPatternMainNoArgs() Assert.IsAssignableFrom(factory(Array.Empty())); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void TopLevelStatements() { var assembly = Assembly.Load("TopLevelStatements"); From 19e412eead6e81fab4ba2dd76f25a48ecb55f659 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 6 Jun 2021 13:06:51 -0700 Subject: [PATCH 11/17] Run tests with RemoteExecutor since they rely on a global event - DiagnosticSources will publish events globally and we need to make sure there's no cross talk between tests --- .../tests/HostFactoryResolverTests.cs | 82 ++++++++++++------- ...xtensions.HostFactoryResolver.Tests.csproj | 1 + 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs index 6b4229dca90435..8f377b4a6761dc 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs @@ -5,12 +5,15 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using Microsoft.DotNet.RemoteExecutor; using Xunit; namespace Microsoft.Extensions.Hosting.Tests { public class HostFactoryResolverTests { + public static bool RequirementsMet => RemoteExecutor.IsSupported && PlatformDetection.IsThreadingSupported; + [Fact] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(BuildWebHostPatternTestSite.Program))] public void BuildWebHostPattern_CanFindWebHost() @@ -120,74 +123,95 @@ public void CreateHostBuilderPattern__Invalid_CantFindHostBuilder() Assert.Null(factory); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [ConditionalFact(nameof(RequirementsMet))] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(CreateHostBuilderInvalidSignature.Program))] public void CreateHostBuilderPattern__Invalid_CantFindServiceProvider() { - var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(CreateHostBuilderInvalidSignature.Program).Assembly); + RemoteExecutor.Invoke(() => + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(CreateHostBuilderInvalidSignature.Program).Assembly); - Assert.NotNull(factory); - Assert.Throws(() => factory(Array.Empty())); + Assert.NotNull(factory); + Assert.Throws(() => factory(Array.Empty())); + }); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [ConditionalFact(nameof(RequirementsMet))] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPattern.Program))] public void NoSpecialEntryPointPattern() { - var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPattern.Program).Assembly); + RemoteExecutor.Invoke(() => + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPattern.Program).Assembly); - Assert.NotNull(factory); - Assert.IsAssignableFrom(factory(Array.Empty())); + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); + }); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [ConditionalFact(nameof(RequirementsMet))] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternThrows.Program))] public void NoSpecialEntryPointPatternThrows() { - var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternThrows.Program).Assembly); + RemoteExecutor.Invoke(() => + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternThrows.Program).Assembly); - Assert.NotNull(factory); - Assert.Throws(() => factory(Array.Empty())); + Assert.NotNull(factory); + Assert.Throws(() => factory(Array.Empty())); + }); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [ConditionalFact(nameof(RequirementsMet))] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternExits.Program))] public void NoSpecialEntryPointPatternExits() { - var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternExits.Program).Assembly); + RemoteExecutor.Invoke(() => + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternExits.Program).Assembly); - Assert.NotNull(factory); - Assert.Throws(() => factory(Array.Empty())); + Assert.NotNull(factory); + Assert.Throws(() => factory(Array.Empty())); + }); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [ConditionalFact(nameof(RequirementsMet))] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternHangs.Program))] public void NoSpecialEntryPointPatternHangs() { - var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternHangs.Program).Assembly); + RemoteExecutor.Invoke(() => + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternHangs.Program).Assembly); - Assert.NotNull(factory); - Assert.Throws(() => factory(Array.Empty())); + Assert.NotNull(factory); + Assert.Throws(() => factory(Array.Empty())); + }); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [ConditionalFact(nameof(RequirementsMet))] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternMainNoArgs.Program))] public void NoSpecialEntryPointPatternMainNoArgs() { - var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternMainNoArgs.Program).Assembly); + RemoteExecutor.Invoke(() => + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternMainNoArgs.Program).Assembly); - Assert.NotNull(factory); - Assert.IsAssignableFrom(factory(Array.Empty())); + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); + }); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [ConditionalFact(nameof(RequirementsMet))] public void TopLevelStatements() { - var assembly = Assembly.Load("TopLevelStatements"); - var factory = HostFactoryResolver.ResolveServiceProviderFactory(assembly); + RemoteExecutor.Invoke(() => + { + var assembly = Assembly.Load("TopLevelStatements"); + var factory = HostFactoryResolver.ResolveServiceProviderFactory(assembly); - Assert.NotNull(factory); - Assert.IsAssignableFrom(factory(Array.Empty())); + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); + }); } } } diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj index 2730011c5beccf..d065e56ddfd095 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj @@ -1,6 +1,7 @@ $(NetCoreAppCurrent);net461 + true From 12ae4e99273821e6354ef635ef2882ee334b70f4 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 6 Jun 2021 17:11:10 -0700 Subject: [PATCH 12/17] Increase the timeout --- .../src/HostFactoryResolver.cs | 2 +- .../tests/HostFactoryResolverTests.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs index a6e26179767364..bcb170c2c9d57d 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs @@ -155,7 +155,7 @@ private class DeferredHostBuilder : IHostBuilder, IObserver, private Action _configure; // The amount of time we wait for the diagnostic source events to fire - private static readonly TimeSpan _waitTimeout = TimeSpan.FromSeconds(5); + private static readonly TimeSpan _waitTimeout = TimeSpan.FromSeconds(20); public DeferredHostBuilder(string[] args, MethodInfo entryPoint) { diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs index 8f377b4a6761dc..0e924777606da6 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs @@ -127,7 +127,7 @@ public void CreateHostBuilderPattern__Invalid_CantFindHostBuilder() [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(CreateHostBuilderInvalidSignature.Program))] public void CreateHostBuilderPattern__Invalid_CantFindServiceProvider() { - RemoteExecutor.Invoke(() => + using var _ = RemoteExecutor.Invoke(() => { var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(CreateHostBuilderInvalidSignature.Program).Assembly); @@ -140,7 +140,7 @@ public void CreateHostBuilderPattern__Invalid_CantFindServiceProvider() [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPattern.Program))] public void NoSpecialEntryPointPattern() { - RemoteExecutor.Invoke(() => + using var _ = RemoteExecutor.Invoke(() => { var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPattern.Program).Assembly); @@ -153,7 +153,7 @@ public void NoSpecialEntryPointPattern() [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternThrows.Program))] public void NoSpecialEntryPointPatternThrows() { - RemoteExecutor.Invoke(() => + using var _ = RemoteExecutor.Invoke(() => { var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternThrows.Program).Assembly); @@ -166,7 +166,7 @@ public void NoSpecialEntryPointPatternThrows() [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternExits.Program))] public void NoSpecialEntryPointPatternExits() { - RemoteExecutor.Invoke(() => + using var _ = RemoteExecutor.Invoke(() => { var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternExits.Program).Assembly); @@ -179,7 +179,7 @@ public void NoSpecialEntryPointPatternExits() [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternHangs.Program))] public void NoSpecialEntryPointPatternHangs() { - RemoteExecutor.Invoke(() => + using var _ = RemoteExecutor.Invoke(() => { var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternHangs.Program).Assembly); @@ -192,7 +192,7 @@ public void NoSpecialEntryPointPatternHangs() [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternMainNoArgs.Program))] public void NoSpecialEntryPointPatternMainNoArgs() { - RemoteExecutor.Invoke(() => + using var _ = RemoteExecutor.Invoke(() => { var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternMainNoArgs.Program).Assembly); @@ -204,7 +204,7 @@ public void NoSpecialEntryPointPatternMainNoArgs() [ConditionalFact(nameof(RequirementsMet))] public void TopLevelStatements() { - RemoteExecutor.Invoke(() => + using var _ = RemoteExecutor.Invoke(() => { var assembly = Assembly.Load("TopLevelStatements"); var factory = HostFactoryResolver.ResolveServiceProviderFactory(assembly); From ad5c27edbdb27f0258fe77a87c23a60384666533 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Mon, 7 Jun 2021 09:39:54 -0700 Subject: [PATCH 13/17] Added test for events --- .../tests/UnitTests/HostBuilderTests.cs | 59 +++++++++++++++++++ ...osoft.Extensions.Hosting.Unit.Tests.csproj | 1 + 2 files changed, 60 insertions(+) diff --git a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostBuilderTests.cs b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostBuilderTests.cs index a07be287a3b95d..1f35e1c15f4802 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostBuilderTests.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostBuilderTests.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Reflection; +using Microsoft.DotNet.RemoteExecutor; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; @@ -32,6 +34,37 @@ public void DefaultConfigIsMutable() } } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void BuildFiresEvents() + { + using var _ = RemoteExecutor.Invoke(() => + { + IHostBuilder hostBuilderFromEvent = null; + IHost hostFromEvent = null; + + var listener = new HostingListener((pair) => + { + if (pair.Key == "HostBuilding") + { + hostBuilderFromEvent = (IHostBuilder)pair.Value; + } + + if (pair.Key == "HostBuilt") + { + hostFromEvent = (IHost)pair.Value; + } + }); + + using var sub = DiagnosticListener.AllListeners.Subscribe(listener); + + var hostBuilder = new HostBuilder(); + var host = hostBuilder.Build(); + + Assert.Same(hostBuilder, hostBuilderFromEvent); + Assert.Same(host, hostFromEvent); + }); + } + [Fact] public void ConfigureHostConfigurationPropagated() { @@ -657,6 +690,32 @@ public void HostBuilderCanConfigureBackgroundServiceExceptionBehavior( options.Value.BackgroundServiceExceptionBehavior); } + private class HostingListener : IObserver, IObserver> + { + private IDisposable? _disposable; + private readonly Action> _callback; + + public HostingListener(Action> callback) + { + _callback = callback; + } + + public void OnCompleted() { _disposable?.Dispose(); } + public void OnError(Exception error) { } + public void OnNext(DiagnosticListener value) + { + if (value.Name == "Microsoft.Extensions.Hosting") + { + _disposable = value.Subscribe(this); + } + } + + public void OnNext(KeyValuePair value) + { + _callback(value); + } + } + private class FakeFileProvider : IFileProvider, IDisposable { public bool Disposed { get; private set; } diff --git a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Microsoft.Extensions.Hosting.Unit.Tests.csproj b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Microsoft.Extensions.Hosting.Unit.Tests.csproj index 8038a16a4d5afd..c5049661e96a16 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Microsoft.Extensions.Hosting.Unit.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Microsoft.Extensions.Hosting.Unit.Tests.csproj @@ -3,6 +3,7 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent);net461 true + true From 7b01d0b283020e0b55def175d83fe20fcea47d9f Mon Sep 17 00:00:00 2001 From: David Fowler Date: Mon, 7 Jun 2021 16:00:23 -0700 Subject: [PATCH 14/17] Remove dependency on hosting in shared file --- .../src/HostFactoryResolver.cs | 83 ++++--------------- 1 file changed, 17 insertions(+), 66 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs index bcb170c2c9d57d..7753606f30ee3b 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs @@ -37,14 +37,14 @@ internal sealed class HostFactoryResolver return ResolveFactory(assembly, CreateHostBuilder); } - public static Func? ResolveHostBuilderFactory(Assembly assembly) + public static Func? ResolveHostFactory(Assembly assembly, Action? configureHostBuilder = null) { if (assembly.EntryPoint is null) { return null; } - return args => new DeferredHostBuilder(args, assembly.EntryPoint); + return args => new HostingListener(args, assembly.EntryPoint, configureHostBuilder).CreateHost(); } private static Func? ResolveFactory(Assembly assembly, string name) @@ -109,14 +109,13 @@ private static bool IsFactory(MethodInfo? factory) }; } - var deferredFactory = ResolveHostBuilderFactory(assembly); - if (deferredFactory != null) + var hostFactory = ResolveHostFactory(assembly, builder => { }); + if (hostFactory != null) { return args => { - var hostBuilder = deferredFactory(args); - var host = hostBuilder.Build(); - return host.Services; + var host = hostFactory(args); + return GetServiceProvider(host); }; } @@ -140,39 +139,27 @@ private static bool IsFactory(MethodInfo? factory) return (IServiceProvider?)servicesProperty?.GetValue(host); } - // This host builder captures calls to the IHostBuilder then replays them on the application's - // IHostBuilder when the event fires - private class DeferredHostBuilder : IHostBuilder, IObserver, IObserver> + private class HostingListener : IObserver, IObserver> { - public IDictionary Properties { get; } = new Dictionary(); - private readonly string[] _args; private readonly MethodInfo _entryPoint; - private readonly TaskCompletionSource _hostTcs = new(); + private readonly TaskCompletionSource _hostTcs = new(); private IDisposable? _disposable; - private Action _configure; + private Action? _configure; // The amount of time we wait for the diagnostic source events to fire - private static readonly TimeSpan _waitTimeout = TimeSpan.FromSeconds(20); + private static readonly TimeSpan s_waitTimeout = TimeSpan.FromSeconds(20); - public DeferredHostBuilder(string[] args, MethodInfo entryPoint) + public HostingListener(string[] args, MethodInfo entryPoint, Action? configure) { _args = args; _entryPoint = entryPoint; - _configure = b => - { - // Copy the properties from this builder into the builder - // that we're going to receive - foreach (var pair in Properties) - { - b.Properties[pair.Key] = pair.Value; - } - }; + _configure = configure; } - public IHost Build() + public object CreateHost() { using var subscription = DiagnosticListener.AllListeners.Subscribe(this); @@ -192,7 +179,7 @@ public IHost Build() _entryPoint.Invoke(null, new object[] { _args }); } - // Try to set an exception if the entrypoint returns gracefully, this will force + // Try to set an exception if the entry point returns gracefully, this will force // build to throw _hostTcs.TrySetException(new InvalidOperationException("Unable to build IHost")); } @@ -222,14 +209,14 @@ public IHost Build() try { // Wait before throwing an exception - if (!_hostTcs.Task.Wait(_waitTimeout)) + if (!_hostTcs.Task.Wait(s_waitTimeout)) { throw new InvalidOperationException("Unable to build IHost"); } } catch (AggregateException) when (_hostTcs.Task.IsCompleted) { - // Lets this propogate out of the call to GetAwaiter().GetResult() + // Lets this propagate out of the call to GetAwaiter().GetResult() } Debug.Assert(_hostTcs.Task.IsCompleted); @@ -237,42 +224,6 @@ public IHost Build() return _hostTcs.Task.GetAwaiter().GetResult(); } - public IHostBuilder ConfigureAppConfiguration(Action configureDelegate) - { - _configure += b => b.ConfigureAppConfiguration(configureDelegate); - return this; - } - - public IHostBuilder ConfigureContainer(Action configureDelegate) - { - _configure += b => b.ConfigureContainer(configureDelegate); - return this; - } - - public IHostBuilder ConfigureHostConfiguration(Action configureDelegate) - { - _configure += b => b.ConfigureHostConfiguration(configureDelegate); - return this; - } - - public IHostBuilder ConfigureServices(Action configureDelegate) - { - _configure += b => b.ConfigureServices(configureDelegate); - return this; - } - - public IHostBuilder UseServiceProviderFactory(IServiceProviderFactory factory) where TContainerBuilder : notnull - { - _configure += b => b.UseServiceProviderFactory(factory); - return this; - } - - public IHostBuilder UseServiceProviderFactory(Func> factory) where TContainerBuilder : notnull - { - _configure += b => b.UseServiceProviderFactory(factory); - return this; - } - public void OnCompleted() { _disposable?.Dispose(); @@ -297,7 +248,7 @@ public void OnNext(KeyValuePair value) { if (value.Value is IHostBuilder builder) { - _configure(builder); + _configure?.Invoke(builder); } } From f8f2a69c4dc11d85a5cbb369074d6824d5c3e33d Mon Sep 17 00:00:00 2001 From: David Fowler Date: Mon, 7 Jun 2021 16:31:25 -0700 Subject: [PATCH 15/17] Actually remove hosting dependency --- .../src/HostFactoryResolver.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs index 7753606f30ee3b..7acc074990ec44 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs @@ -246,21 +246,15 @@ public void OnNext(KeyValuePair value) { if (value.Key == "HostBuilding") { - if (value.Value is IHostBuilder builder) - { - _configure?.Invoke(builder); - } + _configure?.Invoke(value.Value!); } if (value.Key == "HostBuilt") { - if (value.Value is IHost host) - { - _hostTcs.TrySetResult(host); + _hostTcs.TrySetResult(value.Value!); - // Stop the host from running further - throw new StopTheHostException(); - } + // Stop the host from running further + throw new StopTheHostException(); } } From 5625ba58bbc332ac8157ac72b30172bcd0d37639 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Mon, 7 Jun 2021 22:57:50 -0700 Subject: [PATCH 16/17] Detect the version of Microsoft.Extensions.Hosting before waiting for events to fire - We want to fail fast if we know this version of hosting will never fire events of if the hosting assembly fails to load. Added a version check. - Allow the caller to specify a wait timeout. --- .../src/HostFactoryResolver.cs | 40 ++++++++++++++----- .../tests/HostFactoryResolverTests.cs | 16 ++++---- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs index 7acc074990ec44..5837e34c120301 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs @@ -22,6 +22,9 @@ internal sealed class HostFactoryResolver public const string CreateWebHostBuilder = nameof(CreateWebHostBuilder); public const string CreateHostBuilder = nameof(CreateHostBuilder); + // The amount of time we wait for the diagnostic source events to fire + private static readonly TimeSpan s_defaultWaitTimeout = TimeSpan.FromSeconds(5); + public static Func? ResolveWebHostFactory(Assembly assembly) { return ResolveFactory(assembly, BuildWebHost); @@ -37,14 +40,33 @@ internal sealed class HostFactoryResolver return ResolveFactory(assembly, CreateHostBuilder); } - public static Func? ResolveHostFactory(Assembly assembly, Action? configureHostBuilder = null) + public static Func? ResolveHostFactory(Assembly assembly, TimeSpan? waitTimeout = null, Action? configureHostBuilder = null) { if (assembly.EntryPoint is null) { return null; } - return args => new HostingListener(args, assembly.EntryPoint, configureHostBuilder).CreateHost(); + 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) + var hostingAssembly = Assembly.Load("Microsoft.Extensions.Hosting"); + if (hostingAssembly.GetName().Version is Version version && version.Major < 6) + { + return null; + } + + // We're using a version >= 6 so the events can fire. If they don't fire + // then it's because the application isn't using the hosting APIs + } + catch + { + // There was an error loading the extensions assembly, return null. + return null; + } + + return args => new HostingListener(args, assembly.EntryPoint, waitTimeout ?? s_defaultWaitTimeout, configureHostBuilder).CreateHost(); } private static Func? ResolveFactory(Assembly assembly, string name) @@ -74,7 +96,7 @@ private static bool IsFactory(MethodInfo? factory) } // Used by EF tooling without any Hosting references. Looses some return type safety checks. - public static Func? ResolveServiceProviderFactory(Assembly assembly) + public static Func? ResolveServiceProviderFactory(Assembly assembly, TimeSpan? waitTimeout = null) { // Prefer the older patterns by default for back compat. var webHostFactory = ResolveWebHostFactory(assembly); @@ -109,7 +131,7 @@ private static bool IsFactory(MethodInfo? factory) }; } - var hostFactory = ResolveHostFactory(assembly, builder => { }); + var hostFactory = ResolveHostFactory(assembly, waitTimeout: waitTimeout); if (hostFactory != null) { return args => @@ -143,19 +165,17 @@ private class HostingListener : IObserver, IObserver _hostTcs = new(); private IDisposable? _disposable; - private Action? _configure; - // The amount of time we wait for the diagnostic source events to fire - private static readonly TimeSpan s_waitTimeout = TimeSpan.FromSeconds(20); - - public HostingListener(string[] args, MethodInfo entryPoint, Action? configure) + public HostingListener(string[] args, MethodInfo entryPoint, TimeSpan waitTimeout, Action? configure) { _args = args; _entryPoint = entryPoint; + _waitTimeout = waitTimeout; _configure = configure; } @@ -209,7 +229,7 @@ public object CreateHost() try { // Wait before throwing an exception - if (!_hostTcs.Task.Wait(s_waitTimeout)) + if (!_hostTcs.Task.Wait(_waitTimeout)) { throw new InvalidOperationException("Unable to build IHost"); } diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs index 0e924777606da6..f68836a77d0716 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs @@ -14,6 +14,8 @@ public class HostFactoryResolverTests { public static bool RequirementsMet => RemoteExecutor.IsSupported && PlatformDetection.IsThreadingSupported; + private static readonly TimeSpan s_WaitTimeout = TimeSpan.FromSeconds(20); + [Fact] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(BuildWebHostPatternTestSite.Program))] public void BuildWebHostPattern_CanFindWebHost() @@ -129,7 +131,7 @@ public void CreateHostBuilderPattern__Invalid_CantFindServiceProvider() { using var _ = RemoteExecutor.Invoke(() => { - var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(CreateHostBuilderInvalidSignature.Program).Assembly); + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(CreateHostBuilderInvalidSignature.Program).Assembly, s_WaitTimeout); Assert.NotNull(factory); Assert.Throws(() => factory(Array.Empty())); @@ -142,7 +144,7 @@ public void NoSpecialEntryPointPattern() { using var _ = RemoteExecutor.Invoke(() => { - var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPattern.Program).Assembly); + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPattern.Program).Assembly, s_WaitTimeout); Assert.NotNull(factory); Assert.IsAssignableFrom(factory(Array.Empty())); @@ -155,7 +157,7 @@ public void NoSpecialEntryPointPatternThrows() { using var _ = RemoteExecutor.Invoke(() => { - var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternThrows.Program).Assembly); + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternThrows.Program).Assembly, s_WaitTimeout); Assert.NotNull(factory); Assert.Throws(() => factory(Array.Empty())); @@ -168,7 +170,7 @@ public void NoSpecialEntryPointPatternExits() { using var _ = RemoteExecutor.Invoke(() => { - var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternExits.Program).Assembly); + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternExits.Program).Assembly, s_WaitTimeout); Assert.NotNull(factory); Assert.Throws(() => factory(Array.Empty())); @@ -181,7 +183,7 @@ public void NoSpecialEntryPointPatternHangs() { using var _ = RemoteExecutor.Invoke(() => { - var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternHangs.Program).Assembly); + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternHangs.Program).Assembly, s_WaitTimeout); Assert.NotNull(factory); Assert.Throws(() => factory(Array.Empty())); @@ -194,7 +196,7 @@ public void NoSpecialEntryPointPatternMainNoArgs() { using var _ = RemoteExecutor.Invoke(() => { - var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternMainNoArgs.Program).Assembly); + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternMainNoArgs.Program).Assembly, s_WaitTimeout); Assert.NotNull(factory); Assert.IsAssignableFrom(factory(Array.Empty())); @@ -207,7 +209,7 @@ public void TopLevelStatements() using var _ = RemoteExecutor.Invoke(() => { var assembly = Assembly.Load("TopLevelStatements"); - var factory = HostFactoryResolver.ResolveServiceProviderFactory(assembly); + var factory = HostFactoryResolver.ResolveServiceProviderFactory(assembly, s_WaitTimeout); Assert.NotNull(factory); Assert.IsAssignableFrom(factory(Array.Empty())); From 315575d2ad957adb22379ecc2d1be27c82f46f42 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Tue, 8 Jun 2021 02:31:59 -0700 Subject: [PATCH 17/17] Added a flag to allow running the application to completion --- .../src/HostFactoryResolver.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs index 5837e34c120301..16e1fe40dc8366 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs @@ -40,7 +40,7 @@ internal sealed class HostFactoryResolver return ResolveFactory(assembly, CreateHostBuilder); } - public static Func? ResolveHostFactory(Assembly assembly, TimeSpan? waitTimeout = null, Action? configureHostBuilder = null) + public static Func? ResolveHostFactory(Assembly assembly, TimeSpan? waitTimeout = null, bool stopApplication = true, Action? configureHostBuilder = null) { if (assembly.EntryPoint is null) { @@ -66,7 +66,7 @@ internal sealed class HostFactoryResolver return null; } - return args => new HostingListener(args, assembly.EntryPoint, waitTimeout ?? s_defaultWaitTimeout, configureHostBuilder).CreateHost(); + return args => new HostingListener(args, assembly.EntryPoint, waitTimeout ?? s_defaultWaitTimeout, stopApplication, configureHostBuilder).CreateHost(); } private static Func? ResolveFactory(Assembly assembly, string name) @@ -166,16 +166,18 @@ private class HostingListener : IObserver, IObserver _hostTcs = new(); private IDisposable? _disposable; private Action? _configure; - public HostingListener(string[] args, MethodInfo entryPoint, TimeSpan waitTimeout, Action? configure) + public HostingListener(string[] args, MethodInfo entryPoint, TimeSpan waitTimeout, bool stopApplication, Action? configure) { _args = args; _entryPoint = entryPoint; _waitTimeout = waitTimeout; + _stopApplication = stopApplication; _configure = configure; } @@ -273,8 +275,11 @@ public void OnNext(KeyValuePair value) { _hostTcs.TrySetResult(value.Value!); - // Stop the host from running further - throw new StopTheHostException(); + if (_stopApplication) + { + // Stop the host from running further + throw new StopTheHostException(); + } } }