diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs new file mode 100644 index 0000000000..a3d38720d7 --- /dev/null +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Core.Notifications +{ + /// + /// Notification that occurs when Umbraco has completely booted up and the request processing pipeline is configured. + /// + /// + public class UmbracoApplicationStartedNotification : INotification + { } +} diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs index 4cbf0a55c6..dd60f9431c 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs @@ -1,23 +1,23 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - namespace Umbraco.Cms.Core.Notifications { /// - /// Notification that occurs at the very end of the Umbraco boot - /// process and after all initialize. + /// Notification that occurs at the very end of the Umbraco boot process (after all s are initialized). /// + /// public class UmbracoApplicationStartingNotification : INotification { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The runtime level public UmbracoApplicationStartingNotification(RuntimeLevel runtimeLevel) => RuntimeLevel = runtimeLevel; /// - /// Gets the runtime level of execution. + /// Gets the runtime level. /// + /// + /// The runtime level. + /// public RuntimeLevel RuntimeLevel { get; } } } diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs new file mode 100644 index 0000000000..be4c6ccfd4 --- /dev/null +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Core.Notifications +{ + /// + /// Notification that occurs when Umbraco has completely shutdown. + /// + /// + public class UmbracoApplicationStoppedNotification : INotification + { } +} diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs index db86a1e614..6d5234bbcc 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs @@ -1,4 +1,9 @@ namespace Umbraco.Cms.Core.Notifications { - public class UmbracoApplicationStoppingNotification : INotification { } + /// + /// Notification that occurs when Umbraco is shutting down (after all s are terminated). + /// + /// + public class UmbracoApplicationStoppingNotification : INotification + { } } diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 175bceb9e0..5dbe78c2f5 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -2,6 +2,8 @@ using System; using System.ComponentModel; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; @@ -16,12 +18,13 @@ using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; using ComponentCollection = Umbraco.Cms.Core.Composing.ComponentCollection; +using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; namespace Umbraco.Cms.Infrastructure.Runtime { + /// public class CoreRuntime : IRuntime { - private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; private readonly ComponentCollection _components; private readonly IApplicationShutdownRegistry _applicationShutdownRegistry; @@ -32,14 +35,16 @@ namespace Umbraco.Cms.Infrastructure.Runtime private readonly IHostingEnvironment _hostingEnvironment; private readonly IUmbracoVersion _umbracoVersion; private readonly IServiceProvider _serviceProvider; + private readonly IHostApplicationLifetime _hostApplicationLifetime; + private readonly ILogger _logger; private CancellationToken _cancellationToken; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public CoreRuntime( - ILoggerFactory loggerFactory, IRuntimeState state, + ILoggerFactory loggerFactory, ComponentCollection components, IApplicationShutdownRegistry applicationShutdownRegistry, IProfilingLogger profilingLogger, @@ -48,9 +53,11 @@ namespace Umbraco.Cms.Infrastructure.Runtime IEventAggregator eventAggregator, IHostingEnvironment hostingEnvironment, IUmbracoVersion umbracoVersion, - IServiceProvider serviceProvider) + IServiceProvider serviceProvider, + IHostApplicationLifetime hostApplicationLifetime) { State = state; + _loggerFactory = loggerFactory; _components = components; _applicationShutdownRegistry = applicationShutdownRegistry; @@ -61,6 +68,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime _hostingEnvironment = hostingEnvironment; _umbracoVersion = umbracoVersion; _serviceProvider = serviceProvider; + _hostApplicationLifetime = hostApplicationLifetime; _logger = _loggerFactory.CreateLogger(); } @@ -76,23 +84,49 @@ namespace Umbraco.Cms.Infrastructure.Runtime IUmbracoDatabaseFactory databaseFactory, IEventAggregator eventAggregator, IHostingEnvironment hostingEnvironment, - IUmbracoVersion umbracoVersion - ):this( - loggerFactory, - state, - components, - applicationShutdownRegistry, - profilingLogger, - mainDom, - databaseFactory, - eventAggregator, - hostingEnvironment, - umbracoVersion, - null - ) - { + IUmbracoVersion umbracoVersion, + IServiceProvider serviceProvider) + : this( + state, + loggerFactory, + components, + applicationShutdownRegistry, + profilingLogger, + mainDom, + databaseFactory, + eventAggregator, + hostingEnvironment, + umbracoVersion, + serviceProvider, + serviceProvider?.GetRequiredService()) + { } - } + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete] + public CoreRuntime( + ILoggerFactory loggerFactory, + IRuntimeState state, + ComponentCollection components, + IApplicationShutdownRegistry applicationShutdownRegistry, + IProfilingLogger profilingLogger, + IMainDom mainDom, + IUmbracoDatabaseFactory databaseFactory, + IEventAggregator eventAggregator, + IHostingEnvironment hostingEnvironment, + IUmbracoVersion umbracoVersion) + : this( + loggerFactory, + state, + components, + applicationShutdownRegistry, + profilingLogger, + mainDom, + databaseFactory, + eventAggregator, + hostingEnvironment, + umbracoVersion, + null) + { } /// /// Gets the state of the Umbraco runtime. @@ -103,13 +137,17 @@ namespace Umbraco.Cms.Infrastructure.Runtime public async Task RestartAsync() { await StopAsync(_cancellationToken); + await _eventAggregator.PublishAsync(new UmbracoApplicationStoppedNotification(), _cancellationToken); await StartAsync(_cancellationToken); + await _eventAggregator.PublishAsync(new UmbracoApplicationStartedNotification(), _cancellationToken); } /// public async Task StartAsync(CancellationToken cancellationToken) { + // Store token, so we can re-use this during restart _cancellationToken = cancellationToken; + StaticApplicationLogging.Initialize(_loggerFactory); StaticServiceProvider.Instance = _serviceProvider; @@ -130,6 +168,13 @@ namespace Umbraco.Cms.Infrastructure.Runtime _logger.LogError(exception, msg); }; + // Add application started and stopped notifications (only on initial startup, not restarts) + if (_hostApplicationLifetime.ApplicationStarted.IsCancellationRequested == false) + { + _hostApplicationLifetime.ApplicationStarted.Register(() => _eventAggregator.Publish(new UmbracoApplicationStartedNotification())); + _hostApplicationLifetime.ApplicationStopped.Register(() => _eventAggregator.Publish(new UmbracoApplicationStoppedNotification())); + } + // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate AcquireMainDom(); @@ -137,7 +182,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime await _eventAggregator.PublishAsync(new UmbracoApplicationMainDomAcquiredNotification(), cancellationToken); // notify for unattended install - await _eventAggregator.PublishAsync(new RuntimeUnattendedInstallNotification()); + await _eventAggregator.PublishAsync(new RuntimeUnattendedInstallNotification(), cancellationToken); DetermineRuntimeLevel(); if (!State.UmbracoCanBoot()) @@ -153,7 +198,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime // if level is Run and reason is UpgradeMigrations, that means we need to perform an unattended upgrade var unattendedUpgradeNotification = new RuntimeUnattendedUpgradeNotification(); - await _eventAggregator.PublishAsync(unattendedUpgradeNotification); + await _eventAggregator.PublishAsync(unattendedUpgradeNotification, cancellationToken); switch (unattendedUpgradeNotification.UnattendedUpgradeResult) { case RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors: @@ -161,6 +206,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime { throw new InvalidOperationException($"Unattended upgrade result was {RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors} but no {nameof(BootFailedException)} was registered"); } + // we cannot continue here, the exception will be rethrown by BootFailedMiddelware return; case RuntimeUnattendedUpgradeNotification.UpgradeResult.CoreUpgradeComplete: