Scope events to the execution of the entry point (#54090)
authorDavid Fowler <davidfowl@gmail.com>
Sat, 12 Jun 2021 06:09:46 +0000 (23:09 -0700)
committerGitHub <noreply@github.com>
Sat, 12 Jun 2021 06:09:46 +0000 (23:09 -0700)
* Scope events to the execution of the entry point
- Today we're using the global event source and events that fire in the app domain get captured and this can result in capturing the wrong instances. This fix uses an async local to scope the events for the HostingEventListener to the execution of the application's entry point.
- Removed the RemoteExecutor as a result of this change
- Remove RequirementsMet property

src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs
src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs
src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj

index 6992fd0..6dc54c7 100644 (file)
@@ -180,6 +180,7 @@ namespace Microsoft.Extensions.Hosting
             private IDisposable? _disposable;
             private Action<object>? _configure;
             private Action<Exception?>? _entrypointCompleted;
+            private static readonly AsyncLocal<HostingListener> _currentListener = new();
 
             public HostingListener(string[] args, MethodInfo entryPoint, TimeSpan waitTimeout, bool stopApplication, Action<object>? configure, Action<Exception?>? entrypointCompleted)
             {
@@ -193,6 +194,10 @@ namespace Microsoft.Extensions.Hosting
 
             public object CreateHost()
             {
+                // Set the async local to the instance of the HostingListener so we can filter events that
+                // aren't scoped to this execution of the entry point.
+                _currentListener.Value = this;
+
                 using var subscription = DiagnosticListener.AllListeners.Subscribe(this);
 
                 // Kick off the entry point on a new thread so we don't block the current one
@@ -279,6 +284,12 @@ namespace Microsoft.Extensions.Hosting
 
             public void OnNext(DiagnosticListener value)
             {
+                if (_currentListener.Value != this)
+                {
+                    // Ignore events that aren't for this listener
+                    return;
+                }
+
                 if (value.Name == "Microsoft.Extensions.Hosting")
                 {
                     _disposable = value.Subscribe(this);
@@ -287,6 +298,12 @@ namespace Microsoft.Extensions.Hosting
 
             public void OnNext(KeyValuePair<string, object?> value)
             {
+                if (_currentListener.Value != this)
+                {
+                    // Ignore events that aren't for this listener
+                    return;
+                }
+
                 if (value.Key == "HostBuilding")
                 {
                     _configure?.Invoke(value.Value!);
index 6575392..567b73e 100644 (file)
@@ -6,15 +6,12 @@ using System;
 using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
 using System.Threading;
-using Microsoft.DotNet.RemoteExecutor;
 using Xunit;
 
 namespace Microsoft.Extensions.Hosting.Tests
 {
     public class HostFactoryResolverTests
     {
-        public static bool RequirementsMet => RemoteExecutor.IsSupported && PlatformDetection.IsThreadingSupported;
-
         private static readonly TimeSpan s_WaitTimeout = TimeSpan.FromSeconds(20);
 
         [Fact]
@@ -126,162 +123,132 @@ namespace Microsoft.Extensions.Hosting.Tests
             Assert.Null(factory);
         }
 
-        [ConditionalFact(nameof(RequirementsMet))]
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
         [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(CreateHostBuilderInvalidSignature.Program))]
         public void CreateHostBuilderPattern__Invalid_CantFindServiceProvider()
         {
-            using var _ = RemoteExecutor.Invoke(() => 
-            {
-                var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(CreateHostBuilderInvalidSignature.Program).Assembly, s_WaitTimeout);
+            var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(CreateHostBuilderInvalidSignature.Program).Assembly, s_WaitTimeout);
 
-                Assert.NotNull(factory);
-                Assert.Throws<InvalidOperationException>(() => factory(Array.Empty<string>()));
-            });
+            Assert.NotNull(factory);
+            Assert.Throws<InvalidOperationException>(() => factory(Array.Empty<string>()));
         }
 
-        [ConditionalFact(nameof(RequirementsMet))]
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
         [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPattern.Program))]
         public void NoSpecialEntryPointPattern()
         {
-            using var _ = RemoteExecutor.Invoke(() => 
-            {
-                var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPattern.Program).Assembly, s_WaitTimeout);
+            var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPattern.Program).Assembly, s_WaitTimeout);
 
-                Assert.NotNull(factory);
-                Assert.IsAssignableFrom<IServiceProvider>(factory(Array.Empty<string>()));
-            });
+            Assert.NotNull(factory);
+            Assert.IsAssignableFrom<IServiceProvider>(factory(Array.Empty<string>()));
         }
 
