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

345 lines
13 KiB
C#
Raw Normal View History

2016-08-25 15:09:51 +02:00
using System;
using System.Collections.Generic;
2019-01-03 21:00:28 +01:00
using System.Linq;
using System.Reflection;
2016-08-25 15:09:51 +02:00
using System.Threading;
using System.Web;
2016-08-25 15:09:51 +02:00
using Umbraco.Core.Cache;
using Umbraco.Core.Components;
2017-05-30 15:46:25 +02:00
using Umbraco.Core.Composing;
2017-12-28 09:27:57 +01:00
using Umbraco.Core.Configuration;
2016-08-25 15:09:51 +02:00
using Umbraco.Core.Exceptions;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Logging.Serilog;
2017-12-26 11:35:21 +01:00
using Umbraco.Core.Migrations.Upgrade;
using Umbraco.Core.Persistence;
2016-10-17 15:16:07 +02:00
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Services.Implement;
using Umbraco.Core.Sync;
2016-08-25 15:09:51 +02:00
2017-12-28 09:27:57 +01:00
namespace Umbraco.Core.Runtime
2016-08-25 15:09:51 +02:00
{
/// <summary>
/// Represents the Core Umbraco runtime.
/// </summary>
/// <remarks>Does not handle any of the web-related aspects of Umbraco (startup, etc). It
/// should be possible to use this runtime in console apps.</remarks>
public class CoreRuntime : IRuntime
{
2019-01-03 21:00:28 +01:00
private ComponentCollection _components;
2018-11-28 12:59:40 +01:00
private IFactory _factory;
private RuntimeState _state;
2016-08-25 15:09:51 +02:00
/// <summary>
/// Gets the logger.
/// </summary>
protected ILogger Logger { get; private set; }
2018-12-21 16:25:16 +01:00
/// <summary>
/// Gets the profiler.
/// </summary>
protected IProfiler Profiler { get; private set; }
/// <summary>
/// Gets the profiling logger.
/// </summary>
protected IProfilingLogger ProfilingLogger { get; private set; }
2018-11-27 10:37:33 +01:00
/// <inheritdoc />
public IRuntimeState State => _state;
/// <inheritdoc/>
2018-11-29 10:35:16 +01:00
public virtual IFactory Boot(IRegister register)
2016-08-25 15:09:51 +02:00
{
// create and register the essential services
// ie the bare minimum required to boot
2016-08-25 15:09:51 +02:00
// loggers
2018-12-21 16:25:16 +01:00
var logger = Logger = GetLogger();
var profiler = Profiler = GetProfiler();
var profilingLogger = ProfilingLogger = new ProfilingLogger(logger, profiler);
2016-08-25 15:09:51 +02:00
// the boot loader boots using a container scope, so anything that is PerScope will
// be disposed after the boot loader has booted, and anything else will remain.
// note that this REQUIRES that perWebRequestScope has NOT been enabled yet, else
// the container will fail to create a scope since there is no http context when
// the application starts.
// the boot loader is kept in the runtime for as long as Umbraco runs, and components
// are NOT disposed - which is not a big deal as long as they remain lightweight
// objects.
2018-12-21 16:25:16 +01:00
using (var timer = profilingLogger.TraceDuration<CoreRuntime>(
$"Booting Umbraco {UmbracoVersion.SemanticVersion.ToSemanticString()} on {NetworkHelper.MachineName}.",
"Booted.",
"Boot failed."))
{
2018-12-21 16:25:16 +01:00
logger.Debug<CoreRuntime>("Runtime: {Runtime}", GetType().FullName);
// application environment
ConfigureUnhandledException();
ConfigureAssemblyResolve();
ConfigureApplicationRootPath();
Boot(register, timer);
}
return _factory;
}
/// <summary>
/// Boots the runtime within a timer.
/// </summary>
protected virtual IFactory Boot(IRegister register, DisposableTimer timer)
{
Composition composition = null;
try
{
// throws if not full-trust
new AspNetHostingPermission(AspNetHostingPermissionLevel.Unrestricted).Demand();
// application caches
var appCaches = GetAppCaches();
var runtimeCache = appCaches.RuntimeCache;
// database factory
var databaseFactory = GetDatabaseFactory();
// type loader
var globalSettings = Current.Config.Global();
var localTempStorage = globalSettings.LocalTempStorageLocation;
var typeLoader = new TypeLoader(runtimeCache, localTempStorage, ProfilingLogger);
// runtime state
// beware! must use '() => _factory.GetInstance<T>()' and NOT '_factory.GetInstance<T>'
// as the second one captures the current value (null) and therefore fails
_state = new RuntimeState(Logger,
Current.Config.Umbraco(), Current.Config.Global(),
new Lazy<IMainDom>(() => _factory.GetInstance<IMainDom>()),
new Lazy<IServerRegistrar>(() => _factory.GetInstance<IServerRegistrar>()))
{
2018-12-21 16:25:16 +01:00
Level = RuntimeLevel.Boot
};
2018-12-21 16:25:16 +01:00
// main dom
var mainDom = new MainDom(Logger);
2018-12-21 16:25:16 +01:00
// create the composition
composition = new Composition(register, typeLoader, ProfilingLogger, _state);
composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, mainDom, appCaches, databaseFactory, typeLoader, _state);
2018-12-21 16:25:16 +01:00
// register runtime-level services
// there should be none, really - this is here "just in case"
Compose(composition);
2018-12-21 16:25:16 +01:00
// acquire the main domain, determine our runtime level
AcquireMainDom(mainDom);
DetermineRuntimeLevel(databaseFactory, ProfilingLogger);
2019-01-03 21:00:28 +01:00
// get composers, and compose
var composerTypes = ResolveComposerTypes(typeLoader);
composition.WithCollectionBuilder<ComponentCollectionBuilder>();
var composers = new Composers(composition, composerTypes, ProfilingLogger);
composers.Compose();
2018-12-21 16:25:16 +01:00
// create the factory
_factory = Current.Factory = composition.CreateFactory();
2019-01-07 09:30:47 +01:00
// create & initialize the components
2019-01-03 21:00:28 +01:00
_components = _factory.GetInstance<ComponentCollection>();
2019-01-07 09:30:47 +01:00
_components.Initialize();
2018-12-21 16:25:16 +01:00
}
catch (Exception e)
{
_state.Level = RuntimeLevel.BootFailed;
var bfe = e as BootFailedException ?? new BootFailedException("Boot failed.", e);
_state.BootFailedException = bfe;
timer.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves
// if something goes wrong above, we may end up with no factory
// meaning nothing can get the runtime state, etc - so let's try
// to make sure we have a factory
if (_factory == null)
{
2018-12-21 16:25:16 +01:00
try
2018-11-29 12:27:16 +01:00
{
2018-12-21 16:25:16 +01:00
_factory = Current.Factory = composition?.CreateFactory();
2018-11-29 12:27:16 +01:00
}
2018-12-21 16:25:16 +01:00
catch { /* yea */ }
}
2018-12-21 16:25:16 +01:00
// throwing here can cause w3wp to hard-crash and we want to avoid it.
// instead, we're logging the exception and setting level to BootFailed.
// various parts of Umbraco such as UmbracoModule and UmbracoDefaultOwinStartup
// understand this and will nullify themselves, while UmbracoModule will
// throw a BootFailedException for every requests.
}
2018-11-29 10:35:16 +01:00
return _factory;
}
protected virtual void ConfigureUnhandledException()
{
//take care of unhandled exceptions - there is nothing we can do to
// prevent the launch process to go down but at least we can try
// and log the exception
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.Error<CoreRuntime>(exception, msg);
};
}
protected virtual void ConfigureAssemblyResolve()
{
// When an assembly can't be resolved. In here we can do magic with the assembly name and try loading another.
// This is used for loading a signed assembly of AutoMapper (v. 3.1+) without having to recompile old code.
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
// ensure the assembly is indeed AutoMapper and that the PublicKeyToken is null before trying to Load again
// do NOT just replace this with 'return Assembly', as it will cause an infinite loop -> stack overflow
if (args.Name.StartsWith("AutoMapper") && args.Name.EndsWith("PublicKeyToken=null"))
return Assembly.Load(args.Name.Replace(", PublicKeyToken=null", ", PublicKeyToken=be96cd2c38ef1005"));
return null;
};
}
protected virtual void ConfigureApplicationRootPath()
{
var path = GetApplicationRootPath();
if (string.IsNullOrWhiteSpace(path) == false)
IOHelper.SetRootDirectory(path);
}
2018-12-21 16:25:16 +01:00
private void AcquireMainDom(MainDom mainDom)
{
using (var timer = ProfilingLogger.DebugDuration<CoreRuntime>("Acquiring MainDom.", "Acquired."))
{
try
{
mainDom.Acquire();
}
catch
{
timer.Fail();
throw;
}
}
}
2016-10-13 21:08:07 +02:00
// internal for tests
2018-12-17 18:52:43 +01:00
internal void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, IProfilingLogger profilingLogger)
{
2018-12-17 18:52:43 +01:00
using (var timer = profilingLogger.DebugDuration<CoreRuntime>("Determining runtime level.", "Determined."))
{
try
{
2018-12-17 18:52:43 +01:00
_state.DetermineRuntimeLevel(databaseFactory, profilingLogger);
2018-03-30 10:16:21 +02:00
2018-12-17 18:52:43 +01:00
profilingLogger.Debug<CoreRuntime>("Runtime level: {RuntimeLevel}", _state.Level);
2018-03-30 10:16:21 +02:00
if (_state.Level == RuntimeLevel.Upgrade)
{
2018-12-17 18:52:43 +01:00
profilingLogger.Debug<CoreRuntime>("Configure database factory for upgrades.");
databaseFactory.ConfigureForUpgrade();
2018-03-30 10:16:21 +02:00
}
}
catch
{
_state.Level = RuntimeLevel.BootFailed;
timer.Fail();
throw;
}
}
}
2019-01-03 21:00:28 +01:00
private IEnumerable<Type> ResolveComposerTypes(TypeLoader typeLoader)
{
2019-01-03 21:00:28 +01:00
using (var timer = ProfilingLogger.TraceDuration<CoreRuntime>("Resolving composer types.", "Resolved."))
{
try
{
2019-01-03 21:00:28 +01:00
return GetComposerTypes(typeLoader);
}
catch
{
timer.Fail();
throw;
}
}
2016-08-25 15:09:51 +02:00
}
/// <inheritdoc/>
2016-08-25 15:09:51 +02:00
public virtual void Terminate()
{
2019-01-03 21:00:28 +01:00
_components.Terminate();
2016-08-25 15:09:51 +02:00
}
2017-12-26 11:35:21 +01:00
/// <summary>
/// Composes the runtime.
/// </summary>
public virtual void Compose(Composition composition)
2016-08-25 15:09:51 +02:00
{
2018-11-27 10:37:33 +01:00
// nothing
2016-08-25 15:09:51 +02:00
}
#region Getters
2016-08-25 15:09:51 +02:00
// getters can be implemented by runtimes inheriting from CoreRuntime
2016-08-25 15:09:51 +02:00
/// <summary>
2019-01-03 21:00:28 +01:00
/// Gets all composer types.
/// </summary>
2019-01-03 21:00:28 +01:00
protected virtual IEnumerable<Type> GetComposerTypes(TypeLoader typeLoader)
=> typeLoader.GetTypes<IComposer>();
2016-08-25 15:09:51 +02:00
/// <summary>
/// Gets a logger.
/// </summary>
protected virtual ILogger GetLogger()
=> SerilogLogger.CreateWithDefaultConfiguration();
2016-08-25 15:09:51 +02:00
/// <summary>
/// Gets a profiler.
/// </summary>
protected virtual IProfiler GetProfiler()
=> new LogProfiler(ProfilingLogger);
2016-08-25 15:09:51 +02:00
/// <summary>
/// Gets the application caches.
/// </summary>
protected virtual CacheHelper GetAppCaches()
{
// need the deep clone runtime cache provider to ensure entities are cached properly, ie
// are cloned in and cloned out - no request-based cache here since no web-based context,
// is overriden by the web runtime
return new CacheHelper(
new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()),
new StaticCacheProvider(),
NullCacheProvider.Instance,
new IsolatedRuntimeCache(type => new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider())));
}
2016-08-25 15:09:51 +02:00
2016-09-08 18:43:58 +02:00
// by default, returns null, meaning that Umbraco should auto-detect the application root path.
// override and return the absolute path to the Umbraco site/solution, if needed
protected virtual string GetApplicationRootPath()
=> null;
2016-08-25 15:09:51 +02:00
2018-11-27 10:37:33 +01:00
/// <summary>
/// Gets the database factory.
/// </summary>
/// <remarks>This is strictly internal, for tests only.</remarks>
protected internal virtual IUmbracoDatabaseFactory GetDatabaseFactory()
2018-11-28 12:59:40 +01:00
=> new UmbracoDatabaseFactory(Logger, new Lazy<IMapperCollection>(() => _factory.GetInstance<IMapperCollection>()));
2018-11-27 10:37:33 +01:00
2016-08-25 15:09:51 +02:00
#endregion
}
}