diff --git a/src/Microsoft.AspNet.Hosting/ApplicationLifetime.cs b/src/Microsoft.AspNet.Hosting/ApplicationLifetime.cs new file mode 100644 index 0000000000..d1db40def4 --- /dev/null +++ b/src/Microsoft.AspNet.Hosting/ApplicationLifetime.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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; + +namespace Microsoft.AspNet.Hosting +{ + /// + /// Allows consumers to perform cleanup during a graceful shutdown. + /// + public class ApplicationLifetime : IApplicationLifetime + { + private readonly CancellationTokenSource _stoppingSource = new CancellationTokenSource(); + private readonly CancellationTokenSource _stoppedSource = new CancellationTokenSource(); + + public ApplicationLifetime() + { + } + + /// + /// Triggered when the application host is performing a graceful shutdown. + /// Request may still be in flight. Shutdown will block until this event completes. + /// + /// + public CancellationToken ApplicationStopping + { + get { return _stoppingSource.Token; } + } + + /// + /// Triggered when the application host is performing a graceful shutdown. + /// All requests should be complete at this point. Shutdown will block + /// until this event completes. + /// + /// + public CancellationToken ApplicationStopped + { + get { return _stoppedSource.Token; } + } + + /// + /// Signals the ApplicationStopping event and blocks until it completes. + /// + public void SignalStopping() + { + try + { + _stoppingSource.Cancel(throwOnFirstException: false); + } + catch (Exception) + { + // TODO: LOG + } + } + + /// + /// Signals the ApplicationStopped event and blocks until it completes. + /// + public void SignalStopped() + { + try + { + _stoppedSource.Cancel(throwOnFirstException: false); + } + catch (Exception) + { + // TODO: LOG + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Hosting/HostingContext.cs b/src/Microsoft.AspNet.Hosting/HostingContext.cs index ea5685e6bb..7ad428b722 100644 --- a/src/Microsoft.AspNet.Hosting/HostingContext.cs +++ b/src/Microsoft.AspNet.Hosting/HostingContext.cs @@ -27,6 +27,7 @@ namespace Microsoft.AspNet.Hosting { public IServiceProvider Services { get; set; } public IConfiguration Configuration { get; set; } + public ApplicationLifetime Lifetime { get; set; } public IBuilder Builder { get; set; } diff --git a/src/Microsoft.AspNet.Hosting/HostingEngine.cs b/src/Microsoft.AspNet.Hosting/HostingEngine.cs index b8cecf5752..7458f5713b 100644 --- a/src/Microsoft.AspNet.Hosting/HostingEngine.cs +++ b/src/Microsoft.AspNet.Hosting/HostingEngine.cs @@ -21,6 +21,7 @@ using Microsoft.AspNet.Hosting.Builder; using Microsoft.AspNet.Hosting.Server; using Microsoft.AspNet.Hosting.Startup; using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.DependencyInjection.Fallback; namespace Microsoft.AspNet.Hosting { @@ -45,6 +46,7 @@ namespace Microsoft.AspNet.Hosting public IDisposable Start(HostingContext context) { + EnsureLifetime(context); EnsureBuilder(context); EnsureServerFactory(context); InitalizeServerFactory(context); @@ -55,11 +57,24 @@ namespace Microsoft.AspNet.Hosting return new Disposable(() => { + context.Lifetime.SignalStopping(); server.Dispose(); + context.Lifetime.SignalStopped(); pipeline.Dispose(); }); } + private void EnsureLifetime(HostingContext context) + { + if (context.Lifetime == null) + { + context.Lifetime = new ApplicationLifetime(); + } + var serviceCollection = new ServiceCollection(); + serviceCollection.AddInstance(context.Lifetime); + context.Services = serviceCollection.BuildServiceProvider(context.Services); + } + private void EnsureBuilder(HostingContext context) { if (context.Builder != null) diff --git a/src/Microsoft.AspNet.Hosting/IApplicationLifetime.cs b/src/Microsoft.AspNet.Hosting/IApplicationLifetime.cs new file mode 100644 index 0000000000..df20556da1 --- /dev/null +++ b/src/Microsoft.AspNet.Hosting/IApplicationLifetime.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; +using Microsoft.Framework.Runtime; + +namespace Microsoft.AspNet.Hosting +{ + /// + /// Allows consumers to perform cleanup during a graceful shutdown. + /// + [AssemblyNeutral] + public interface IApplicationLifetime + { + /// + /// Triggered when the application host is performing a graceful shutdown. + /// Request may still be in flight. Shutdown will block until this event completes. + /// + /// + CancellationToken ApplicationStopping { get; } + + /// + /// Triggered when the application host is performing a graceful shutdown. + /// All requests should be complete at this point. Shutdown will block + /// until this event completes. + /// + /// + CancellationToken ApplicationStopped { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Hosting/Microsoft.AspNet.Hosting.kproj b/src/Microsoft.AspNet.Hosting/Microsoft.AspNet.Hosting.kproj index 60f6546eb4..c58f27a50a 100644 --- a/src/Microsoft.AspNet.Hosting/Microsoft.AspNet.Hosting.kproj +++ b/src/Microsoft.AspNet.Hosting/Microsoft.AspNet.Hosting.kproj @@ -20,6 +20,7 @@ + @@ -27,6 +28,7 @@ + @@ -45,4 +47,4 @@ - + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Hosting/Program.cs b/src/Microsoft.AspNet.Hosting/Program.cs index 00b4a6af5c..8599267216 100644 --- a/src/Microsoft.AspNet.Hosting/Program.cs +++ b/src/Microsoft.AspNet.Hosting/Program.cs @@ -17,6 +17,8 @@ using System; using System.IO; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Framework.ConfigurationModel; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.Fallback; @@ -67,11 +69,29 @@ namespace Microsoft.AspNet.Hosting throw new Exception("TODO: IHostingEngine service not available exception"); } - using (engine.Start(context)) + var appShutdownService = _serviceProvider.GetService(); + if (appShutdownService == null) + { + throw new Exception("TODO: IApplicationShutdown service not available"); + } + var shutdownHandle = new ManualResetEvent(false); + + var serverShutdown = engine.Start(context); + + appShutdownService.ShutdownRequested.Register(() => + { + serverShutdown.Dispose(); + shutdownHandle.Set(); + }); + + Task ignored = Task.Run(() => { Console.WriteLine("Started"); Console.ReadLine(); - } + appShutdownService.RequestShutdown(); + }); + + shutdownHandle.WaitOne(); } } }