Skip to content

Commit

Permalink
Scope events to the execution of the entry point (#54090)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
davidfowl committed Jun 12, 2021
1 parent 23336b8 commit 2a011f8
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ private class HostingListener : IObserver<DiagnosticListener>, IObserver<KeyValu
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)
{
Expand All @@ -193,6 +194,10 @@ public HostingListener(string[] args, MethodInfo entryPoint, TimeSpan waitTimeou

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
Expand Down Expand Up @@ -279,6 +284,12 @@ public void OnError(Exception error)

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);
Expand All @@ -287,6 +298,12 @@ public void OnNext(DiagnosticListener value)

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!);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,12 @@
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]
Expand Down Expand Up @@ -126,162 +123,132 @@ public void CreateHostBuilderPattern__Invalid_CantFindHostBuilder()
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>()));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent);net461</TargetFrameworks>
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
</PropertyGroup>

<ItemGroup>
Expand Down

0 comments on commit 2a011f8

Please sign in to comment.