Merge branch 'release/2.1' into dev

This commit is contained in:
Chris Ross (ASP.NET) 2018-03-23 12:06:51 -07:00
commit edada4acc7
8 changed files with 49 additions and 151 deletions

View File

@ -23,23 +23,35 @@ namespace GenericHostSample
public class ServiceBaseLifetime : ServiceBase, IHostLifetime public class ServiceBaseLifetime : ServiceBase, IHostLifetime
{ {
private Action<object> _startCallback; private TaskCompletionSource<object> _delayStart = new TaskCompletionSource<object>();
private Action<object> _stopCallback;
private object _startState;
private object _stopState;
public void RegisterDelayStartCallback(Action<object> callback, object state) public ServiceBaseLifetime(IApplicationLifetime applicationLifetime)
{ {
_startCallback = callback ?? throw new ArgumentNullException(nameof(callback)); ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
_startState = state;
Run(this);
} }
public void RegisterStopCallback(Action<object> callback, object state) private IApplicationLifetime ApplicationLifetime { get; }
public Task WaitForStartAsync(CancellationToken cancellationToken)
{ {
_stopCallback = callback ?? throw new ArgumentNullException(nameof(callback)); cancellationToken.Register(() => _delayStart.TrySetCanceled());
_stopState = state; ApplicationLifetime.ApplicationStopping.Register(Stop);
new Thread(Run).Start(); // Otherwise this would block and prevent IHost.StartAsync from finishing.
return _delayStart.Task;
}
private void Run()
{
try
{
Run(this); // This blocks until the service is stopped.
_delayStart.TrySetException(new InvalidOperationException("Stopped without starting"));
}
catch (Exception ex)
{
_delayStart.TrySetException(ex);
}
} }
public Task StopAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken)
@ -48,15 +60,18 @@ namespace GenericHostSample
return Task.CompletedTask; return Task.CompletedTask;
} }
// Called by base.Run when the service is ready to start.
protected override void OnStart(string[] args) protected override void OnStart(string[] args)
{ {
_startCallback(_startState); _delayStart.TrySetResult(null);
base.OnStart(args); base.OnStart(args);
} }
// Called by base.Stop. This may be called multiple times by service Stop, ApplicationStopping, and StopAsync.
// That's OK because StopApplication uses a CancellationTokenSource and prevents any recursion.
protected override void OnStop() protected override void OnStop()
{ {
_stopCallback(_stopState); ApplicationLifetime.StopApplication();
base.OnStop(); base.OnStop();
} }
} }

View File

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -10,20 +9,10 @@ namespace Microsoft.Extensions.Hosting
public interface IHostLifetime public interface IHostLifetime
{ {
/// <summary> /// <summary>
/// Called at the start of <see cref="IHost.StartAsync(CancellationToken)"/> which will wait until the callback is invoked before /// Called at the start of <see cref="IHost.StartAsync(CancellationToken)"/> which will wait until it's compete before
/// continuing. This can be used to delay startup until signaled by an external event. /// continuing. This can be used to delay startup until signaled by an external event.
/// </summary> /// </summary>
/// <param name="callback">A callback that will be invoked when the host should continue.</param> Task WaitForStartAsync(CancellationToken cancellationToken);
/// <param name="state">State to pass to the callback.</param>
void RegisterDelayStartCallback(Action<object> callback, object state);
/// <summary>
/// Called at the start of <see cref="IHost.StartAsync(CancellationToken)"/> to register the given callback for initiating the
/// application shutdown process.
/// </summary>
/// <param name="callback">A callback to invoke when an external signal indicates the application should stop.</param>
/// <param name="state">State to pass to the callback.</param>
void RegisterStopCallback(Action<object> callback, object state);
/// <summary> /// <summary>
/// Called from <see cref="IHost.StopAsync(CancellationToken)"/> to indicate that the host as stopped and clean up resources. /// Called from <see cref="IHost.StopAsync(CancellationToken)"/> to indicate that the host as stopped and clean up resources.

View File

@ -178,7 +178,7 @@ namespace Microsoft.Extensions.Hosting
services.AddSingleton(_hostBuilderContext); services.AddSingleton(_hostBuilderContext);
services.AddSingleton(_appConfiguration); services.AddSingleton(_appConfiguration);
services.AddSingleton<IApplicationLifetime, ApplicationLifetime>(); services.AddSingleton<IApplicationLifetime, ApplicationLifetime>();
services.AddSingleton<IHostLifetime, ProcessLifetime>(); services.AddSingleton<IHostLifetime, ConsoleLifetime>();
services.AddSingleton<IHost, Host>(); services.AddSingleton<IHost, Host>();
services.AddOptions(); services.AddOptions();
services.AddLogging(); services.AddLogging();

View File

@ -26,7 +26,7 @@ namespace Microsoft.Extensions.Hosting.Internal
private IApplicationLifetime ApplicationLifetime { get; } private IApplicationLifetime ApplicationLifetime { get; }
public void RegisterDelayStartCallback(Action<object> callback, object state) public Task WaitForStartAsync(CancellationToken cancellationToken)
{ {
if (!Options.SuppressStatusMessages) if (!Options.SuppressStatusMessages)
{ {
@ -38,18 +38,15 @@ namespace Microsoft.Extensions.Hosting.Internal
}); });
} }
// Console applications start immediately. AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) => ApplicationLifetime.StopApplication();
callback(state);
}
public void RegisterStopCallback(Action<object> callback, object state)
{
AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) => callback(state);
Console.CancelKeyPress += (sender, e) => Console.CancelKeyPress += (sender, e) =>
{ {
e.Cancel = true; e.Cancel = true;
callback(state); ApplicationLifetime.StopApplication();
}; };
// Console applications start immediately.
return Task.CompletedTask;
} }
public Task StopAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken)

