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
{
private Action<object> _startCallback;
private Action<object> _stopCallback;
private object _startState;
private object _stopState;
private TaskCompletionSource<object> _delayStart = new TaskCompletionSource<object>();
public void RegisterDelayStartCallback(Action<object> callback, object state)
public ServiceBaseLifetime(IApplicationLifetime applicationLifetime)
{
_startCallback = callback ?? throw new ArgumentNullException(nameof(callback));
_startState = state;
Run(this);
ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
}
public void RegisterStopCallback(Action<object> callback, object state)
private IApplicationLifetime ApplicationLifetime { get; }
public Task WaitForStartAsync(CancellationToken cancellationToken)
{
_stopCallback = callback ?? throw new ArgumentNullException(nameof(callback));
_stopState = state;
cancellationToken.Register(() => _delayStart.TrySetCanceled());
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)
@ -48,15 +60,18 @@ namespace GenericHostSample
return Task.CompletedTask;
}
// Called by base.Run when the service is ready to start.
protected override void OnStart(string[] args)
{
_startCallback(_startState);
_delayStart.TrySetResult(null);
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()
{
_stopCallback(_stopState);
ApplicationLifetime.StopApplication();
base.OnStop();
}
}

View File

@ -1,7 +1,6 @@
// 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;
@ -10,20 +9,10 @@ namespace Microsoft.Extensions.Hosting
public interface IHostLifetime
{
/// <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.
/// </summary>
/// <param name="callback">A callback that will be invoked when the host should continue.</param>
/// <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);
Task WaitForStartAsync(CancellationToken cancellationToken);
/// <summary>
/// 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(_appConfiguration);
services.AddSingleton<IApplicationLifetime, ApplicationLifetime>();
services.AddSingleton<IHostLifetime, ProcessLifetime>();
services.AddSingleton<IHostLifetime, ConsoleLifetime>();
services.AddSingleton<IHost, Host>();
services.AddOptions();
services.AddLogging();

View File

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

View File

@ -36,13 +36,9 @@ namespace Microsoft.Extensions.Hosting.Internal
{
_logger.Starting();
var delayStart = new TaskCompletionSource<object>();
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;
await _hostLifetime.WaitForStartAsync(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
_hostedServices = Services.GetService<IEnumerable<IHostedService>>();
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 int StartCount { get; internal set; }
public int StoppingCount { get; internal set; }
public int StopCount { get; internal set; }
public Action<Action<object>, object> StartAction { get; set; }
public Action<Action<object>, object> StoppingAction { get; set; }
public Action<CancellationToken> StartAction { get; set; }
public Action StopAction { get; set; }
public void RegisterDelayStartCallback(Action<object> callback, object state)
public Task WaitForStartAsync(CancellationToken cancellationToken)
{
StartCount++;
StartAction?.Invoke(callback, state);
}
public void RegisterStopCallback(Action<object> callback, object state)
{
StoppingCount++;
StoppingAction?.Invoke(callback, state);
StartAction?.Invoke(cancellationToken);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)

View File

@ -233,11 +233,10 @@ namespace Microsoft.Extensions.Hosting
});
services.AddSingleton<IHostLifetime>(_ => new FakeHostLifetime()
{
StartAction = (callback, state) =>
StartAction = ct =>
{
lifetimeStart.Set();
Assert.True(lifetimeContinue.WaitOne(TimeSpan.FromSeconds(5)));
callback(state);
}
});
})
@ -259,7 +258,6 @@ namespace Microsoft.Extensions.Hosting
lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>();
Assert.Equal(1, lifetime.StartCount);
Assert.Equal(1, lifetime.StoppingCount);
Assert.Equal(0, lifetime.StopCount);
}
@ -268,7 +266,6 @@ namespace Microsoft.Extensions.Hosting
Assert.Equal(1, service.DisposeCount);
Assert.Equal(1, lifetime.StartCount);
Assert.Equal(1, lifetime.StoppingCount);
Assert.Equal(0, lifetime.StopCount);
}
@ -292,9 +289,10 @@ namespace Microsoft.Extensions.Hosting
});
services.AddSingleton<IHostLifetime>(_ => new FakeHostLifetime()
{
StartAction = (callback, state) =>
StartAction = ct =>
{
lifetimeStart.Set();
WaitHandle.WaitAny(new[] { lifetimeContinue, ct.WaitHandle });
}
});
})
@ -308,7 +306,7 @@ namespace Microsoft.Extensions.Hosting
Assert.False(serviceStarting.WaitOne(0));
cts.Cancel();
await Assert.ThrowsAsync<TaskCanceledException>(() => startTask);
await Assert.ThrowsAsync<OperationCanceledException>(() => startTask);
Assert.False(serviceStarting.WaitOne(0));
lifetimeContinue.Set();
@ -322,7 +320,6 @@ namespace Microsoft.Extensions.Hosting
lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>();
Assert.Equal(1, lifetime.StartCount);
Assert.Equal(1, lifetime.StoppingCount);
Assert.Equal(0, lifetime.StopCount);
}
@ -331,61 +328,6 @@ namespace Microsoft.Extensions.Hosting
Assert.Equal(1, service.DisposeCount);
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);
}
@ -398,10 +340,7 @@ namespace Microsoft.Extensions.Hosting
.ConfigureServices((services) =>
{
services.AddSingleton<IHostedService, FakeHostedService>();
services.AddSingleton<IHostLifetime>(_ => new FakeHostLifetime()
{
StartAction = (callback, state) => callback(state),
});
services.AddSingleton<IHostLifetime, FakeHostLifetime>();
})
.Build())
{
@ -414,7 +353,6 @@ namespace Microsoft.Extensions.Hosting
lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>();
Assert.Equal(1, lifetime.StartCount);
Assert.Equal(1, lifetime.StoppingCount);
Assert.Equal(0, lifetime.StopCount);
await host.StopAsync();
@ -424,7 +362,6 @@ namespace Microsoft.Extensions.Hosting
Assert.Equal(0, service.DisposeCount);
Assert.Equal(1, lifetime.StartCount);
Assert.Equal(1, lifetime.StoppingCount);
Assert.Equal(1, lifetime.StopCount);
}
@ -433,7 +370,6 @@ namespace Microsoft.Extensions.Hosting
Assert.Equal(1, service.DisposeCount);
Assert.Equal(1, lifetime.StartCount);
Assert.Equal(1, lifetime.StoppingCount);
Assert.Equal(1, lifetime.StopCount);
}