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:
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user