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 _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; /// /// Initializes a new instance of the class. /// 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(); } /// /// Gets the state of the Umbraco runtime. /// public IRuntimeState State { get; } /// public async Task RestartAsync() { await StopAsync(_cancellationToken); await StartAsync(_cancellationToken); } /// 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("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("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 } } } }