From 9f365727e0e0d1c51e8f0c90cef8452986b87505 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 20 Jan 2016 14:10:31 +0100 Subject: [PATCH 01/14] Bumps version --- build/UmbracoVersion.txt | 2 +- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index e847b7606c..4f9245d744 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,2 +1,2 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) -7.3.5 \ No newline at end of file +7.3.6 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index f2c18b921b..e8cdf14c5b 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -11,5 +11,5 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("7.3.5")] -[assembly: AssemblyInformationalVersion("7.3.5")] \ No newline at end of file +[assembly: AssemblyFileVersion("7.3.6")] +[assembly: AssemblyInformationalVersion("7.3.6")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 8b9fbf05fd..4582e57e82 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("7.3.5"); + private static readonly Version Version = new Version("7.3.6"); /// /// Gets the current version of Umbraco. diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 46ca224bb2..88b157f4f5 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2414,9 +2414,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\" True True - 7350 + 7360 / - http://localhost:7350 + http://localhost:7360 False False From f78a42598552f7ee0c7dc6d545acf53bb7dacdb0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Jan 2016 16:37:47 +0100 Subject: [PATCH 02/14] U4-7816 The IsolatedCache instance in the RepositoryFactory differs from the ApplicationContext singleton instance --- .../Cache/IsolatedRuntimeCache.cs | 8 +- src/Umbraco.Core/CacheHelper.cs | 74 ++++++++----------- .../Persistence/RepositoryFactory.cs | 30 ++++---- src/Umbraco.Web/WebBootManager.cs | 15 +++- 4 files changed, 61 insertions(+), 66 deletions(-) diff --git a/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs b/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs index 103f90345d..da20f7eb73 100644 --- a/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs +++ b/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Cache /// public class IsolatedRuntimeCache { - private readonly Func _cacheFactory; + internal Func CacheFactory { get; set; } /// /// Constructor that allows specifying a factory for the type of runtime isolated cache to create @@ -20,7 +20,7 @@ namespace Umbraco.Core.Cache /// public IsolatedRuntimeCache(Func cacheFactory) { - _cacheFactory = cacheFactory; + CacheFactory = cacheFactory; } private readonly ConcurrentDictionary _isolatedCache = new ConcurrentDictionary(); @@ -32,7 +32,7 @@ namespace Umbraco.Core.Cache /// public IRuntimeCacheProvider GetOrCreateCache() { - return _isolatedCache.GetOrAdd(typeof(T), type => _cacheFactory(type)); + return _isolatedCache.GetOrAdd(typeof(T), type => CacheFactory(type)); } /// @@ -41,7 +41,7 @@ namespace Umbraco.Core.Cache /// public IRuntimeCacheProvider GetOrCreateCache(Type type) { - return _isolatedCache.GetOrAdd(type, t => _cacheFactory(t)); + return _isolatedCache.GetOrAdd(type, t => CacheFactory(t)); } /// diff --git a/src/Umbraco.Core/CacheHelper.cs b/src/Umbraco.Core/CacheHelper.cs index 303cf234fd..4b009e5f86 100644 --- a/src/Umbraco.Core/CacheHelper.cs +++ b/src/Umbraco.Core/CacheHelper.cs @@ -18,12 +18,8 @@ namespace Umbraco.Core /// public class CacheHelper { - private readonly IsolatedRuntimeCache _isolatedCacheManager; - private readonly ICacheProvider _requestCache; private static readonly ICacheProvider NullRequestCache = new NullCacheProvider(); - private readonly ICacheProvider _staticCache; private static readonly ICacheProvider NullStaticCache = new NullCacheProvider(); - private readonly IRuntimeCacheProvider _runtimeCache; private static readonly IRuntimeCacheProvider NullRuntimeCache = new NullCacheProvider(); /// @@ -90,45 +86,33 @@ namespace Umbraco.Core if (staticCacheProvider == null) throw new ArgumentNullException("staticCacheProvider"); if (requestCacheProvider == null) throw new ArgumentNullException("requestCacheProvider"); if (isolatedCacheManager == null) throw new ArgumentNullException("isolatedCacheManager"); - _runtimeCache = httpCacheProvider; - _staticCache = staticCacheProvider; - _requestCache = requestCacheProvider; - _isolatedCacheManager = isolatedCacheManager; + RuntimeCache = httpCacheProvider; + StaticCache = staticCacheProvider; + RequestCache = requestCacheProvider; + IsolatedRuntimeCache = isolatedCacheManager; } /// /// Returns the current Request cache /// - public ICacheProvider RequestCache - { - get { return _requestCache; } - } + public ICacheProvider RequestCache { get; internal set; } /// /// Returns the current Runtime cache /// - public ICacheProvider StaticCache - { - get { return _staticCache; } - } + public ICacheProvider StaticCache { get; internal set; } /// /// Returns the current Runtime cache /// - public IRuntimeCacheProvider RuntimeCache - { - get { return _runtimeCache; } - } + public IRuntimeCacheProvider RuntimeCache { get; internal set; } /// /// Returns the current Isolated Runtime cache manager /// - public IsolatedRuntimeCache IsolatedRuntimeCache - { - get { return _isolatedCacheManager; } - } - - #region Legacy Runtime/Http Cache accessors + public IsolatedRuntimeCache IsolatedRuntimeCache { get; internal set; } + + #region Legacy Runtime/Http Cache accessors /// /// Clears the item in umbraco's runtime cache @@ -137,8 +121,8 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public void ClearAllCache() { - _runtimeCache.ClearAllCache(); - _isolatedCacheManager.ClearAllCaches(); + RuntimeCache.ClearAllCache(); + IsolatedRuntimeCache.ClearAllCaches(); } /// @@ -149,7 +133,7 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public void ClearCacheItem(string key) { - _runtimeCache.ClearCacheItem(key); + RuntimeCache.ClearCacheItem(key); } @@ -161,7 +145,7 @@ namespace Umbraco.Core [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] public void ClearCacheObjectTypes(string typeName) { - _runtimeCache.ClearCacheObjectTypes(typeName); + RuntimeCache.ClearCacheObjectTypes(typeName); } /// @@ -171,7 +155,7 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public void ClearCacheObjectTypes() { - _runtimeCache.ClearCacheObjectTypes(); + RuntimeCache.ClearCacheObjectTypes(); } /// @@ -182,7 +166,7 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public void ClearCacheByKeySearch(string keyStartsWith) { - _runtimeCache.ClearCacheByKeySearch(keyStartsWith); + RuntimeCache.ClearCacheByKeySearch(keyStartsWith); } /// @@ -193,14 +177,14 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public void ClearCacheByKeyExpression(string regexString) { - _runtimeCache.ClearCacheByKeyExpression(regexString); + RuntimeCache.ClearCacheByKeyExpression(regexString); } [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) { - return _runtimeCache.GetCacheItemsByKeySearch(keyStartsWith); + return RuntimeCache.GetCacheItemsByKeySearch(keyStartsWith); } /// @@ -213,7 +197,7 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public TT GetCacheItem(string cacheKey) { - return _runtimeCache.GetCacheItem(cacheKey); + return RuntimeCache.GetCacheItem(cacheKey); } /// @@ -227,7 +211,7 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public TT GetCacheItem(string cacheKey, Func getCacheItem) { - return _runtimeCache.GetCacheItem(cacheKey, getCacheItem); + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem); } @@ -244,7 +228,7 @@ namespace Umbraco.Core public TT GetCacheItem(string cacheKey, TimeSpan timeout, Func getCacheItem) { - return _runtimeCache.GetCacheItem(cacheKey, getCacheItem, timeout); + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout); } @@ -263,7 +247,7 @@ namespace Umbraco.Core CacheItemRemovedCallback refreshAction, TimeSpan timeout, Func getCacheItem) { - return _runtimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction); + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction); } @@ -283,7 +267,7 @@ namespace Umbraco.Core CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan timeout, Func getCacheItem) { - return _runtimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction); + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction); } @@ -306,7 +290,7 @@ namespace Umbraco.Core TimeSpan timeout, Func getCacheItem) { - var cache = _runtimeCache as HttpRuntimeCacheProvider; + var cache = RuntimeCache as HttpRuntimeCacheProvider; if (cache != null) { var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); @@ -330,7 +314,7 @@ namespace Umbraco.Core CacheDependency cacheDependency, Func getCacheItem) { - var cache = _runtimeCache as HttpRuntimeCacheProvider; + var cache = RuntimeCache as HttpRuntimeCacheProvider; if (cache != null) { var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), null, false, priority, null, cacheDependency); @@ -352,7 +336,7 @@ namespace Umbraco.Core CacheItemPriority priority, Func getCacheItem) { - _runtimeCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority); + RuntimeCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority); } @@ -371,7 +355,7 @@ namespace Umbraco.Core TimeSpan timeout, Func getCacheItem) { - _runtimeCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority); + RuntimeCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority); } /// @@ -390,7 +374,7 @@ namespace Umbraco.Core TimeSpan timeout, Func getCacheItem) { - var cache = _runtimeCache as HttpRuntimeCacheProvider; + var cache = RuntimeCache as HttpRuntimeCacheProvider; if (cache != null) { cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, null, cacheDependency); @@ -416,7 +400,7 @@ namespace Umbraco.Core TimeSpan? timeout, Func getCacheItem) { - var cache = _runtimeCache as HttpRuntimeCacheProvider; + var cache = RuntimeCache as HttpRuntimeCacheProvider; if (cache != null) { cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 9bb1d8d11c..1c1e551d34 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -31,29 +31,27 @@ namespace Umbraco.Core.Persistence //if (sqlSyntax == null) throw new ArgumentNullException("sqlSyntax"); if (settings == null) throw new ArgumentNullException("settings"); - _cacheHelper = cacheHelper; + _cacheHelper = cacheHelper; //IMPORTANT: We will force the DeepCloneRuntimeCacheProvider to be used here which is a wrapper for the underlying // runtime cache to ensure that anything that can be deep cloned in/out is done so, this also ensures that our tracks // changes entities are reset. if ((_cacheHelper.RuntimeCache is DeepCloneRuntimeCacheProvider) == false) { - var originalHelper = cacheHelper; - - _cacheHelper = new CacheHelper( - new DeepCloneRuntimeCacheProvider(originalHelper.RuntimeCache), - originalHelper.StaticCache, - originalHelper.RequestCache, - new IsolatedRuntimeCache(type => - { - var cache = originalHelper.IsolatedRuntimeCache.GetOrCreateCache(type); - return (cache is DeepCloneRuntimeCacheProvider) == false - //wrap the original if it's not DeepCloneRuntimeCacheProvider - ? new DeepCloneRuntimeCacheProvider(cache) - : cache; - })); + var origRuntimeCache = cacheHelper.RuntimeCache; + _cacheHelper.RuntimeCache = new DeepCloneRuntimeCacheProvider(origRuntimeCache); } - + //If the factory for isolated cache doesn't return DeepCloneRuntimeCacheProvider, then ensure it does + if (_cacheHelper.IsolatedRuntimeCache.CacheFactory.Method.ReturnType != typeof (DeepCloneRuntimeCacheProvider)) + { + var origFactory = cacheHelper.IsolatedRuntimeCache.CacheFactory; + _cacheHelper.IsolatedRuntimeCache.CacheFactory = type => + { + var cache = origFactory(type); + return new DeepCloneRuntimeCacheProvider(cache); + }; + } + _noCache = CacheHelper.CreateDisabledCacheHelper(); _logger = logger; _sqlSyntax = sqlSyntax; diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index f84d204bb1..dcf1e98be4 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -39,6 +39,7 @@ using Umbraco.Web.Scheduling; using Umbraco.Web.UI.JavaScript; using Umbraco.Web.WebApi; using umbraco.BusinessLogic; +using Umbraco.Core.Cache; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Publishing; @@ -240,7 +241,19 @@ namespace Umbraco.Web protected override CacheHelper CreateApplicationCache() { //create a web-based cache helper - return new CacheHelper(); + var cacheHelper = new CacheHelper( + //we need to have the dep clone runtime cache provider to ensure + //all entities are cached properly (cloned in and cloned out) + new DeepCloneRuntimeCacheProvider(new HttpRuntimeCacheProvider(HttpRuntime.Cache)), + new StaticCacheProvider(), + //we have no request based cache when not running in web-based context + new NullCacheProvider(), + new IsolatedRuntimeCache(type => + //we need to have the dep clone runtime cache provider to ensure + //all entities are cached properly (cloned in and cloned out) + new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); + + return cacheHelper; } /// From 3ecb9d0f862347d515dd5e70890e14d77c34b952 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Jan 2016 17:05:27 +0100 Subject: [PATCH 03/14] U4-7807 Domain, language, public access cache (GetAll) caches not working when there are no items --- .../Cache/DefaultRepositoryCachePolicy.cs | 13 +++++- .../Cache/FullDataSetRepositoryCachePolicy.cs | 29 +++++++++++- ...FullDataSetRepositoryCachePolicyFactory.cs | 2 +- .../Cache/FullDataSetCachePolicyTests.cs | 45 ++++++++++++++++++- 4 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs index 58b3a3956e..66d9b2ac25 100644 --- a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs @@ -158,8 +158,7 @@ namespace Umbraco.Core.Cache else if (_options.GetAllCacheAllowZeroCount) { //if the repository allows caching a zero count, then check the zero count cache - var zeroCount = Cache.GetCacheItem(GetCacheTypeKey()); - if (zeroCount != null && zeroCount.Any() == false) + if (HasZeroCountCache()) { //there is a zero count cache so return an empty list return new TEntity[] {}; @@ -179,6 +178,16 @@ namespace Umbraco.Core.Cache return entityCollection; } + /// + /// Looks up the zero count cache, must return null if it doesn't exist + /// + /// + protected virtual bool HasZeroCountCache() + { + var zeroCount = Cache.GetCacheItem(GetCacheTypeKey()); + return (zeroCount != null && zeroCount.Any() == false); + } + /// /// Performs the lookup for all entities of this type from the cache /// diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index c4c86b2ec7..40b100ef67 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using System.Linq; using Umbraco.Core.Collections; using Umbraco.Core.Models.EntityBase; @@ -12,10 +14,18 @@ namespace Umbraco.Core.Cache internal class FullDataSetRepositoryCachePolicy : DefaultRepositoryCachePolicy where TEntity : class, IAggregateRoot { - public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache) : base(cache, new RepositoryCachePolicyOptions()) + public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache) : base(cache, + new RepositoryCachePolicyOptions + { + //Definitely allow zero'd cache entires since this is a full set, in many cases there will be none, + // and we must cache this! + GetAllCacheAllowZeroCount = true + }) { } + private bool? _hasZeroCountCache; + /// /// For this type of caching policy, we don't cache individual items /// @@ -44,6 +54,19 @@ namespace Umbraco.Core.Cache }); } + /// + /// Looks up the zero count cache, must return null if it doesn't exist + /// + /// + protected override bool HasZeroCountCache() + { + if (_hasZeroCountCache.HasValue) + return _hasZeroCountCache.Value; + + _hasZeroCountCache = Cache.GetCacheItem>(GetCacheTypeKey()) != null; + return _hasZeroCountCache.Value; + } + /// /// This policy will cache the full data set as a single collection /// @@ -51,6 +74,10 @@ namespace Umbraco.Core.Cache protected override TEntity[] GetAllFromCache() { var found = Cache.GetCacheItem>(GetCacheTypeKey()); + + //This method will get called before checking for zero count cache, so we'll just set the flag here + _hasZeroCountCache = found != null; + return found == null ? new TEntity[] { } : found.WhereNotNull().ToArray(); } } diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs index 470db33b6a..75bdae7e83 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Cache public FullDataSetRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache) { - _runtimeCache = runtimeCache; + _runtimeCache = runtimeCache; } public virtual IRepositoryCachePolicy CreatePolicy() diff --git a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs index 7fd894e2a9..3d3884c686 100644 --- a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs +++ b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Web.Caching; using Moq; using NUnit.Framework; @@ -13,6 +14,48 @@ namespace Umbraco.Tests.Cache [TestFixture] public class FullDataSetCachePolicyTests { + [Test] + public void Get_All_Caches_Empty_List() + { + var cached = new List(); + + IList list = null; + + var cache = new Mock(); + cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string cacheKey, Func o, TimeSpan? t, bool b, CacheItemPriority cip, CacheItemRemovedCallback circ, string[] s) => + { + cached.Add(cacheKey); + + list = o() as IList; + }); + cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(() => + { + //return null if this is the first pass + return cached.Any() ? new DeepCloneableList() : null; + }); + + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + using (defaultPolicy) + { + var found = defaultPolicy.GetAll(new object[] {}, o => new AuditItem[] {}); + } + + Assert.AreEqual(1, cached.Count); + Assert.IsNotNull(list); + + //Do it again, ensure that its coming from the cache! + defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + using (defaultPolicy) + { + var found = defaultPolicy.GetAll(new object[] { }, o => new AuditItem[] { }); + } + + Assert.AreEqual(1, cached.Count); + Assert.IsNotNull(list); + } + [Test] public void Get_All_Caches_As_Single_List() { @@ -28,7 +71,7 @@ namespace Umbraco.Tests.Cache list = o() as IList; }); - cache.Setup(x => x.GetCacheItemsByKeySearch(It.IsAny())).Returns(new AuditItem[] { }); + cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem[] { }); var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); using (defaultPolicy) From fe774f557c279b59404b11a83975c92dc58de76d Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Jan 2016 17:06:14 +0100 Subject: [PATCH 04/14] Fixes ms value: U4-7810 DatabaseServerRegistrar should not execute as soon as a request is made, this slows down app start --- .../Sync/DatabaseServerRegistrarOptions.cs | 12 +- .../ServerRegistrationEventHandler.cs | 128 +++++++++++++----- 2 files changed, 106 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Core/Sync/DatabaseServerRegistrarOptions.cs b/src/Umbraco.Core/Sync/DatabaseServerRegistrarOptions.cs index 33ab1c8f57..5b5f6fc457 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerRegistrarOptions.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerRegistrarOptions.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; namespace Umbraco.Core.Sync { @@ -14,13 +15,18 @@ namespace Umbraco.Core.Sync { StaleServerTimeout = TimeSpan.FromMinutes(2); // 2 minutes ThrottleSeconds = 30; // 30 seconds + RecurringSeconds = 60; // do it every minute } - /// - /// The number of seconds to wait between each updates to the database. - /// + [Obsolete("This is no longer used")] + [EditorBrowsable(EditorBrowsableState.Never)] public int ThrottleSeconds { get; set; } + /// + /// The amount of seconds to wait between calls to the database on the background thread + /// + public int RecurringSeconds { get; set; } + /// /// The time span to wait before considering a server stale, after it has last been accessed. /// diff --git a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs index 2a61d4177d..6c15814b92 100644 --- a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs +++ b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; +using System.Threading.Tasks; using System.Web; using Newtonsoft.Json; using Umbraco.Core; @@ -6,6 +8,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Services; using Umbraco.Core.Sync; using Umbraco.Web.Routing; +using Umbraco.Web.Scheduling; namespace Umbraco.Web.Strategies { @@ -22,74 +25,137 @@ namespace Umbraco.Web.Strategies /// public sealed class ServerRegistrationEventHandler : ApplicationEventHandler { - private readonly object _locko = new object(); private DatabaseServerRegistrar _registrar; - private DateTime _lastUpdated = DateTime.MinValue; + private BackgroundTaskRunner _backgroundTaskRunner; + private bool _started = false; // bind to events protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { _registrar = ServerRegistrarResolver.Current.Registrar as DatabaseServerRegistrar; + _backgroundTaskRunner = new BackgroundTaskRunner( + new BackgroundTaskRunnerOptions { AutoStart = true }, + applicationContext.ProfilingLogger.Logger); + // only for the DatabaseServerRegistrar if (_registrar == null) return; + //We will start the whole process when a successful request is made UmbracoModule.RouteAttempt += UmbracoModuleRouteAttempt; } - // handles route attempts. + /// + /// Handle when a request is made + /// + /// + /// + /// + /// We require this because: + /// - ApplicationContext.UmbracoApplicationUrl is initialized by UmbracoModule in BeginRequest + /// - RegisterServer is called on UmbracoModule.RouteAttempt which is triggered in ProcessRequest + /// we are safe, UmbracoApplicationUrl has been initialized + /// private void UmbracoModuleRouteAttempt(object sender, RoutableAttemptEventArgs e) { - if (e.HttpContext.Request == null || e.HttpContext.Request.Url == null) return; - switch (e.Outcome) { case EnsureRoutableOutcome.IsRoutable: // front-end request RegisterServer(e); + //remove handler, we're done + UmbracoModule.RouteAttempt -= UmbracoModuleRouteAttempt; break; case EnsureRoutableOutcome.NotDocumentRequest: // anything else (back-end request, service...) //so it's not a document request, we'll check if it's a back office request if (e.HttpContext.Request.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath)) + { RegisterServer(e); - break; - /* - case EnsureRoutableOutcome.NotReady: - case EnsureRoutableOutcome.NotConfigured: - case EnsureRoutableOutcome.NoContent: - default: - // otherwise, do nothing - break; - */ + //remove handler, we're done + UmbracoModule.RouteAttempt -= UmbracoModuleRouteAttempt; + } + break; } } - - // register current server (throttled). + private void RegisterServer(UmbracoRequestEventArgs e) { - lock (_locko) // ensure we trigger only once - { - var secondsSinceLastUpdate = DateTime.Now.Subtract(_lastUpdated).TotalSeconds; - if (secondsSinceLastUpdate < _registrar.Options.ThrottleSeconds) return; - _lastUpdated = DateTime.Now; - } + //only process once + if (_started) return; + _started = true; + + var serverAddress = e.UmbracoContext.Application.UmbracoApplicationUrl; var svc = e.UmbracoContext.Application.Services.ServerRegistrationService; - // because - // - ApplicationContext.UmbracoApplicationUrl is initialized by UmbracoModule in BeginRequest - // - RegisterServer is called on UmbracoModule.RouteAttempt which is triggered in ProcessRequest - // we are safe, UmbracoApplicationUrl has been initialized - var serverAddress = e.UmbracoContext.Application.UmbracoApplicationUrl; + //Perform the rest async, we don't want to block the startup sequence + // this will just reoccur on a background thread + _backgroundTaskRunner.Add(new TouchServerTask(_backgroundTaskRunner, + 15000, //delay before first execution + _registrar.Options.RecurringSeconds * 1000, //amount of ms between executions + svc, _registrar, serverAddress)); + } - try + private class TouchServerTask : RecurringTaskBase + { + private readonly IServerRegistrationService _svc; + private readonly DatabaseServerRegistrar _registrar; + private readonly string _serverAddress; + + /// + /// Initializes a new instance of the class. + /// + /// The task runner. + /// The delay. + /// The period. + /// + /// + /// + /// The task will repeat itself periodically. Use this constructor to create a new task. + public TouchServerTask(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, + IServerRegistrationService svc, DatabaseServerRegistrar registrar, string serverAddress) + : base(runner, delayMilliseconds, periodMilliseconds) { - svc.TouchServer(serverAddress, svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout); + if (svc == null) throw new ArgumentNullException("svc"); + _svc = svc; + _registrar = registrar; + _serverAddress = serverAddress; } - catch (Exception ex) + + public override bool IsAsync { - LogHelper.Error("Failed to update server record in database.", ex); + get { return false; } + } + + public override bool RunsOnShutdown + { + get { return false; } + } + + /// + /// Runs the background task. + /// + /// A value indicating whether to repeat the task. + public override bool PerformRun() + { + try + { + _svc.TouchServer(_serverAddress, _svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout); + + return true; // repeat + } + catch (Exception ex) + { + LogHelper.Error("Failed to update server record in database.", ex); + + return false; // probably stop if we have an error + } + } + + public override Task PerformRunAsync(CancellationToken token) + { + throw new NotImplementedException(); } } } From 4dc4c36581379b3ff5f2d18e6609e32fecdd2183 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Jan 2016 17:08:21 +0100 Subject: [PATCH 05/14] U4-7811 Content type repositories should use FullDataSetRepositoryCachePolicyFactory for performance Conflicts: src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs --- .../Repositories/ContentTypeBaseRepository.cs | 93 +++++-------------- .../Repositories/ContentTypeRepository.cs | 47 +++++++--- .../IContentTypeCompositionRepository.cs | 11 +++ .../Interfaces/IContentTypeRepository.cs | 2 +- .../Interfaces/IMediaTypeRepository.cs | 5 +- .../Interfaces/IMemberTypeRepository.cs | 2 +- .../Repositories/MediaTypeRepository.cs | 40 ++++++-- .../Repositories/MemberTypeRepository.cs | 55 ++++++----- .../Services/ContentTypeService.cs | 10 +- .../Services/MemberTypeService.cs | 5 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Repositories/ContentTypeRepositoryTest.cs | 2 +- .../Repositories/MediaTypeRepositoryTest.cs | 2 +- .../Repositories/MemberTypeRepositoryTest.cs | 2 +- .../Cache/ContentTypeCacheRefresher.cs | 6 +- 15 files changed, 142 insertions(+), 141 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeCompositionRepository.cs diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index fbc3069e5c..2f42919ce3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -30,12 +30,9 @@ namespace Umbraco.Core.Persistence.Repositories protected ContentTypeBaseRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) - { - _guidRepo = new GuidReadOnlyContentTypeBaseRepository(this, work, cache, logger, sqlSyntax); + { } - - private readonly GuidReadOnlyContentTypeBaseRepository _guidRepo; - + /// /// Returns the content type ids that match the query /// @@ -1197,71 +1194,20 @@ AND umbracoNode.id <> @id", } - /// - /// Inner repository to support the GUID lookups and keep the caching consistent - /// - internal class GuidReadOnlyContentTypeBaseRepository : PetaPocoRepositoryBase - { - private readonly ContentTypeBaseRepository _parentRepo; - - public GuidReadOnlyContentTypeBaseRepository( - ContentTypeBaseRepository parentRepo, - IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) - { - _parentRepo = parentRepo; - } - - protected override TEntity PerformGet(Guid id) - { - return _parentRepo.PerformGet(id); - } - - protected override IEnumerable PerformGetAll(params Guid[] ids) - { - return _parentRepo.PerformGetAll(ids); - } - - protected override Sql GetBaseQuery(bool isCount) - { - return _parentRepo.GetBaseQuery(isCount); - } - - protected override string GetBaseWhereClause() - { - return "umbracoNode.uniqueID = @Id"; - } - - #region No implementation required - protected override IEnumerable PerformGetByQuery(IQuery query) - { - throw new NotImplementedException(); - } - - protected override IEnumerable GetDeleteClauses() - { - throw new NotImplementedException(); - } - - protected override Guid NodeObjectTypeId - { - get { throw new NotImplementedException(); } - } - - protected override void PersistNewItem(TEntity entity) - { - throw new NotImplementedException(); - } - - protected override void PersistUpdatedItem(TEntity entity) - { - throw new NotImplementedException(); - } - #endregion - } - protected abstract TEntity PerformGet(Guid id); + protected abstract TEntity PerformGet(string alias); protected abstract IEnumerable PerformGetAll(params Guid[] ids); + protected abstract bool PerformExists(Guid id); + + /// + /// Gets an Entity by alias + /// + /// + /// + public TEntity Get(string alias) + { + return PerformGet(alias); + } /// /// Gets an Entity by Id @@ -1270,7 +1216,7 @@ AND umbracoNode.id <> @id", /// public TEntity Get(Guid id) { - return _guidRepo.Get(id); + return PerformGet(id); } /// @@ -1278,9 +1224,12 @@ AND umbracoNode.id <> @id", /// /// /// - public IEnumerable GetAll(params Guid[] ids) + /// + /// Ensure explicit implementation, we don't want to have any accidental calls to this since it is essentially the same signature as the main GetAll when there are no parameters + /// + IEnumerable IReadRepository.GetAll(params Guid[] ids) { - return _guidRepo.GetAll(ids); + return PerformGetAll(ids); } /// @@ -1290,7 +1239,7 @@ AND umbracoNode.id <> @id", /// public bool Exists(Guid id) { - return _guidRepo.Exists(id); + return PerformExists(id); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index 95601011fa..9b4f2cc44d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -27,15 +28,20 @@ namespace Umbraco.Core.Persistence.Repositories _templateRepository = templateRepository; } - #region Overrides of RepositoryBase - + private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory + { + get + { + //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + } + } + protected override IContentType PerformGet(int id) { - var contentTypes = ContentTypeQueryMapper.GetContentTypes( - new[] {id}, Database, SqlSyntax, this, _templateRepository); - - var contentType = contentTypes.SingleOrDefault(); - return contentType; + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Id == id); } protected override IEnumerable PerformGetAll(params int[] ids) @@ -244,25 +250,36 @@ namespace Umbraco.Core.Persistence.Repositories protected override IContentType PerformGet(Guid id) { - var contentTypes = ContentTypeQueryMapper.GetContentTypes( - new[] { id }, Database, SqlSyntax, this, _templateRepository); + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Key == id); + } - var contentType = contentTypes.SingleOrDefault(); - return contentType; + protected override IContentType PerformGet(string alias) + { + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); } protected override IEnumerable PerformGetAll(params Guid[] ids) { + //use the underlying GetAll which will force cache all content types + if (ids.Any()) { - return ContentTypeQueryMapper.GetContentTypes(ids, Database, SqlSyntax, this, _templateRepository); + return GetAll().Where(x => ids.Contains(x.Key)); } else { - var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); - var allIds = Database.Fetch(sql).ToArray(); - return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); + return GetAll(); + //var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); + //var allIds = Database.Fetch(sql).ToArray(); + //return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); } } + + protected override bool PerformExists(Guid id) + { + return GetAll().FirstOrDefault(x => x.Key == id) != null; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeCompositionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeCompositionRepository.cs new file mode 100644 index 0000000000..649726b100 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeCompositionRepository.cs @@ -0,0 +1,11 @@ +using System; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IContentTypeCompositionRepository : IRepositoryQueryable, IReadRepository + where TEntity : IContentTypeComposition + { + TEntity Get(string alias); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs index ca7e0e3c32..8aa3217ce1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IContentTypeRepository : IRepositoryQueryable, IReadRepository + public interface IContentTypeRepository : IContentTypeCompositionRepository { /// /// Gets all entities of the specified query diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs index d28c59ac5b..4a82de7610 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs @@ -1,11 +1,10 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IMediaTypeRepository : IRepositoryQueryable, IReadRepository + public interface IMediaTypeRepository : IContentTypeCompositionRepository { /// /// Gets all entities of the specified query diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs index ae7739b28b..fc877d5227 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs @@ -3,7 +3,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { - public interface IMemberTypeRepository : IRepositoryQueryable, IReadRepository + public interface IMemberTypeRepository : IContentTypeCompositionRepository { } diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index bf963492d2..613ca623c7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -24,7 +25,15 @@ namespace Umbraco.Core.Persistence.Repositories { } - #region Overrides of RepositoryBase + private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory + { + get + { + //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + } + } protected override IMediaType PerformGet(int id) { @@ -168,25 +177,36 @@ namespace Umbraco.Core.Persistence.Repositories protected override IMediaType PerformGet(Guid id) { - var contentTypes = ContentTypeQueryMapper.GetMediaTypes( - new[] { id }, Database, SqlSyntax, this); - - var contentType = contentTypes.SingleOrDefault(); - return contentType; + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Key == id); } protected override IEnumerable PerformGetAll(params Guid[] ids) { + //use the underlying GetAll which will force cache all content types + if (ids.Any()) { - return ContentTypeQueryMapper.GetMediaTypes(ids, Database, SqlSyntax, this); + return GetAll().Where(x => ids.Contains(x.Key)); } else { - var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); - var allIds = Database.Fetch(sql).ToArray(); - return ContentTypeQueryMapper.GetMediaTypes(allIds, Database, SqlSyntax, this); + return GetAll(); + //var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); + //var allIds = Database.Fetch(sql).ToArray(); + //return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); } } + + protected override bool PerformExists(Guid id) + { + return GetAll().FirstOrDefault(x => x.Key == id) != null; + } + + protected override IMediaType PerformGet(string alias) + { + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 39f2a93082..b864c05a83 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using log4net; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models; @@ -26,6 +27,16 @@ namespace Umbraco.Core.Persistence.Repositories { } + private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory + { + get + { + //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + } + } + #region Overrides of RepositoryBase protected override IMemberType PerformGet(int id) @@ -262,38 +273,36 @@ namespace Umbraco.Core.Persistence.Repositories protected override IMemberType PerformGet(Guid id) { - var sql = GetBaseQuery(false); - sql.Where("umbracoNode.uniqueID = @Id", new { Id = id }); - sql.OrderByDescending(x => x.NodeId); - - var dtos = - Database.Fetch( - new PropertyTypePropertyGroupRelator().Map, sql); - - if (dtos == null || dtos.Any() == false) - return null; - - var factory = new MemberTypeReadOnlyFactory(); - var member = factory.BuildEntity(dtos.First()); - - return member; + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Key == id); } protected override IEnumerable PerformGetAll(params Guid[] ids) { - var sql = GetBaseQuery(false); + //use the underlying GetAll which will force cache all content types + if (ids.Any()) { - var statement = string.Join(" OR ", ids.Select(x => string.Format("umbracoNode.uniqueID='{0}'", x))); - sql.Where(statement); + return GetAll().Where(x => ids.Contains(x.Key)); } - sql.OrderByDescending(x => x.NodeId, SqlSyntax); + else + { + return GetAll(); + //var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); + //var allIds = Database.Fetch(sql).ToArray(); + //return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); + } + } - var dtos = - Database.Fetch( - new PropertyTypePropertyGroupRelator().Map, sql); + protected override bool PerformExists(Guid id) + { + return GetAll().FirstOrDefault(x => x.Key == id) != null; + } - return BuildFromDtos(dtos); + protected override IMemberType PerformGet(string alias) + { + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); } /// diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index f144fa51e4..e6ead68810 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -158,10 +158,7 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.Alias == alias); - var contentTypes = repository.GetByQuery(query); - - return contentTypes.FirstOrDefault(); + return repository.Get(alias); } } @@ -531,10 +528,7 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.Alias == alias); - var contentTypes = repository.GetByQuery(query); - - return contentTypes.FirstOrDefault(); + return repository.Get(alias); } } diff --git a/src/Umbraco.Core/Services/MemberTypeService.cs b/src/Umbraco.Core/Services/MemberTypeService.cs index 73061be4d0..c6bc9dd24a 100644 --- a/src/Umbraco.Core/Services/MemberTypeService.cs +++ b/src/Umbraco.Core/Services/MemberTypeService.cs @@ -69,10 +69,7 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateMemberTypeRepository(UowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.Alias == alias); - var contentTypes = repository.GetByQuery(query); - - return contentTypes.FirstOrDefault(); + return repository.Get(alias); } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2bf00fcc9d..2ef0501f8e 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -456,6 +456,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 1669b1af5e..f4471e6def 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -297,7 +297,7 @@ namespace Umbraco.Tests.Persistence.Repositories var allGuidIds = repository.GetAll().Select(x => x.Key).ToArray(); // Act - var contentTypes = repository.GetAll(allGuidIds); + var contentTypes = ((IReadRepository)repository).GetAll(allGuidIds); int count = DatabaseContext.Database.ExecuteScalar( "SELECT COUNT(*) FROM umbracoNode WHERE nodeObjectType = @NodeObjectType", diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs index d0d66c5479..39b9475641 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -188,7 +188,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act - var mediaTypes = repository.GetAll(allGuidIds); + var mediaTypes = ((IReadRepository)repository).GetAll(allGuidIds); int count = DatabaseContext.Database.ExecuteScalar( diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs index dca6b4f9f1..aa4027d8a8 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -115,7 +115,7 @@ namespace Umbraco.Tests.Persistence.Repositories repository.AddOrUpdate(memberType2); unitOfWork.Commit(); - var result = repository.GetAll(memberType1.Key, memberType2.Key); + var result = ((IReadRepository)repository).GetAll(memberType1.Key, memberType2.Key); //there are 3 because of the Member type created for init data Assert.AreEqual(2, result.Count()); diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 9664681bb7..44a6efe9ff 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -128,7 +128,11 @@ namespace Umbraco.Web.Cache { ClearAllIsolatedCacheByEntityType(); ClearAllIsolatedCacheByEntityType(); - + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + //all property type cache ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.PropertyTypeCacheKey); //all content type property cache From 2c399860ca071a78040aecac44fc55cff9873c94 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Jan 2016 17:27:46 +0100 Subject: [PATCH 06/14] Fixes DisposableTimer so that the profiled step is disposed - and thus tracked/timed properly! --- src/Umbraco.Core/DisposableTimer.cs | 65 +++++++++++++++++++---------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Core/DisposableTimer.cs b/src/Umbraco.Core/DisposableTimer.cs index 816256360a..c7e8874449 100644 --- a/src/Umbraco.Core/DisposableTimer.cs +++ b/src/Umbraco.Core/DisposableTimer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Web; @@ -12,6 +13,12 @@ namespace Umbraco.Core /// public class DisposableTimer : DisposableObject { + private readonly ILogger _logger; + private readonly LogType? _logType; + private readonly IProfiler _profiler; + private readonly Type _loggerType; + private readonly string _endMessage; + private readonly IDisposable _profilerStep; private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); private readonly Action _callback; @@ -25,25 +32,12 @@ namespace Umbraco.Core if (logger == null) throw new ArgumentNullException("logger"); if (loggerType == null) throw new ArgumentNullException("loggerType"); - _callback = x => - { - if (profiler != null) - { - profiler.DisposeIfDisposable(); - } - switch (logType) - { - case LogType.Debug: - logger.Debug(loggerType, () => endMessage + " (took " + x + "ms)"); - break; - case LogType.Info: - logger.Info(loggerType, () => endMessage + " (took " + x + "ms)"); - break; - default: - throw new ArgumentOutOfRangeException("logType"); - } - - }; + _logger = logger; + _logType = logType; + _profiler = profiler; + _loggerType = loggerType; + _endMessage = endMessage; + switch (logType) { case LogType.Debug: @@ -58,7 +52,7 @@ namespace Umbraco.Core if (profiler != null) { - profiler.Step(loggerType, startMessage); + _profilerStep = profiler.Step(loggerType, startMessage); } } @@ -223,7 +217,36 @@ namespace Umbraco.Core /// protected override void DisposeResources() { - _callback.Invoke(Stopwatch.ElapsedMilliseconds); + if (_profiler != null) + { + _profiler.DisposeIfDisposable(); + } + + if (_profilerStep != null) + { + _profilerStep.Dispose(); + } + + if (_logType.HasValue && _endMessage.IsNullOrWhiteSpace() == false && _loggerType != null && _logger != null) + { + switch (_logType) + { + case LogType.Debug: + _logger.Debug(_loggerType, () => _endMessage + " (took " + Stopwatch.ElapsedMilliseconds + "ms)"); + break; + case LogType.Info: + _logger.Info(_loggerType, () => _endMessage + " (took " + Stopwatch.ElapsedMilliseconds + "ms)"); + break; + default: + throw new ArgumentOutOfRangeException("logType"); + } + } + + if (_callback != null) + { + _callback.Invoke(Stopwatch.ElapsedMilliseconds); + } + } } From 20dc4f5bc4c4185eb1b33267dccd7f760a19ecab Mon Sep 17 00:00:00 2001 From: Shannon Date: Sat, 23 Jan 2016 12:27:40 +0100 Subject: [PATCH 07/14] fixes merge issue/build --- .../Persistence/Repositories/ContentTypeRepository.cs | 4 +--- .../Persistence/Repositories/MediaTypeRepository.cs | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index 9b4f2cc44d..8ab98e86e4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -70,9 +70,7 @@ namespace Umbraco.Core.Persistence.Repositories ? GetAll(dtos.DistinctBy(x => x.ContentTypeDto.NodeId).Select(x => x.ContentTypeDto.NodeId).ToArray()) : Enumerable.Empty(); } - - #endregion - + /// /// Gets all entities of the specified query /// diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index 613ca623c7..68f7a3c0d9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -70,9 +70,7 @@ namespace Umbraco.Core.Persistence.Repositories ? GetAll(dtos.DistinctBy(x => x.NodeId).Select(x => x.NodeId).ToArray()) : Enumerable.Empty(); } - - #endregion - + /// /// Gets all entities of the specified query From b5e8cb29dd785ed979ec058d54d5b41b382e2b2e Mon Sep 17 00:00:00 2001 From: Shannon Date: Sat, 23 Jan 2016 12:38:08 +0100 Subject: [PATCH 08/14] ensure appctx and security isn't nulled on disposal (it's an application singleton it doesn't need to be nulled), creates new CreateContext method for creating standalone UmbracoContext instances. --- src/Umbraco.Web/UmbracoContext.cs | 57 ++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 1c688080ce..fca5ff2371 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -125,6 +125,7 @@ namespace Umbraco.Web if (umbracoSettings == null) throw new ArgumentNullException("umbracoSettings"); if (urlProviders == null) throw new ArgumentNullException("urlProviders"); + //if there's already a singleton, and we're not replacing then there's no need to ensure anything if (UmbracoContext.Current != null) { if (replaceContext == false) @@ -132,6 +133,39 @@ namespace Umbraco.Web UmbracoContext.Current._replacing = true; } + var umbracoContext = CreateContext(httpContext, applicationContext, webSecurity, umbracoSettings, urlProviders, preview ?? false); + + //assign the singleton + UmbracoContext.Current = umbracoContext; + return UmbracoContext.Current; + } + + /// + /// Creates a standalone UmbracoContext instance + /// + /// + /// + /// + /// + /// + /// + /// + /// A new instance of UmbracoContext + /// + public static UmbracoContext CreateContext( + HttpContextBase httpContext, + ApplicationContext applicationContext, + WebSecurity webSecurity, + IUmbracoSettingsSection umbracoSettings, + IEnumerable urlProviders, + bool preview) + { + if (httpContext == null) throw new ArgumentNullException("httpContext"); + if (applicationContext == null) throw new ArgumentNullException("applicationContext"); + if (webSecurity == null) throw new ArgumentNullException("webSecurity"); + if (umbracoSettings == null) throw new ArgumentNullException("umbracoSettings"); + if (urlProviders == null) throw new ArgumentNullException("urlProviders"); + var umbracoContext = new UmbracoContext( httpContext, applicationContext, @@ -142,15 +176,15 @@ namespace Umbraco.Web // create the RoutingContext, and assign var routingContext = new RoutingContext( umbracoContext, - + //TODO: Until the new cache is done we can't really expose these to override/mock new Lazy>(() => ContentFinderResolver.Current.Finders), new Lazy(() => ContentLastChanceFinderResolver.Current.Finder), - + // create the nice urls provider // there's one per request because there are some behavior parameters that can be changed new Lazy( - () => new UrlProvider( + () => new UrlProvider( umbracoContext, umbracoSettings.WebRouting, urlProviders), @@ -159,9 +193,7 @@ namespace Umbraco.Web //assign the routing context back umbracoContext.RoutingContext = routingContext; - //assign the singleton - UmbracoContext.Current = umbracoContext; - return UmbracoContext.Current; + return umbracoContext; } /// @@ -460,18 +492,9 @@ namespace Umbraco.Web protected override void DisposeResources() { Security.DisposeIfDisposable(); - Security = null; - _umbracoContext = null; - //ensure not to dispose this! - Application = null; - //Before we set these to null but in fact these are application lifespan singletons so - //there's no reason we need to set them to null and this also caused a problem with packages - //trying to access the cache properties on RequestEnd. - //http://issues.umbraco.org/issue/U4-2734 - //http://our.umbraco.org/projects/developer-tools/301-url-tracker/version-2/44327-Issues-with-URL-Tracker-in-614 - //ContentCache = null; - //MediaCache = null; + //If not running in a web ctx, ensure the thread based instance is nulled + _umbracoContext = null; } } } \ No newline at end of file From e91b53e66d3a8c8ecdee73d8f40a6a1276372646 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 25 Jan 2016 11:09:20 +0100 Subject: [PATCH 09/14] back ported: https://github.com/Umbraco/Umbraco-CMS/commit/2d0f198f58071f5113d5c3437f10f2b85220c348 --- .../Persistence/Repositories/ContentRepository.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 85491b9f36..1f5cc1ecd4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -155,8 +155,8 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM cmsContentVersion WHERE ContentId = @Id", "DELETE FROM cmsContentXml WHERE nodeId = @Id", "DELETE FROM cmsContent WHERE nodeId = @Id", - "DELETE FROM umbracoNode WHERE id = @Id", - "DELETE FROM umbracoAccess WHERE nodeId = @Id" + "DELETE FROM umbracoAccess WHERE nodeId = @Id", + "DELETE FROM umbracoNode WHERE id = @Id" }; return list; } From e461cb92da18c0f9cb99087de98e76251bac5242 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 26 Jan 2016 16:07:44 +0100 Subject: [PATCH 10/14] U4-7787 Shorthand use of ?altTemplate= doesn't work #U4-7787 Fixed --- src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index 955e316bc3..acc17b370a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -475,7 +475,7 @@ namespace Umbraco.Core.Persistence.Repositories if (aliases.Any() == false) return base.GetAll(); //return from base.GetAll, this is all cached - return base.GetAll().Where(x => aliases.Contains(x.Alias)); + return base.GetAll().Where(x => aliases.InvariantContains(x.Alias)); } public IEnumerable GetChildren(int masterTemplateId) From 74ddd09c5d325ed788e4a5f6116d92862e65e7d2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 17:05:46 +0100 Subject: [PATCH 11/14] U4-7836 YSOD when upgrading Umbraco with error during AddUserDataClaims - this also ensures that no empty/null sections can be added to the user object since this is where the original exception was coming from. --- .../Persistence/Relators/UserSectionRelator.cs | 9 ++++++--- .../Install/InstallSteps/SetUmbracoVersionStep.cs | 11 ++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs b/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs index 859fa7cf5a..923348e729 100644 --- a/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs +++ b/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs @@ -18,8 +18,11 @@ namespace Umbraco.Core.Persistence.Relators // Is this the same DictionaryItem as the current one we're processing if (Current != null && Current.Id == a.Id) { - // Yes, just add this User2AppDto to the current item's collection - Current.User2AppDtos.Add(p); + if (p.AppAlias.IsNullOrWhiteSpace() == false) + { + // Yes, just add this User2AppDto to the current item's collection + Current.User2AppDtos.Add(p); + } // Return null to indicate we're not done with this User yet return null; @@ -35,7 +38,7 @@ namespace Umbraco.Core.Persistence.Relators Current = a; Current.User2AppDtos = new List(); //this can be null since we are doing a left join - if (p.AppAlias != null) + if (p.AppAlias.IsNullOrWhiteSpace() == false) { Current.User2AppDtos.Add(p); } diff --git a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs index 6523925505..f7c9b3d6a5 100644 --- a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs @@ -40,9 +40,14 @@ namespace Umbraco.Web.Install.InstallSteps var clientDependencyConfig = new ClientDependencyConfiguration(_applicationContext.ProfilingLogger.Logger); var clientDependencyUpdated = clientDependencyConfig.IncreaseVersionNumber(); - var security = new WebSecurity(_httpContext, _applicationContext); - security.PerformLogin(0); - + //During a new install we'll log the default user in (which is id = 0). + // During an upgrade, the user will already need to be logged in in order to run the installer. + if (InstallTypeTarget == InstallationType.NewInstall) + { + var security = new WebSecurity(_httpContext, _applicationContext); + security.PerformLogin(0); + } + //reports the ended install var ih = new InstallHelper(UmbracoContext.Current); ih.InstallStatus(true, ""); From 72900ef3b3d996cc32e7681afd9241b847dbe06e Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 17:56:25 +0100 Subject: [PATCH 12/14] U4-7813 Enable MiniProfiler to profiler during startup Conflicts: src/Umbraco.Core/Umbraco.Core.csproj --- .../Profiling/StartupWebProfilerProvider.cs | 126 ++++++++++++++++++ src/Umbraco.Core/Profiling/WebProfiler.cs | 33 +++-- src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Web/WebBootManager.cs | 4 +- 4 files changed, 155 insertions(+), 9 deletions(-) create mode 100644 src/Umbraco.Core/Profiling/StartupWebProfilerProvider.cs diff --git a/src/Umbraco.Core/Profiling/StartupWebProfilerProvider.cs b/src/Umbraco.Core/Profiling/StartupWebProfilerProvider.cs new file mode 100644 index 0000000000..16ce638c0e --- /dev/null +++ b/src/Umbraco.Core/Profiling/StartupWebProfilerProvider.cs @@ -0,0 +1,126 @@ +using System.Threading; +using System.Web; +using StackExchange.Profiling; + +namespace Umbraco.Core.Profiling +{ + /// + /// Allows us to profile items during app startup - before an HttpRequest is created + /// + internal class StartupWebProfilerProvider : WebRequestProfilerProvider + { + public StartupWebProfilerProvider() + { + _startupPhase = StartupPhase.Boot; + //create the startup profiler + _startupProfiler = new MiniProfiler("http://localhost/umbraco-startup", ProfileLevel.Verbose) + { + Name = "StartupProfiler" + }; + } + + private MiniProfiler _startupProfiler; + private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); + + private enum StartupPhase + { + None = 0, + Boot = 1, + Request = 2 + } + + private volatile StartupPhase _startupPhase; + + public void BootComplete() + { + using (new ReadLock(_locker)) + { + if (_startupPhase != StartupPhase.Boot) return; + } + + using (var l = new UpgradeableReadLock(_locker)) + { + if (_startupPhase == StartupPhase.Boot) + { + l.UpgradeToWriteLock(); + + ////Now we need to transfer some information from our startup phase to the normal + ////web request phase to output the startup profiled information. + ////Stop our internal startup profiler, this will write out it's results to storage. + //StopProfiler(_startupProfiler); + //SaveProfiler(_startupProfiler); + + _startupPhase = StartupPhase.Request; + } + } + } + + public override void Stop(bool discardResults) + { + using (new ReadLock(_locker)) + { + if (_startupPhase == StartupPhase.None) + { + base.Stop(discardResults); + return; + } + } + + using (var l = new UpgradeableReadLock(_locker)) + { + if (_startupPhase > 0 && base.GetCurrentProfiler() == null) + { + l.UpgradeToWriteLock(); + + _startupPhase = StartupPhase.None; + + if (HttpContext.Current != null) + { + HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler; + base.Stop(discardResults); + _startupProfiler = null; + } + } + else + { + base.Stop(discardResults); + } + } + } + + public override MiniProfiler Start(ProfileLevel level) + { + using (new ReadLock(_locker)) + { + if (_startupPhase > 0 && base.GetCurrentProfiler() == null) + { + SetProfilerActive(_startupProfiler); + return _startupProfiler; + } + + return base.Start(level); + } + } + + public override MiniProfiler GetCurrentProfiler() + { + using (new ReadLock(_locker)) + { + if (_startupPhase > 0) + { + try + { + var current = base.GetCurrentProfiler(); + if (current == null) return _startupProfiler; + } + catch + { + return _startupProfiler; + } + } + + return base.GetCurrentProfiler(); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Profiling/WebProfiler.cs b/src/Umbraco.Core/Profiling/WebProfiler.cs index 00d088bca7..7e2cf49313 100644 --- a/src/Umbraco.Core/Profiling/WebProfiler.cs +++ b/src/Umbraco.Core/Profiling/WebProfiler.cs @@ -12,15 +12,24 @@ namespace Umbraco.Core.Profiling /// internal class WebProfiler : IProfiler { + private StartupWebProfilerProvider _startupWebProfilerProvider; /// /// Constructor - /// - /// - /// Binds to application events to enable the MiniProfiler - /// + /// internal WebProfiler() { + //setup some defaults + MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); + MiniProfiler.Settings.StackMaxLength = 5000; + + //At this point we know that we've been constructed during app startup, there won't be an HttpRequest in the HttpContext + // since it hasn't started yet. So we need to do some hacking to enable profiling during startup. + _startupWebProfilerProvider = new StartupWebProfilerProvider(); + //this should always be the case during startup, we'll need to set a custom profiler provider + MiniProfiler.Settings.ProfilerProvider = _startupWebProfilerProvider; + + //Binds to application events to enable the MiniProfiler with a real HttpRequest UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit; } @@ -53,7 +62,12 @@ namespace Umbraco.Core.Profiling /// void UmbracoApplicationEndRequest(object sender, EventArgs e) { - if (CanPerformProfilingAction(sender)) + if (_startupWebProfilerProvider != null) + { + Stop(); + _startupWebProfilerProvider = null; + } + else if (CanPerformProfilingAction(sender)) { Stop(); } @@ -66,6 +80,11 @@ namespace Umbraco.Core.Profiling /// void UmbracoApplicationBeginRequest(object sender, EventArgs e) { + if (_startupWebProfilerProvider != null) + { + _startupWebProfilerProvider.BootComplete(); + } + if (CanPerformProfilingAction(sender)) { Start(); @@ -124,9 +143,7 @@ namespace Umbraco.Core.Profiling /// Start the profiler /// public void Start() - { - MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); - MiniProfiler.Settings.StackMaxLength = 5000; + { MiniProfiler.Start(); } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2ef0501f8e..d11270592a 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -469,6 +469,7 @@ + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index dcf1e98be4..a9495afa7c 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -183,7 +183,9 @@ namespace Umbraco.Web base.InitializeProfilerResolver(); //Set the profiler to be the web profiler - ProfilerResolver.Current.SetProfiler(new WebProfiler()); + var profiler = new WebProfiler(); + ProfilerResolver.Current.SetProfiler(profiler); + profiler.Start(); } /// From 19bc97a7fd2bc864dd9419e63420ba94076d795e Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 18:56:29 +0100 Subject: [PATCH 13/14] Removes the static (hack) cache for content types for creating published content --- .../PublishedContent/PublishedContentType.cs | 58 ++----------------- .../Cache/ContentTypeCacheRefresher.cs | 8 +-- .../Cache/DataTypeCacheRefresher.cs | 1 - 3 files changed, 7 insertions(+), 60 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 5f30c08ce7..3cff4f0298 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -98,45 +98,10 @@ namespace Umbraco.Core.Models.PublishedContent #endregion - #region Cache - - // these methods are called by ContentTypeCacheRefresher and DataTypeCacheRefresher - - internal static void ClearAll() - { - Logging.LogHelper.Debug("Clear all."); - // ok and faster to do it by types, assuming noone else caches PublishedContentType instances - //ApplicationContext.Current.ApplicationCache.ClearStaticCacheByKeySearch("PublishedContentType_"); - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes(); - } - - internal static void ClearContentType(int id) - { - Logging.LogHelper.Debug("Clear content type w/id {0}.", () => id); - // requires a predicate because the key does not contain the ID - // faster than key strings comparisons anyway - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes( - (key, value) => value.Id == id); - } - - internal static void ClearDataType(int id) - { - Logging.LogHelper.Debug("Clear data type w/id {0}.", () => id); - // there is no recursion to handle here because a PublishedContentType contains *all* its - // properties ie both its own properties and those that were inherited (it's based upon an - // IContentTypeComposition) and so every PublishedContentType having a property based upon - // the cleared data type, be it local or inherited, will be cleared. - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes( - (key, value) => value.PropertyTypes.Any(x => x.DataTypeId == id)); - } - + public static PublishedContentType Get(PublishedItemType itemType, string alias) { - var key = string.Format("PublishedContentType_{0}_{1}", - itemType.ToString().ToLowerInvariant(), alias.ToLowerInvariant()); - - var type = ApplicationContext.Current.ApplicationCache.StaticCache.GetCacheItem(key, - () => CreatePublishedContentType(itemType, alias)); + var type = CreatePublishedContentType(itemType, alias); return type; } @@ -169,21 +134,8 @@ namespace Umbraco.Core.Models.PublishedContent return new PublishedContentType(contentType); } - // for unit tests - changing the callback must reset the cache obviously - private static Func _getPublishedContentTypeCallBack; - internal static Func GetPublishedContentTypeCallback - { - get { return _getPublishedContentTypeCallBack; } - set - { - // see note above - //ClearAll(); - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheByKeySearch("PublishedContentType_"); - - _getPublishedContentTypeCallBack = value; - } - } - - #endregion + // for unit tests + internal static Func GetPublishedContentTypeCallback { get; set; } + } } diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 44a6efe9ff..c44b3f2b51 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -141,9 +141,7 @@ namespace Umbraco.Web.Cache ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.ContentTypeCacheKey); //clear static object cache global::umbraco.cms.businesslogic.ContentType.RemoveAllDataTypeCache(); - - PublishedContentType.ClearAll(); - + base.RefreshAll(); } @@ -280,9 +278,7 @@ namespace Umbraco.Web.Cache //clears the dictionary object cache of the legacy ContentType global::umbraco.cms.businesslogic.ContentType.RemoveFromDataTypeCache(payload.Alias); - - PublishedContentType.ClearContentType(payload.Id); - + //need to recursively clear the cache for each child content type foreach (var descendant in payload.DescendantPayloads) { diff --git a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs index 11b3ab6294..173f4dcb86 100644 --- a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs @@ -108,7 +108,6 @@ namespace Umbraco.Web.Cache if (dataTypeCache) dataTypeCache.Result.ClearCacheByKeySearch(string.Format("{0}{1}", CacheKeys.DataTypePreValuesCacheKey, payload.Id)); - PublishedContentType.ClearDataType(payload.Id); }); base.Refresh(jsonPayload); From 7a25cb52b218510c1c2230e64df56168601125a1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 19:13:42 +0100 Subject: [PATCH 14/14] updates the repository cache policies so that we aren't keeping everything in cache unnecessarily --- .../Cache/DefaultRepositoryCachePolicy.cs | 17 ++++++++++++++--- .../Cache/FullDataSetRepositoryCachePolicy.cs | 4 ++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs index 66d9b2ac25..45e79a1b67 100644 --- a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs @@ -11,6 +11,10 @@ namespace Umbraco.Core.Cache /// /// /// + /// + /// This cache policy uses sliding expiration and caches instances for 5 minutes. However if allow zero count is true, then we use the + /// default policy with no expiry. + /// internal class DefaultRepositoryCachePolicy : DisposableObject, IRepositoryCachePolicy where TEntity : class, IAggregateRoot { @@ -54,7 +58,9 @@ namespace Umbraco.Core.Cache //just to be safe, we cannot cache an item without an identity if (entity.HasIdentity) { - Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity); + Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity, + timeout: TimeSpan.FromMinutes(5), + isSliding: true); } //If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared @@ -225,7 +231,9 @@ namespace Umbraco.Core.Cache //just to be safe, we cannot cache an item without an identity if (entity.HasIdentity) { - Cache.InsertCacheItem(cacheKey, () => entity); + Cache.InsertCacheItem(cacheKey, () => entity, + timeout: TimeSpan.FromMinutes(5), + isSliding: true); } }); } @@ -244,6 +252,7 @@ namespace Umbraco.Core.Cache { //there was nothing returned but we want to cache a zero count result so add an TEntity[] to the cache // to signify that there is a zero count cache + //NOTE: Don't set expiry/sliding for a zero count Cache.InsertCacheItem(GetCacheTypeKey(), () => new TEntity[] {}); } else @@ -256,7 +265,9 @@ namespace Umbraco.Core.Cache //just to be safe, we cannot cache an item without an identity if (localCopy.HasIdentity) { - Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => localCopy); + Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => localCopy, + timeout: TimeSpan.FromMinutes(5), + isSliding: true); } } } diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index 40b100ef67..3b3c98fc80 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -11,6 +11,10 @@ namespace Umbraco.Core.Cache /// /// /// + /// + /// This caching policy has no sliding expiration but uses the default ObjectCache.InfiniteAbsoluteExpiration as it's timeout, so it + /// should not leave the cache unless the cache memory is exceeded and it gets thrown out. + /// internal class FullDataSetRepositoryCachePolicy : DefaultRepositoryCachePolicy where TEntity : class, IAggregateRoot {