Merge branch 'release/2.1' into dev
This commit is contained in:
commit
edada4acc7
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue