Add IsRestarting property to Umbraco application notifications (#11883)

* Add IsRestarting property to Umbraco application notifications

* Add IUmbracoApplicationLifetimeNotification and update constructors

* Only subscribe to events on initial startup

* Cleanup CoreRuntime

* Do not reset StaticApplicationLogging instance after stopping/during restart
This commit is contained in:
Ronald Barendse
2022-01-25 06:38:20 +01:00
committed by Bjarke Berg
parent f49b661c26
commit 76593aa7ca
6 changed files with 139 additions and 68 deletions

View File

@@ -0,0 +1,17 @@
namespace Umbraco.Cms.Core.Notifications
{
/// <summary>
/// Represents an Umbraco application lifetime (starting, started, stopping, stopped) notification.
/// </summary>
/// <seealso cref="Umbraco.Cms.Core.Notifications.INotification" />
public interface IUmbracoApplicationLifetimeNotification : INotification
{
/// <summary>
/// Gets a value indicating whether Umbraco is restarting (e.g. after an install or upgrade).
/// </summary>
/// <value>
/// <c>true</c> if Umbraco is restarting; otherwise, <c>false</c>.
/// </value>
bool IsRestarting { get; }
}
}

View File

@@ -3,7 +3,16 @@ namespace Umbraco.Cms.Core.Notifications
/// <summary>
/// Notification that occurs when Umbraco has completely booted up and the request processing pipeline is configured.
/// </summary>
/// <seealso cref="Umbraco.Cms.Core.Notifications.INotification" />
public class UmbracoApplicationStartedNotification : INotification
{ }
/// <seealso cref="Umbraco.Cms.Core.Notifications.IUmbracoApplicationLifetimeNotification" />
public class UmbracoApplicationStartedNotification : IUmbracoApplicationLifetimeNotification
{
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoApplicationStartedNotification" /> class.
/// </summary>
/// <param name="isRestarting">Indicates whether Umbraco is restarting.</param>
public UmbracoApplicationStartedNotification(bool isRestarting) => IsRestarting = isRestarting;
/// <inheritdoc />
public bool IsRestarting { get; }
}
}

View File

@@ -1,16 +1,34 @@
using System;
namespace Umbraco.Cms.Core.Notifications
{
/// <summary>
/// Notification that occurs at the very end of the Umbraco boot process (after all <see cref="IComponent" />s are initialized).
/// </summary>
/// <seealso cref="Umbraco.Cms.Core.Notifications.INotification" />
public class UmbracoApplicationStartingNotification : INotification
/// <seealso cref="Umbraco.Cms.Core.Notifications.IUmbracoApplicationLifetimeNotification" />
public class UmbracoApplicationStartingNotification : IUmbracoApplicationLifetimeNotification
{
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoApplicationStartingNotification" /> class.
/// </summary>
/// <param name="runtimeLevel">The runtime level</param>
public UmbracoApplicationStartingNotification(RuntimeLevel runtimeLevel) => RuntimeLevel = runtimeLevel;
[Obsolete("Use ctor with all params")]
public UmbracoApplicationStartingNotification(RuntimeLevel runtimeLevel)
: this(runtimeLevel, false)
{
// TODO: Remove this constructor in V10
}
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoApplicationStartingNotification" /> class.
/// </summary>
/// <param name="runtimeLevel">The runtime level</param>
/// <param name="isRestarting">Indicates whether Umbraco is restarting.</param>
public UmbracoApplicationStartingNotification(RuntimeLevel runtimeLevel, bool isRestarting)
{
RuntimeLevel = runtimeLevel;
IsRestarting = isRestarting;
}
/// <summary>
/// Gets the runtime level.
@@ -19,5 +37,8 @@ namespace Umbraco.Cms.Core.Notifications
/// The runtime level.
/// </value>
public RuntimeLevel RuntimeLevel { get; }
/// <inheritdoc />
public bool IsRestarting { get; }
}
}

View File

