diff --git a/src/Umbraco.Core/Notifications/IUmbracoApplicationLifetimeNotification.cs b/src/Umbraco.Core/Notifications/IUmbracoApplicationLifetimeNotification.cs
new file mode 100644
index 0000000000..4b0ea6826a
--- /dev/null
+++ b/src/Umbraco.Core/Notifications/IUmbracoApplicationLifetimeNotification.cs
@@ -0,0 +1,17 @@
+namespace Umbraco.Cms.Core.Notifications
+{
+ ///
+ /// Represents an Umbraco application lifetime (starting, started, stopping, stopped) notification.
+ ///
+ ///
+ public interface IUmbracoApplicationLifetimeNotification : INotification
+ {
+ ///
+ /// Gets a value indicating whether Umbraco is restarting (e.g. after an install or upgrade).
+ ///
+ ///
+ /// true if Umbraco is restarting; otherwise, false.
+ ///
+ bool IsRestarting { get; }
+ }
+}
diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs
index a3d38720d7..196af7dfe1 100644
--- a/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs
+++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs
@@ -3,7 +3,16 @@ 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
- { }
+ ///
+ public class UmbracoApplicationStartedNotification : IUmbracoApplicationLifetimeNotification
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Indicates whether Umbraco is restarting.
+ public UmbracoApplicationStartedNotification(bool isRestarting) => IsRestarting = isRestarting;
+
+ ///
+ public bool IsRestarting { get; }
+ }
}
diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs
index dd60f9431c..82b87aa3bf 100644
--- a/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs
+++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs
@@ -1,16 +1,34 @@
+using System;
+
namespace Umbraco.Cms.Core.Notifications
{
///
/// Notification that occurs at the very end of the Umbraco boot process (after all s are initialized).
///
- ///
- public class UmbracoApplicationStartingNotification : INotification
+ ///
+ public class UmbracoApplicationStartingNotification : IUmbracoApplicationLifetimeNotification
{
///
/// Initializes a new instance of the class.
///
/// The runtime level
- 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
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The runtime level
+ /// Indicates whether Umbraco is restarting.
+ public UmbracoApplicationStartingNotification(RuntimeLevel runtimeLevel, bool isRestarting)
+ {
+ RuntimeLevel = runtimeLevel;
+ IsRestarting = isRestarting;
+ }
///
/// Gets the runtime level.
@@ -19,5 +37,8 @@ namespace Umbraco.Cms.Core.Notifications
/// The runtime level.
///
public RuntimeLevel RuntimeLevel { get; }
+
+ ///
+ public bool IsRestarting { get; }
}
}
diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs
index be4c6ccfd4..c6dac40a26 100644
--- a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs
+++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs
@@ -3,7 +3,16 @@ namespace Umbraco.Cms.Core.Notifications
///
/// Notification that occurs when Umbraco has completely shutdown.
///
- ///
- public class UmbracoApplicationStoppedNotification : INotification
- { }
+ ///
+ public class UmbracoApplicationStoppedNotification : IUmbracoApplicationLifetimeNotification
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Indicates whether Umbraco is restarting.
+ public UmbracoApplicationStoppedNotification(bool isRestarting) => IsRestarting = isRestarting;
+
+ ///
+ public bool IsRestarting { get; }
+ }
}
diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs
index 6d5234bbcc..062ca954d9 100644
--- a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs
+++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs
@@ -1,9 +1,30 @@
+using System;
+
namespace Umbraco.Cms.Core.Notifications
{
///
/// Notification that occurs when Umbraco is shutting down (after all s are terminated).
///
- ///
- public class UmbracoApplicationStoppingNotification : INotification
- { }
+ ///
+ public class UmbracoApplicationStoppingNotification : IUmbracoApplicationLifetimeNotification
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ [Obsolete("Use ctor with all params")]
+ public UmbracoApplicationStoppingNotification()
+ : this(false)
+ {
+ // TODO: Remove this constructor in V10
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Indicates whether Umbraco is restarting.
+ public UmbracoApplicationStoppingNotification(bool isRestarting) => IsRestarting = isRestarting;
+
+ ///
+ public bool IsRestarting { get; }
+ }
}
diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
index 5dbe78c2f5..851d67e713 100644
--- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
+++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
@@ -134,60 +134,48 @@ namespace Umbraco.Cms.Infrastructure.Runtime
public IRuntimeState State { get; }
///
- 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);
///
- public async Task StartAsync(CancellationToken cancellationToken)
+ public async Task StopAsync(CancellationToken cancellationToken) => await StopAsync(cancellationToken, false);
+
+ ///
+ 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("Acquiring MainDom.", "Acquired."))
+ using DisposableTimer timer = _profilingLogger.DebugDuration("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
}
}
}