Files
Umbraco-CMS/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs

201 lines
8.0 KiB
C#

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Exceptions;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Runtime;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Runtime
{
public class CoreRuntime : IRuntime
{
private readonly ILogger<CoreRuntime> _logger;
private readonly ILoggerFactory _loggerFactory;
private readonly ComponentCollection _components;
private readonly IApplicationShutdownRegistry _applicationShutdownRegistry;
private readonly IProfilingLogger _profilingLogger;
private readonly IMainDom _mainDom;
private readonly IUmbracoDatabaseFactory _databaseFactory;
private readonly IEventAggregator _eventAggregator;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IUmbracoVersion _umbracoVersion;
private CancellationToken _cancellationToken;
/// <summary>
/// Initializes a new instance of the <see cref="CoreRuntime"/> class.
/// </summary>
public CoreRuntime(
ILoggerFactory loggerFactory,
IRuntimeState state,
ComponentCollection components,
IApplicationShutdownRegistry applicationShutdownRegistry,
IProfilingLogger profilingLogger,
IMainDom mainDom,
IUmbracoDatabaseFactory databaseFactory,
IEventAggregator eventAggregator,
IHostingEnvironment hostingEnvironment,
IUmbracoVersion umbracoVersion)
{
State = state;
_loggerFactory = loggerFactory;
_components = components;
_applicationShutdownRegistry = applicationShutdownRegistry;
_profilingLogger = profilingLogger;
_mainDom = mainDom;
_databaseFactory = databaseFactory;
_eventAggregator = eventAggregator;
_hostingEnvironment = hostingEnvironment;
_umbracoVersion = umbracoVersion;
_logger = _loggerFactory.CreateLogger<CoreRuntime>();
}
/// <summary>
/// Gets the state of the Umbraco runtime.
/// </summary>
public IRuntimeState State { get; }
/// <inheritdoc/>
public async Task RestartAsync()
{
await StopAsync(_cancellationToken);
await StartAsync(_cancellationToken);
}
/// <inheritdoc/>
public async Task StartAsync(CancellationToken cancellationToken)
{
_cancellationToken = cancellationToken;
StaticApplicationLogging.Initialize(_loggerFactory);
AppDomain.CurrentDomain.UnhandledException += (_, args) =>
{
var exception = (Exception)args.ExceptionObject;
var isTerminating = args.IsTerminating; // always true?
var msg = "Unhandled exception in AppDomain";
if (isTerminating)
{
msg += " (terminating)";
}
msg += ".";
_logger.LogError(exception, msg);
};
// acquire the main domain - if this fails then anything that should be registered with MainDom will not operate
AcquireMainDom();
await _eventAggregator.PublishAsync(new UmbracoApplicationMainDomAcquiredNotification(), cancellationToken);
// notify for unattended install
await _eventAggregator.PublishAsync(new RuntimeUnattendedInstallNotification());
DetermineRuntimeLevel();
if (!State.UmbracoCanBoot())
{
return; // The exception will be rethrown by BootFailedMiddelware
}
IApplicationShutdownRegistry hostingEnvironmentLifetime = _applicationShutdownRegistry;
if (hostingEnvironmentLifetime == null)
{
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
var unattendedUpgradeNotification = new RuntimeUnattendedUpgradeNotification();
await _eventAggregator.PublishAsync(unattendedUpgradeNotification);
switch (unattendedUpgradeNotification.UnattendedUpgradeResult)
{
case RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors:
if (State.BootFailedException == null)
{
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:
case RuntimeUnattendedUpgradeNotification.UpgradeResult.PackageMigrationComplete:
// upgrade is done, set reason to Run
DetermineRuntimeLevel();
break;
case RuntimeUnattendedUpgradeNotification.UpgradeResult.NotRequired:
break;
}
await _eventAggregator.PublishAsync(new UmbracoApplicationComponentsInstallingNotification(State.Level), cancellationToken);
// create & initialize the components
_components.Initialize();
await _eventAggregator.PublishAsync(new UmbracoApplicationStartingNotification(State.Level), cancellationToken);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
_components.Terminate();
await _eventAggregator.PublishAsync(new UmbracoApplicationStoppingNotification(), cancellationToken);
StaticApplicationLogging.Initialize(null);
}
private void AcquireMainDom()
{
using (DisposableTimer timer = _profilingLogger.DebugDuration<CoreRuntime>("Acquiring MainDom.", "Acquired."))
{
try
{
_mainDom.Acquire(_applicationShutdownRegistry);
}
catch
{
timer?.Fail();
throw;
}
}
}
private void DetermineRuntimeLevel()
{
if (State.BootFailedException != null)
{
// there's already been an exception so cannot boot and no need to check
return;
}
using DisposableTimer timer = _profilingLogger.DebugDuration<CoreRuntime>("Determining runtime level.", "Determined.");
try
{
State.DetermineRuntimeLevel();
_logger.LogDebug("Runtime level: {RuntimeLevel} - {RuntimeLevelReason}", State.Level, State.Reason);
if (State.Level == RuntimeLevel.Upgrade)
{
_logger.LogDebug("Configure database factory for upgrades.");
_databaseFactory.ConfigureForUpgrade();
}
}
catch (Exception ex)
{
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
}
}
}
}