@@ -3,7 +3,16 @@ namespace Umbraco.Cms.Core.Notifications
/// <summary>
/// Notification that occurs when Umbraco has completely shutdown.
/// </summary>
/// <seealso cref="Umbraco.Cms.Core.Notifications.INotification" />
public class UmbracoApplicationStoppedNotification : INotification
{ }
/// <seealso cref="Umbraco.Cms.Core.Notifications.IUmbracoApplicationLifetimeNotification" />
public class UmbracoApplicationStoppedNotification : IUmbracoApplicationLifetimeNotification
{
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoApplicationStoppedNotification" /> class.
/// </summary>
/// <param name="isRestarting">Indicates whether Umbraco is restarting.</param>
public UmbracoApplicationStoppedNotification(bool isRestarting) => IsRestarting = isRestarting;
/// <inheritdoc />
public bool IsRestarting { get; }
}
}

View File

@@ -1,9 +1,30 @@
using System;
namespace Umbraco.Cms.Core.Notifications
{
/// <summary>
/// Notification that occurs when Umbraco is shutting down (after all <see cref="IComponent" />s are terminated).
/// </summary>
/// <seealso cref="Umbraco.Cms.Core.Notifications.INotification" />
public class UmbracoApplicationStoppingNotification : INotification
{ }
/// <seealso cref="Umbraco.Cms.Core.Notifications.IUmbracoApplicationLifetimeNotification" />
public class UmbracoApplicationStoppingNotification : IUmbracoApplicationLifetimeNotification
{
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoApplicationStoppingNotification" /> class.
/// </summary>
[Obsolete("Use ctor with all params")]
public UmbracoApplicationStoppingNotification()
: this(false)
{
// TODO: Remove this constructor in V10
}
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoApplicationStoppingNotification" /> class.
/// </summary>
/// <param name="isRestarting">Indicates whether Umbraco is restarting.</param>
public UmbracoApplicationStoppingNotification(bool isRestarting) => IsRestarting = isRestarting;
/// <inheritdoc />
public bool IsRestarting { get; }
}
}

View File