-        [ConditionalFact(nameof(RequirementsMet))]
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
         [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPattern.Program))]
         public void NoSpecialEntryPointPatternHostBuilderConfigureHostBuilderCallbackIsCalled()
         {
-            using var _ = RemoteExecutor.Invoke(() => 
+            bool called = false;
+            void ConfigureHostBuilder(object hostBuilder)
             {
-                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);
-            });
+                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))]
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
         [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)
             {
-                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);
-            });
+                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))]
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
         [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)
             {
-                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);
-            });
+                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))]
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
         [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternThrows.Program))]
         public void NoSpecialEntryPointPatternThrows()
         {
-            using var _ = RemoteExecutor.Invoke(() => 
-            {
-                var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternThrows.Program).Assembly, s_WaitTimeout);
+            var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternThrows.Program).Assembly, s_WaitTimeout);
 
-                Assert.NotNull(factory);
-                Assert.Throws<Exception>(() => factory(Array.Empty<string>()));
-            });
+            Assert.NotNull(factory);
+            Assert.Throws<Exception>(() => factory(Array.Empty<string>()));
         }
 
-        [ConditionalFact(nameof(RequirementsMet))]
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
         [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternExits.Program))]
         public void NoSpecialEntryPointPatternExits()
         {
-            using var _ = RemoteExecutor.Invoke(() => 
-            {
-                var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternExits.Program).Assembly, s_WaitTimeout);
+            var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternExits.Program).Assembly, s_WaitTimeout);
 
-                Assert.NotNull(factory);
-                Assert.Throws<InvalidOperationException>(() => factory(Array.Empty<string>()));
-            });
+            Assert.NotNull(factory);
+            Assert.Throws<InvalidOperationException>(() => factory(Array.Empty<string>()));
         }
 
-        [ConditionalFact(nameof(RequirementsMet))]
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
         [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternHangs.Program))]
         public void NoSpecialEntryPointPatternHangs()
         {
-            using var _ = RemoteExecutor.Invoke(() => 
-            {
-                var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternHangs.Program).Assembly, s_WaitTimeout);
+            var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternHangs.Program).Assembly, s_WaitTimeout);
 
-                Assert.NotNull(factory);
-                Assert.Throws<InvalidOperationException>(() => factory(Array.Empty<string>()));
-            });
+            Assert.NotNull(factory);
+            Assert.Throws<InvalidOperationException>(() => factory(Array.Empty<string>()));
         }
 
-        [ConditionalFact(nameof(RequirementsMet))]
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
         [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPatternMainNoArgs.Program))]
         public void NoSpecialEntryPointPatternMainNoArgs()
         {
-            using var _ = RemoteExecutor.Invoke(() => 
-            {
-                var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternMainNoArgs.Program).Assembly, s_WaitTimeout);
+            var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(NoSpecialEntryPointPatternMainNoArgs.Program).Assembly, s_WaitTimeout);
 
-                Assert.NotNull(factory);
-                Assert.IsAssignableFrom<IServiceProvider>(factory(Array.Empty<string>()));
-            });
+            Assert.NotNull(factory);
+            Assert.IsAssignableFrom<IServiceProvider>(factory(Array.Empty<string>()));
         }
 
-        [ConditionalFact(nameof(RequirementsMet))]
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
         public void TopLevelStatements()
         {
-            using var _ = RemoteExecutor.Invoke(() => 
-            {
-                var assembly = Assembly.Load("TopLevelStatements");
-                var factory = HostFactoryResolver.ResolveServiceProviderFactory(assembly, s_WaitTimeout);
+            var assembly = Assembly.Load("TopLevelStatements");
+            var factory = HostFactoryResolver.ResolveServiceProviderFactory(assembly, s_WaitTimeout);
 
-                Assert.NotNull(factory);
-                Assert.IsAssignableFrom<IServiceProvider>(factory(Array.Empty<string>()));
-            });
+            Assert.NotNull(factory);
+            Assert.IsAssignableFrom<IServiceProvider>(factory(Array.Empty<string>()));
         }
     }
 }