View File

@ -36,13 +36,9 @@ namespace Microsoft.Extensions.Hosting.Internal
{ {
_logger.Starting(); _logger.Starting();
var delayStart = new TaskCompletionSource<object>(); await _hostLifetime.WaitForStartAsync(cancellationToken);
cancellationToken.Register(obj => ((TaskCompletionSource<object>)obj).TrySetCanceled(), delayStart);
_hostLifetime.RegisterDelayStartCallback(obj => ((TaskCompletionSource<object>)obj).TrySetResult(null), delayStart);
_hostLifetime.RegisterStopCallback(obj => (obj as IApplicationLifetime)?.StopApplication(), _applicationLifetime);
await delayStart.Task;
cancellationToken.ThrowIfCancellationRequested();
_hostedServices = Services.GetService<IEnumerable<IHostedService>>(); _hostedServices = Services.GetService<IEnumerable<IHostedService>>();
foreach (var hostedService in _hostedServices) foreach (var hostedService in _hostedServices)

View File

@ -1,28 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.Hosting.Internal
{
public class ProcessLifetime : IHostLifetime
{
public void RegisterDelayStartCallback(Action<object> callback, object state)
{
// Never delays start.
callback(state);
}
public void RegisterStopCallback(Action<object> callback, object state)
{
AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) => callback(state);
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}

View File

@ -10,23 +10,16 @@ namespace Microsoft.Extensions.Hosting.Tests.Fakes
public class FakeHostLifetime : IHostLifetime public class FakeHostLifetime : IHostLifetime
{ {
public int StartCount { get; internal set; } public int StartCount { get; internal set; }
public int StoppingCount { get; internal set; }
public int StopCount { get; internal set; } public int StopCount { get; internal set; }
public Action<Action<object>, object> StartAction { get; set; } public Action<CancellationToken> StartAction { get; set; }
public Action<Action<object>, object> StoppingAction { get; set; }
public Action StopAction { get; set; } public Action StopAction { get; set; }
public void RegisterDelayStartCallback(Action<object> callback, object state) public Task WaitForStartAsync(CancellationToken cancellationToken)
{ {
StartCount++; StartCount++;
StartAction?.Invoke(callback, state); StartAction?.Invoke(cancellationToken);
} return Task.CompletedTask;
public void RegisterStopCallback(Action<object> callback, object state)
{
StoppingCount++;
StoppingAction?.Invoke(callback, state);
} }
public Task StopAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken)

View File

@ -233,11 +233,10 @@ namespace Microsoft.Extensions.Hosting
}); });
services.AddSingleton<IHostLifetime>(_ => new FakeHostLifetime() services.AddSingleton<IHostLifetime>(_ => new FakeHostLifetime()
{ {
StartAction = (callback, state) => StartAction = ct =>
{ {
lifetimeStart.Set(); lifetimeStart.Set();
Assert.True(lifetimeContinue.WaitOne(TimeSpan.FromSeconds(5))); Assert.True(lifetimeContinue.WaitOne(TimeSpan.FromSeconds(5)));
callback(state);
} }
}); });
}) })
@ -259,7 +258,6 @@ namespace Microsoft.Extensions.Hosting
lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>(); lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>();
Assert.Equal(1, lifetime.StartCount); Assert.Equal(1, lifetime.StartCount);
Assert.Equal(1, lifetime.StoppingCount);
Assert.Equal(0, lifetime.StopCount); Assert.Equal(0, lifetime.StopCount);
} }
@ -268,7 +266,6 @@ namespace Microsoft.Extensions.Hosting
Assert.Equal(1, service.DisposeCount); Assert.Equal(1, service.DisposeCount);
Assert.Equal(1, lifetime.StartCount); Assert.Equal(1, lifetime.StartCount);
Assert.Equal(1, lifetime.StoppingCount);
Assert.Equal(0, lifetime.StopCount); Assert.Equal(0, lifetime.StopCount);
} }
@ -292,9 +289,10 @@ namespace Microsoft.Extensions.Hosting
}); });
services.AddSingleton<IHostLifetime>(_ => new FakeHostLifetime() services.AddSingleton<IHostLifetime>(_ => new FakeHostLifetime()
{ {
StartAction = (callback, state) => StartAction = ct =>
{ {
lifetimeStart.Set(); lifetimeStart.Set();
WaitHandle.WaitAny(new[] { lifetimeContinue, ct.WaitHandle });
} }
}); });
}) })
@ -308,7 +306,7 @@ namespace Microsoft.Extensions.Hosting
Assert.False(serviceStarting.WaitOne(0)); Assert.False(serviceStarting.WaitOne(0));
cts.Cancel(); cts.Cancel();
await Assert.ThrowsAsync<TaskCanceledException>(() => startTask); await Assert.ThrowsAsync<OperationCanceledException>(() => startTask);
Assert.False(serviceStarting.WaitOne(0)); Assert.False(serviceStarting.WaitOne(0));
lifetimeContinue.Set(); lifetimeContinue.Set();
@ -322,7 +320,6 @@ namespace Microsoft.Extensions.Hosting
lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>(); lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>();
Assert.Equal(1, lifetime.StartCount); Assert.Equal(1, lifetime.StartCount);
Assert.Equal(1, lifetime.StoppingCount);
Assert.Equal(0, lifetime.StopCount); Assert.Equal(0, lifetime.StopCount);
} }
@ -331,61 +328,6 @@ namespace Microsoft.Extensions.Hosting
Assert.Equal(1, service.DisposeCount); Assert.Equal(1, service.DisposeCount);
Assert.Equal(1, lifetime.StartCount); Assert.Equal(1, lifetime.StartCount);
Assert.Equal(1, lifetime.StoppingCount);
Assert.Equal(0, lifetime.StopCount);
}
[Fact]
public async Task HostLifetimeOnStoppingTriggersIApplicationLifetime()
{
var lifetimeRegistered = new ManualResetEvent(false);
Action<object> stoppingAction = null;
object stoppingState = null;
FakeHostedService service;
FakeHostLifetime lifetime;
using (var host = CreateBuilder()
.ConfigureServices((services) =>
{
services.AddSingleton<IHostedService, FakeHostedService>();
services.AddSingleton<IHostLifetime>(_ => new FakeHostLifetime()
{
StartAction = (callback, state) => callback(state),
StoppingAction = (callback, state) =>
{
stoppingAction = callback;
stoppingState = state;
lifetimeRegistered.Set();
}
});
})
.Build())
{
await host.StartAsync();
Assert.True(lifetimeRegistered.WaitOne(0));
var appLifetime = host.Services.GetRequiredService<IApplicationLifetime>();
stoppingAction(stoppingState);
Assert.True(appLifetime.ApplicationStopping.WaitHandle.WaitOne(TimeSpan.FromSeconds(5)));
service = (FakeHostedService)host.Services.GetRequiredService<IHostedService>();
Assert.Equal(1, service.StartCount);
Assert.Equal(0, service.StopCount);
Assert.Equal(0, service.DisposeCount);
lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>();
Assert.Equal(1, lifetime.StartCount);
Assert.Equal(1, lifetime.StoppingCount);
Assert.Equal(0, lifetime.StopCount);
}
Assert.Equal(1, service.StartCount);
Assert.Equal(0, service.StopCount);
Assert.Equal(1, service.DisposeCount);
Assert.Equal(1, lifetime.StartCount);
Assert.Equal(1, lifetime.StoppingCount);
Assert.Equal(0, lifetime.StopCount); Assert.Equal(0, lifetime.StopCount);
} }
@ -398,10 +340,7 @@ namespace Microsoft.Extensions.Hosting
.ConfigureServices((services) => .ConfigureServices((services) =>
{ {
services.AddSingleton<IHostedService, FakeHostedService>(); services.AddSingleton<IHostedService, FakeHostedService>();
services.AddSingleton<IHostLifetime>(_ => new FakeHostLifetime() services.AddSingleton<IHostLifetime, FakeHostLifetime>();
{
StartAction = (callback, state) => callback(state),
});
}) })
.Build()) .Build())
{ {
@ -414,7 +353,6 @@ namespace Microsoft.Extensions.Hosting
lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>(); lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>();
Assert.Equal(1, lifetime.StartCount); Assert.Equal(1, lifetime.StartCount);
Assert.Equal(1, lifetime.StoppingCount);
Assert.Equal(0, lifetime.StopCount); Assert.Equal(0, lifetime.StopCount);
await host.StopAsync(); await host.StopAsync();
@ -424,7 +362,6 @@ namespace Microsoft.Extensions.Hosting
Assert.Equal(0, service.DisposeCount); Assert.Equal(0, service.DisposeCount);
Assert.Equal(1, lifetime.StartCount); Assert.Equal(1, lifetime.StartCount);
Assert.Equal(1, lifetime.StoppingCount);
Assert.Equal(1, lifetime.StopCount); Assert.Equal(1, lifetime.StopCount);
} }
@ -433,7 +370,6 @@ namespace Microsoft.Extensions.Hosting
Assert.Equal(1, service.DisposeCount); Assert.Equal(1, service.DisposeCount);
Assert.Equal(1, lifetime.StartCount); Assert.Equal(1, lifetime.StartCount);
Assert.Equal(1, lifetime.StoppingCount);
Assert.Equal(1, lifetime.StopCount); Assert.Equal(1, lifetime.StopCount);
} }