@@ -134,60 +134,48 @@ namespace Umbraco.Cms.Infrastructure.Runtime
public IRuntimeState State { get; }
/// <inheritdoc/>
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) => await StartAsync(cancellationToken, false);
/// <inheritdoc/>
public async Task StartAsync(CancellationToken cancellationToken)
public async Task StopAsync(CancellationToken cancellationToken) => await StopAsync(cancellationToken, false);
/// <inheritdoc/>
public async Task RestartAsync()
{
await StopAsync(_cancellationToken, true);
await _eventAggregator.PublishAsync(new UmbracoApplicationStoppedNotification(true), _cancellationToken);
await StartAsync(_cancellationToken, true);
await _eventAggregator.PublishAsync(new UmbracoApplicationStartedNotification(true), _cancellationToken);
}
private async Task StartAsync(CancellationToken cancellationToken, bool isRestarting)
{
// Store token, so we can re-use this during restart
_cancellationToken = cancellationToken;
StaticApplicationLogging.Initialize(_loggerFactory);
StaticServiceProvider.Instance = _serviceProvider;
AppDomain.CurrentDomain.UnhandledException += (_, args) =>
if (isRestarting == false)
{
var exception = (Exception)args.ExceptionObject;
var isTerminating = args.IsTerminating; // always true?
StaticApplicationLogging.Initialize(_loggerFactory);
StaticServiceProvider.Instance = _serviceProvider;
var msg = "Unhandled exception in AppDomain";
if (isTerminating)
{
msg += " (terminating)";
}
msg += ".";
_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()));
AppDomain.CurrentDomain.UnhandledException += (_, args)
=> _logger.LogError(args.ExceptionObject as Exception, $"Unhandled exception in AppDomain{(args.IsTerminating ? " (terminating)" : null)}.");
}
// acquire the main domain - if this fails then anything that should be registered with MainDom will not operate
// Acquire the main domain - if this fails then anything that should be registered with MainDom will not operate
AcquireMainDom();
// TODO (V10): Remove this obsoleted notification publish.
await _eventAggregator.PublishAsync(new UmbracoApplicationMainDomAcquiredNotification(), cancellationToken);
// notify for unattended install
// Notify for unattended install
await _eventAggregator.PublishAsync(new RuntimeUnattendedInstallNotification(), cancellationToken);
DetermineRuntimeLevel();
if (!State.UmbracoCanBoot())
{
return; // The exception will be rethrown by BootFailedMiddelware
// We cannot continue here, the exception will be rethrown by BootFailedMiddelware
return;
}
IApplicationShutdownRegistry hostingEnvironmentLifetime = _applicationShutdownRegistry;
@@ -196,7 +184,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime
throw new InvalidOperationException($"An instance of {typeof(IApplicationShutdownRegistry)} could not be resolved from the container, ensure that one if registered in your runtime before calling {nameof(IRuntime)}.{nameof(StartAsync)}");
}
// if level is Run and reason is UpgradeMigrations, that means we need to perform an unattended upgrade
// 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, cancellationToken);
switch (unattendedUpgradeNotification.UnattendedUpgradeResult)
@@ -207,54 +195,59 @@ 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
// We cannot continue here, the exception will be rethrown by BootFailedMiddelware
return;
case RuntimeUnattendedUpgradeNotification.UpgradeResult.CoreUpgradeComplete:
case RuntimeUnattendedUpgradeNotification.UpgradeResult.PackageMigrationComplete:
// upgrade is done, set reason to Run
// Upgrade is done, set reason to Run
DetermineRuntimeLevel();
break;
case RuntimeUnattendedUpgradeNotification.UpgradeResult.NotRequired:
break;
}
// TODO (V10): Remove this obsoleted notification publish.
// TODO (V10): Remove this obsoleted notification publish
await _eventAggregator.PublishAsync(new UmbracoApplicationComponentsInstallingNotification(State.Level), cancellationToken);
// create & initialize the components
// Initialize the components
_components.Initialize();
await _eventAggregator.PublishAsync(new UmbracoApplicationStartingNotification(State.Level), cancellationToken);
await _eventAggregator.PublishAsync(new UmbracoApplicationStartingNotification(State.Level, isRestarting), cancellationToken);
if (isRestarting == false)
{
// Add application started and stopped notifications last (to ensure they're always published after starting)
_hostApplicationLifetime.ApplicationStarted.Register(() => _eventAggregator.Publish(new UmbracoApplicationStartedNotification(false)));
_hostApplicationLifetime.ApplicationStopped.Register(() => _eventAggregator.Publish(new UmbracoApplicationStoppedNotification(false)));
}
}
public async Task StopAsync(CancellationToken cancellationToken)
private async Task StopAsync(CancellationToken cancellationToken, bool isRestarting)
{
_components.Terminate();
await _eventAggregator.PublishAsync(new UmbracoApplicationStoppingNotification(), cancellationToken);
StaticApplicationLogging.Initialize(null);
await _eventAggregator.PublishAsync(new UmbracoApplicationStoppingNotification(isRestarting), cancellationToken);
}
private void AcquireMainDom()
{
using (DisposableTimer timer = _profilingLogger.DebugDuration<CoreRuntime>("Acquiring MainDom.", "Acquired."))
using DisposableTimer timer = _profilingLogger.DebugDuration<CoreRuntime>("Acquiring MainDom.", "Acquired.");
try
{
try
{
_mainDom.Acquire(_applicationShutdownRegistry);
}
catch
{
timer?.Fail();
throw;
}
_mainDom.Acquire(_applicationShutdownRegistry);
}
catch
{
timer?.Fail();
throw;
}
}
private void DetermineRuntimeLevel()
{
if (State.BootFailedException != null)
if (State.BootFailedException is not null)
{
// there's already been an exception so cannot boot and no need to check
// There's already been an exception, so cannot boot and no need to check
return;
}
@@ -277,7 +270,8 @@ namespace Umbraco.Cms.Infrastructure.Runtime
State.Configure(RuntimeLevel.BootFailed, RuntimeLevelReason.BootFailedOnException);
timer?.Fail();
_logger.LogError(ex, "Boot Failed");
// We do not throw the exception. It will be rethrown by BootFailedMiddleware
// We do not throw the exception, it will be rethrown by BootFailedMiddleware
}
}
}