2016-08-25 15:09:51 +02:00
using System ;
using System.Collections.Generic ;
2019-01-07 10:43:28 +01:00
using System.Diagnostics ;
2019-11-11 18:56:14 +11:00
using System.IO ;
2020-09-15 15:14:44 +02:00
using Microsoft.Extensions.Logging ;
2016-08-25 15:09:51 +02:00
using Umbraco.Core.Cache ;
2017-05-30 15:46:25 +02:00
using Umbraco.Core.Composing ;
2017-12-28 09:27:57 +01:00
using Umbraco.Core.Configuration ;
2020-09-02 18:10:29 +10:00
using Umbraco.Core.Events ;
2016-08-25 15:09:51 +02:00
using Umbraco.Core.Exceptions ;
2019-11-20 13:38:41 +01:00
using Umbraco.Core.Hosting ;
2016-08-25 15:09:51 +02:00
using Umbraco.Core.IO ;
using Umbraco.Core.Logging ;
2016-09-01 19:06:08 +02:00
using Umbraco.Core.Persistence ;
2016-10-17 15:16:07 +02:00
using Umbraco.Core.Persistence.Mappers ;
2020-09-15 15:14:44 +02:00
using ILogger = Umbraco . Core . Logging . ILogger ;
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 ;
2020-09-02 18:10:29 +10:00
// runtime state, this instance will get replaced again once the essential services are available to run the check
private RuntimeState _state = RuntimeState . Booting ( ) ;
2020-09-15 15:14:44 +02:00
private readonly ILoggerFactory _loggerFactory ;
2019-11-20 10:20:20 +01:00
private readonly IUmbracoBootPermissionChecker _umbracoBootPermissionChecker ;
2020-03-17 16:26:56 +01:00
private readonly IGlobalSettings _globalSettings ;
private readonly IConnectionStrings _connectionStrings ;
2016-08-25 15:09:51 +02:00
2019-12-12 12:55:17 +01:00
public CoreRuntime (
2020-09-16 09:40:49 +02:00
Configs configs ,
2019-12-12 12:55:17 +01:00
IUmbracoVersion umbracoVersion ,
IIOHelper ioHelper ,
ILogger logger ,
2020-09-15 15:14:44 +02:00
ILoggerFactory loggerFactory ,
2019-12-12 12:55:17 +01:00
IProfiler profiler ,
IUmbracoBootPermissionChecker umbracoBootPermissionChecker ,
IHostingEnvironment hostingEnvironment ,
IBackOfficeInfo backOfficeInfo ,
IDbProviderFactoryCreator dbProviderFactoryCreator ,
2020-03-13 14:43:41 +11:00
IMainDom mainDom ,
2020-04-20 06:19:59 +02:00
ITypeFinder typeFinder ,
2020-09-04 00:27:43 +10:00
AppCaches appCaches )
2019-11-15 11:07:37 +01:00
{
IOHelper = ioHelper ;
Configs = configs ;
2020-09-04 00:27:43 +10:00
AppCaches = appCaches ;
2019-11-15 11:07:37 +01:00
UmbracoVersion = umbracoVersion ;
2019-11-20 10:20:20 +01:00
Profiler = profiler ;
2019-11-20 13:38:41 +01:00
HostingEnvironment = hostingEnvironment ;
2019-11-21 14:19:56 +01:00
BackOfficeInfo = backOfficeInfo ;
2019-12-12 08:11:23 +01:00
DbProviderFactoryCreator = dbProviderFactoryCreator ;
2019-11-20 10:20:20 +01:00
2020-09-15 15:14:44 +02:00
_loggerFactory = loggerFactory ;
2019-11-20 10:20:20 +01:00
_umbracoBootPermissionChecker = umbracoBootPermissionChecker ;
2020-09-16 09:40:49 +02:00
2019-11-15 11:07:37 +01:00
Logger = logger ;
2020-01-06 14:59:17 +01:00
MainDom = mainDom ;
2020-03-13 14:43:41 +11:00
TypeFinder = typeFinder ;
2020-01-06 14:59:17 +01:00
2020-03-17 16:26:56 +01:00
_globalSettings = Configs . Global ( ) ;
_connectionStrings = configs . ConnectionStrings ( ) ;
2019-11-15 11:07:37 +01:00
}
2018-11-26 16:54:32 +01:00
/// <summary>
/// Gets the logger.
/// </summary>
2020-09-15 12:40:35 +02:00
public ILogger Logger { get ; }
2018-11-26 16:54:32 +01:00
2019-11-21 14:19:56 +01:00
protected IBackOfficeInfo BackOfficeInfo { get ; }
2020-03-25 15:06:22 +11:00
2019-12-12 08:11:23 +01:00
public IDbProviderFactoryCreator DbProviderFactoryCreator { get ; }
2019-11-21 14:19:56 +01:00
2018-12-21 16:25:16 +01:00
/// <summary>
/// Gets the profiler.
/// </summary>
2020-03-25 15:06:22 +11:00
protected IProfiler Profiler { get ; }
2018-12-21 16:25:16 +01:00
2018-11-26 16:54:32 +01:00
/// <summary>
/// Gets the profiling logger.
/// </summary>
2020-09-02 18:10:29 +10:00
public IProfilingLogger ProfilingLogger { get ; private set ; }
2018-11-26 16:54:32 +01:00
2019-11-11 16:07:47 +11:00
/// <summary>
/// Gets the <see cref="ITypeFinder"/>
/// </summary>
2020-03-13 14:43:41 +11:00
protected ITypeFinder TypeFinder { get ; }
2019-11-11 16:07:47 +11:00
2019-11-11 18:56:14 +11:00
/// <summary>
/// Gets the <see cref="IIOHelper"/>
/// </summary>
2019-11-15 11:07:37 +01:00
protected IIOHelper IOHelper { get ; }
2019-11-20 13:38:41 +01:00
protected IHostingEnvironment HostingEnvironment { get ; }
2020-09-02 18:10:29 +10:00
public Configs Configs { get ; }
2020-09-04 00:27:43 +10:00
public AppCaches AppCaches { get ; }
2020-09-02 18:10:29 +10:00
public IUmbracoVersion UmbracoVersion { get ; }
2019-11-11 18:56:14 +11:00
2018-11-27 10:37:33 +01:00
/// <inheritdoc />
public IRuntimeState State = > _state ;
2020-03-25 15:06:22 +11:00
public IMainDom MainDom { get ; }
2019-12-04 17:18:15 +11:00
2016-08-31 16:48:57 +02:00
/// <inheritdoc/>
2020-03-25 15:06:22 +11:00
public virtual IFactory Configure ( IRegister register )
2016-08-25 15:09:51 +02:00
{
2020-03-24 11:53:56 +11:00
if ( register is null ) throw new ArgumentNullException ( nameof ( register ) ) ;
2020-04-20 06:19:59 +02:00
2018-11-26 16:54:32 +01:00
// create and register the essential services
// ie the bare minimum required to boot
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.
2019-11-14 11:47:34 +01:00
var umbracoVersion = new UmbracoVersion ( ) ;
2019-11-20 10:20:20 +01:00
var profilingLogger = ProfilingLogger = new ProfilingLogger ( Logger , Profiler ) ;
2018-12-21 16:25:16 +01:00
using ( var timer = profilingLogger . TraceDuration < CoreRuntime > (
2019-11-14 11:47:34 +01:00
$"Booting Umbraco {umbracoVersion.SemanticVersion.ToSemanticString()}." ,
2016-09-11 19:57:33 +02:00
"Booted." ,
"Boot failed." ) )
2016-09-01 19:06:08 +02:00
{
2020-04-20 06:19:59 +02:00
2020-09-15 08:45:40 +02:00
Logger . LogInformation ( "Booting site '{HostingSiteName}', app '{HostingApplicationId}', path '{HostingPhysicalPath}', server '{MachineName}'." ,
2020-04-20 06:19:59 +02:00
HostingEnvironment ? . SiteName ,
HostingEnvironment ? . ApplicationId ,
HostingEnvironment ? . ApplicationPhysicalPath ,
NetworkHelper . MachineName ) ;
2020-09-16 10:24:05 +02:00
Logger . LogDebug ( "Runtime: {Runtime}" , GetType ( ) . FullName ) ;
2018-12-21 16:25:16 +01:00
2020-05-04 14:40:11 +02:00
AppDomain . CurrentDomain . SetData ( "DataDirectory" , HostingEnvironment ? . MapPathContentRoot ( Constants . SystemDirectories . Data ) ) ;
2020-04-28 07:01:30 +02:00
2018-12-21 16:25:16 +01:00
// application environment
ConfigureUnhandledException ( ) ;
2020-04-20 06:19:59 +02:00
_factory = Configure ( register , timer ) ;
return _factory ;
2018-12-21 16:25:16 +01:00
}
}
/// <summary>
2020-03-25 15:06:22 +11:00
/// Configure the runtime within a timer.
2018-12-21 16:25:16 +01:00
/// </summary>
2020-03-25 15:06:22 +11:00
private IFactory Configure ( IRegister register , DisposableTimer timer )
2018-12-21 16:25:16 +01:00
{
2020-03-24 11:53:56 +11:00
if ( register is null ) throw new ArgumentNullException ( nameof ( register ) ) ;
if ( timer is null ) throw new ArgumentNullException ( nameof ( timer ) ) ;
2018-12-21 16:25:16 +01:00
Composition composition = null ;
2020-03-25 15:06:22 +11:00
IFactory factory = null ;
2018-12-21 16:25:16 +01:00
try
{
2019-01-23 21:22:49 +01:00
// run handlers
2020-09-02 18:10:29 +10:00
OnRuntimeBoot ( ) ;
2019-01-23 21:22:49 +01:00
2018-12-21 16:25:16 +01:00
// database factory
2020-09-02 18:10:29 +10:00
var databaseFactory = CreateDatabaseFactory ( ) ;
2018-12-21 16:25:16 +01:00
2019-11-08 14:26:06 +11:00
// type finder/loader
2020-09-15 15:14:44 +02:00
var typeLoader = new TypeLoader ( TypeFinder , AppCaches . RuntimeCache , new DirectoryInfo ( HostingEnvironment . LocalTempPath ) , _loggerFactory . CreateLogger ( "TypeLoader" ) , ProfilingLogger ) ;
2018-11-26 16:54:32 +01:00
2020-09-02 18:10:29 +10:00
// re-create the state object with the essential services
_state = new RuntimeState ( Configs . Global ( ) , UmbracoVersion , databaseFactory , Logger ) ;
2018-12-21 16:25:16 +01:00
// create the composition
2020-09-04 00:27:43 +10:00
composition = new Composition ( register , typeLoader , ProfilingLogger , _state , Configs , IOHelper , AppCaches ) ;
2020-09-02 18:10:29 +10:00
2020-09-04 00:27:43 +10:00
composition . RegisterEssentials ( Logger , Profiler , ProfilingLogger , MainDom , AppCaches , databaseFactory , typeLoader , _state , TypeFinder , IOHelper , UmbracoVersion , DbProviderFactoryCreator , HostingEnvironment , BackOfficeInfo ) ;
2018-11-26 16:54:32 +01:00
2020-03-25 15:06:22 +11:00
// register ourselves (TODO: Should we put this in RegisterEssentials?)
composition . Register < IRuntime > ( _ = > this , Lifetime . Singleton ) ;
2019-01-23 21:22:49 +01:00
2020-09-02 18:10:29 +10:00
// run handlers
2020-09-04 00:27:43 +10:00
OnRuntimeEssentials ( composition , AppCaches , typeLoader , databaseFactory ) ;
2020-09-02 18:10:29 +10:00
2020-05-19 14:51:05 +10:00
try
2019-12-18 11:08:15 +11:00
{
2020-05-19 14:51:05 +10:00
// determine our runtime level
2020-09-02 18:10:29 +10:00
DetermineRuntimeLevel ( databaseFactory ) ;
2020-05-19 14:51:05 +10:00
}
finally
{
// always run composers
RunComposers ( typeLoader , composition ) ;
2020-01-06 14:59:17 +01:00
}
2019-12-18 11:08:15 +11:00
2020-05-19 14:51:05 +10:00
// create the factory
factory = composition . CreateFactory ( ) ;
2018-12-21 16:25:16 +01:00
}
catch ( Exception e )
{
var bfe = e as BootFailedException ? ? new BootFailedException ( "Boot failed." , e ) ;
2019-01-07 10:43:28 +01:00
if ( _state ! = null )
{
_state . Level = RuntimeLevel . BootFailed ;
_state . BootFailedException = bfe ;
}
2019-03-06 18:10:43 +01:00
timer ? . Fail ( exception : bfe ) ; // be sure to log the exception - even if we repeat ourselves
2018-12-21 16:25:16 +01:00
// 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
2020-03-25 15:06:22 +11:00
if ( factory = = null )
2016-09-11 19:57:33 +02:00
{
2018-12-21 16:25:16 +01:00
try
2018-11-29 12:27:16 +01:00
{
2020-03-25 15:06:22 +11:00
factory = composition ? . CreateFactory ( ) ;
2018-11-29 12:27:16 +01:00
}
2018-12-21 16:25:16 +01:00
catch { /* yea */ }
2016-09-11 19:57:33 +02:00
}
2018-12-21 16:25:16 +01:00
2019-01-07 10:43:28 +01:00
Debugger . Break ( ) ;
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.
2016-09-11 19:57:33 +02:00
}
2018-11-29 10:35:16 +01:00
2020-03-25 15:06:22 +11:00
return factory ;
}
public void Start ( )
{
2020-05-08 17:30:30 +10:00
if ( _state . Level < = RuntimeLevel . BootFailed )
throw new InvalidOperationException ( $"Cannot start the runtime if the runtime level is less than or equal to {RuntimeLevel.BootFailed}" ) ;
2020-03-25 15:06:22 +11:00
// throws if not full-trust
_umbracoBootPermissionChecker . ThrowIfNotPermissions ( ) ;
2020-03-26 15:39:20 +11:00
var hostingEnvironmentLifetime = _factory . TryGetInstance < IApplicationShutdownRegistry > ( ) ;
2020-03-25 15:06:22 +11:00
if ( hostingEnvironmentLifetime = = null )
2020-03-26 15:39:20 +11:00
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(Start)}" ) ;
2020-03-25 15:06:22 +11:00
// acquire the main domain - if this fails then anything that should be registered with MainDom will not operate
2020-03-26 15:39:20 +11:00
AcquireMainDom ( MainDom , _factory . GetInstance < IApplicationShutdownRegistry > ( ) ) ;
2020-03-25 15:06:22 +11:00
// create & initialize the components
_components = _factory . GetInstance < ComponentCollection > ( ) ;
_components . Initialize ( ) ;
2020-04-21 11:00:30 +02:00
// now (and only now) is the time to switch over to perWebRequest scopes.
// up until that point we may not have a request, and scoped services would
// fail to resolve - but we run Initialize within a factory scope - and then,
// here, we switch the factory to bind scopes to requests
_factory . EnablePerWebRequestScope ( ) ;
2016-09-11 19:57:33 +02:00
}
2016-09-01 19:06:08 +02:00
2018-11-26 16:54:32 +01:00
protected virtual void ConfigureUnhandledException ( )
2018-11-19 22:02:51 +11:00
{
//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 + = "." ;
2020-09-16 09:40:49 +02:00
Logger . LogError ( exception , msg ) ;
2018-11-19 22:02:51 +11:00
} ;
}
2020-05-19 14:51:05 +10:00
private void RunComposers ( TypeLoader typeLoader , Composition composition )
{
// get composers, and compose
var composerTypes = ResolveComposerTypes ( typeLoader ) ;
IEnumerable < Attribute > enableDisableAttributes ;
using ( ProfilingLogger . DebugDuration < CoreRuntime > ( "Scanning enable/disable composer attributes" ) )
{
enableDisableAttributes = typeLoader . GetAssemblyAttributes ( typeof ( EnableComposerAttribute ) , typeof ( DisableComposerAttribute ) ) ;
}
2020-09-15 15:14:44 +02:00
var composers = new Composers ( composition , composerTypes , enableDisableAttributes , _loggerFactory . CreateLogger ( "Composers" ) , ProfilingLogger ) ;
2020-05-19 14:51:05 +10:00
composers . Compose ( ) ;
}
2020-03-26 15:39:20 +11:00
private bool AcquireMainDom ( IMainDom mainDom , IApplicationShutdownRegistry applicationShutdownRegistry )
2016-09-11 19:57:33 +02:00
{
2018-11-26 16:54:32 +01:00
using ( var timer = ProfilingLogger . DebugDuration < CoreRuntime > ( "Acquiring MainDom." , "Acquired." ) )
2016-09-11 19:57:33 +02:00
{
try
2016-09-01 19:06:08 +02:00
{
2020-03-26 15:39:20 +11:00
return mainDom . Acquire ( applicationShutdownRegistry ) ;
2016-09-01 19:06:08 +02:00
}
2016-09-11 19:57:33 +02:00
catch
{
2019-03-06 11:56:03 +01:00
timer ? . Fail ( ) ;
2016-09-11 19:57:33 +02:00
throw ;
}
}
}
2016-09-01 19:06:08 +02:00
2020-09-02 18:10:29 +10:00
private void DetermineRuntimeLevel ( IUmbracoDatabaseFactory databaseFactory )
2016-09-11 19:57:33 +02:00
{
2020-09-02 18:10:29 +10:00
using var timer = ProfilingLogger . DebugDuration < CoreRuntime > ( "Determining runtime level." , "Determined." ) ;
try
2016-09-11 19:57:33 +02:00
{
2020-09-02 18:10:29 +10:00
_state . DetermineRuntimeLevel ( ) ;
2018-03-30 10:16:21 +02:00
2020-09-15 12:40:35 +02:00
Logger . LogDebug ( "Runtime level: {RuntimeLevel} - {RuntimeLevelReason}" , _state . Level , _state . Reason ) ;
2018-03-30 10:16:21 +02:00
2020-09-02 18:10:29 +10:00
if ( _state . Level = = RuntimeLevel . Upgrade )
2016-09-01 19:06:08 +02:00
{
2020-09-15 12:40:35 +02:00
Logger . LogDebug ( "Configure database factory for upgrades." ) ;
2020-09-02 18:10:29 +10:00
databaseFactory . ConfigureForUpgrade ( ) ;
2016-09-01 19:06:08 +02:00
}
2016-09-11 19:57:33 +02:00
}
2020-09-02 18:10:29 +10:00
catch
{
_state . Level = RuntimeLevel . BootFailed ;
_state . Reason = RuntimeLevelReason . BootFailedOnException ;
timer ? . Fail ( ) ;
throw ;
}
2016-09-11 19:57:33 +02:00
}
2016-09-01 19:06:08 +02:00
2019-01-03 21:00:28 +01:00
private IEnumerable < Type > ResolveComposerTypes ( TypeLoader typeLoader )
2016-09-11 19:57:33 +02:00
{
2019-01-03 21:00:28 +01:00
using ( var timer = ProfilingLogger . TraceDuration < CoreRuntime > ( "Resolving composer types." , "Resolved." ) )
2016-09-11 19:57:33 +02:00
{
try
{
2019-01-03 21:00:28 +01:00
return GetComposerTypes ( typeLoader ) ;
2016-09-11 19:57:33 +02:00
}
catch
{
2019-02-05 20:01:20 +01:00
timer ? . Fail ( ) ;
2016-09-11 19:57:33 +02:00
throw ;
}
2016-09-01 19:06:08 +02:00
}
2016-08-25 15:09:51 +02:00
}
2016-09-01 19:06:08 +02:00
/// <inheritdoc/>
2016-08-25 15:09:51 +02:00
public virtual void Terminate ( )
{
2019-01-24 13:54:54 +01:00
_components ? . Terminate ( ) ;
2016-08-25 15:09:51 +02:00
}
2018-11-26 16:54:32 +01:00
#region Getters
2016-08-25 15:09:51 +02:00
2018-11-26 16:54:32 +01:00
// getters can be implemented by runtimes inheriting from CoreRuntime
2016-08-25 15:09:51 +02:00
2018-11-26 16:54:32 +01:00
/// <summary>
2019-01-03 21:00:28 +01:00
/// Gets all composer types.
2018-11-26 16:54:32 +01:00
/// </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
2020-03-31 17:27:51 +11:00
/// <summary>
/// Returns the application path of the site/solution
/// </summary>
/// <returns></returns>
/// <remarks>
/// By default is null which means it's not running in any virtual folder. If the site is running in a virtual folder, this
/// can be overridden and the virtual path returned (i.e. /mysite/)
/// </remarks>
2018-11-26 16:54:32 +01:00
protected virtual string GetApplicationRootPath ( )
= > null ;
2016-08-25 15:09:51 +02:00
2018-11-27 10:37:33 +01:00
/// <summary>
2020-09-02 18:10:29 +10:00
/// Creates the database factory.
2018-11-27 10:37:33 +01:00
/// </summary>
/// <remarks>This is strictly internal, for tests only.</remarks>
2020-09-02 18:10:29 +10:00
protected internal virtual IUmbracoDatabaseFactory CreateDatabaseFactory ( )
2020-03-17 16:26:56 +01:00
= > new UmbracoDatabaseFactory ( Logger , _globalSettings , _connectionStrings , new Lazy < IMapperCollection > ( ( ) = > _factory . GetInstance < IMapperCollection > ( ) ) , DbProviderFactoryCreator ) ;
2018-11-27 10:37:33 +01:00
2019-01-07 10:43:28 +01:00
2016-08-25 15:09:51 +02:00
#endregion
2020-03-25 15:06:22 +11:00
2020-09-02 18:10:29 +10:00
#region Events
protected void OnRuntimeBoot ( )
{
RuntimeOptions . DoRuntimeBoot ( ProfilingLogger ) ;
RuntimeBooting ? . Invoke ( this , ProfilingLogger ) ;
}
protected void OnRuntimeEssentials ( Composition composition , AppCaches appCaches , TypeLoader typeLoader , IUmbracoDatabaseFactory databaseFactory )
{
RuntimeOptions . DoRuntimeEssentials ( composition , appCaches , typeLoader , databaseFactory ) ;
RuntimeEssentials ? . Invoke ( this , new RuntimeEssentialsEventArgs ( composition , appCaches , typeLoader , databaseFactory ) ) ;
}
public event TypedEventHandler < CoreRuntime , IProfilingLogger > RuntimeBooting ;
public event TypedEventHandler < CoreRuntime , RuntimeEssentialsEventArgs > RuntimeEssentials ;
#endregion
2016-08-25 15:09:51 +02:00
}
}