From a97b56606516fd850cb048d6b00cd9ebb0da2833 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 24 Jan 2017 07:59:46 +0100 Subject: [PATCH] U4-9422 - complete implementing scoped xml cache --- .../Cache/ScopedRepositoryCachePolicy.cs | 5 +- src/Umbraco.Core/Scoping/ActionTime.cs | 12 +- src/Umbraco.Core/Scoping/IScope.cs | 55 +++++++--- src/Umbraco.Core/Scoping/NoScope.cs | 8 +- src/Umbraco.Core/Scoping/Scope.cs | 103 +++++++++--------- src/Umbraco.Tests/Scoping/ScopeTests.cs | 4 +- .../umbraco.presentation/content.cs | 2 +- 7 files changed, 110 insertions(+), 79 deletions(-) diff --git a/src/Umbraco.Core/Cache/ScopedRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/ScopedRepositoryCachePolicy.cs index 917e1b2807..0b54bafbc2 100644 --- a/src/Umbraco.Core/Cache/ScopedRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/ScopedRepositoryCachePolicy.cs @@ -30,8 +30,9 @@ namespace Umbraco.Core.Cache private void RegisterDirty() { // use unique names to de-duplicate - _scope.OnExit("dirty_" + typeof (TEntity).Name, - () => _globalIsolatedCache.ClearAllCache()); + // enlisting multiple times is not a problem + _scope.Enlist("dirty_" + typeof (TEntity).Name, ActionTime.BeforeDispose, + (actionTime, completed) => { if (completed) _globalIsolatedCache.ClearAllCache(); }); } public TEntity Get(TId id, Func performGet, Func> performGetAll) diff --git a/src/Umbraco.Core/Scoping/ActionTime.cs b/src/Umbraco.Core/Scoping/ActionTime.cs index 87cfc3b753..ddde2e8b52 100644 --- a/src/Umbraco.Core/Scoping/ActionTime.cs +++ b/src/Umbraco.Core/Scoping/ActionTime.cs @@ -1,9 +1,13 @@ -namespace Umbraco.Core.Scoping +using System; + +namespace Umbraco.Core.Scoping { + [Flags] public enum ActionTime { - BeforeCommit, - BeforeEvent, - BeforeDispose + None = 0, + BeforeCommit = 1, + BeforeEvent = 2, + BeforeDispose = 4 } } \ No newline at end of file diff --git a/src/Umbraco.Core/Scoping/IScope.cs b/src/Umbraco.Core/Scoping/IScope.cs index 9393d1e323..bdbd4f5b6d 100644 --- a/src/Umbraco.Core/Scoping/IScope.cs +++ b/src/Umbraco.Core/Scoping/IScope.cs @@ -36,25 +36,50 @@ namespace Umbraco.Core.Scoping /// void Complete(); + ///// + ///// Registers an action to execute on exit. + ///// + ///// The unique key of the action. + ///// The action. + ///// + ///// The key is unique (as in, dictionary key). + ///// The action will execute only if the scope completes. + ///// + //void OnExit(string key, Action action); + + ///// + ///// The key is unique (as in, dictionary key). + ///// The action always executes, with an argument indicating whether the scope completed. + ///// + //void OnExit(string key, Action action); + /// - /// Registers an action to execute on exit. + /// Enlists an object into the scope. /// - /// The unique key of the action. - /// The action. - /// - /// The key is unique (as in, dictionary key). - /// The action will execute only if the scope completes. - /// - void OnExit(string key, Action action); + /// The type of the object. + /// The unique key. + /// A method creating the object. + /// The object. + T Enlist(string key, Func creator); - /// - /// The key is unique (as in, dictionary key). - /// The action always executes, with an argument indicating whether the scope completed. - /// - void OnExit(string key, Action action); + /// + /// Enlists an action into the scope. + /// + /// The unique key. + /// When to execute the action. + /// The action to execute. + void Enlist(string key, ActionTime actionTimes, Action action); - // fixme - T Enlist(string key, Func creator, Action action); + /// + /// Enlists an object and an action into the scope. + /// + /// The type of the object. + /// The unique key. + /// A method creating the object. + /// When to execute the action. + /// The action to execute. + /// The object. + T Enlist(string key, Func creator, ActionTime actionTimes, Action action); #if DEBUG_SCOPES Guid InstanceId { get; } diff --git a/src/Umbraco.Core/Scoping/NoScope.cs b/src/Umbraco.Core/Scoping/NoScope.cs index 8a5e58f907..7359ba710c 100644 --- a/src/Umbraco.Core/Scoping/NoScope.cs +++ b/src/Umbraco.Core/Scoping/NoScope.cs @@ -84,19 +84,19 @@ namespace Umbraco.Core.Scoping } /// - public void OnExit(string key, Action action) + public T Enlist(string key, Func creator) { throw new NotImplementedException(); } /// - public void OnExit(string key, Action action) + public T Enlist(string key, Func creator, ActionTime actionTimes, Action action) { throw new NotImplementedException(); } - // fixme - public T Enlist(string key, Func creator, Action action) + /// + public void Enlist(string key, ActionTime actionTimes, Action action) { throw new NotImplementedException(); } diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs index f42ade9008..153bc3f18b 100644 --- a/src/Umbraco.Core/Scoping/Scope.cs +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core.Scoping private IsolatedRuntimeCache _isolatedRuntimeCache; private UmbracoDatabase _database; private IList _messages; - private IDictionary> _exitActions; + private IDictionary _enlisted; // this is v7, in v8 this has to change to RepeatableRead private const IsolationLevel DefaultIsolationLevel = IsolationLevel.ReadCommitted; @@ -258,31 +258,18 @@ namespace Umbraco.Core.Scoping } } - // run everything we need to run when completing - List exceptions = null; - foreach (var action in ExitActions.Values) - { - try - { - action(completed); // fixme try catch and everything - } - catch (Exception e) - { - if (exceptions == null) - exceptions = new List(); - exceptions.Add(e); - } - } - if (exceptions != null) - throw new AggregateException("Exceptions were throws by complete actions.", exceptions); - // run enlisted actions - exceptions = null; + RunEnlisted(ActionTime.BeforeDispose, completed); + } + + private void RunEnlisted(ActionTime actionTime, bool completed) + { + List exceptions = null; foreach (var enlisted in Enlisted.Values) { try { - enlisted.Execute(ActionTime.BeforeDispose, completed); + enlisted.Execute(actionTime, completed); } catch (Exception e) { @@ -292,34 +279,9 @@ namespace Umbraco.Core.Scoping } } if (exceptions != null) - throw new AggregateException("Exceptions were thrown by listed actions at ActionTime.BeforeDispose.", exceptions); + throw new AggregateException("Exceptions were thrown by listed actions at ActionTime " + actionTime + ".", exceptions); } - private IDictionary> ExitActions - { - get - { - if (ParentScope != null) return ParentScope.ExitActions; - - return _exitActions ?? (_exitActions - = new Dictionary>()); - } - } - - /// - public void OnExit(string key, Action action) - { - ExitActions[key] = completed => { if (completed) action(); }; - } - - /// - public void OnExit(string key, Action action) - { - ExitActions[key] = action; - } - - private IDictionary _enlisted; - private IDictionary Enlisted { get @@ -338,11 +300,19 @@ namespace Umbraco.Core.Scoping private class EnlistedObject : IEnlistedObject { + private readonly ActionTime _actionTimes; private readonly Action _action; - public EnlistedObject(T item, Action action) + public EnlistedObject(T item) { Item = item; + _actionTimes = ActionTime.None; + } + + public EnlistedObject(T item, ActionTime actionTimes, Action action) + { + Item = item; + _actionTimes = actionTimes; _action = action; } @@ -350,11 +320,13 @@ namespace Umbraco.Core.Scoping public void Execute(ActionTime actionTime, bool completed) { - _action(actionTime, completed, Item); + if (_actionTimes.HasFlag(actionTime)) + _action(actionTime, completed, Item); } } - public T Enlist(string key, Func creator, Action action) + /// + public T Enlist(string key, Func creator) { IEnlistedObject enlisted; if (Enlisted.TryGetValue(key, out enlisted)) @@ -363,9 +335,38 @@ namespace Umbraco.Core.Scoping if (enlistedAs == null) throw new Exception("An item with a different type has already been enlisted with the same key."); return enlistedAs.Item; } - var enlistedOfT = new EnlistedObject(creator(), action); + var enlistedOfT = new EnlistedObject(creator()); Enlisted[key] = enlistedOfT; return enlistedOfT.Item; } + + /// + public T Enlist(string key, Func creator, ActionTime actionTimes, Action action) + { + IEnlistedObject enlisted; + if (Enlisted.TryGetValue(key, out enlisted)) + { + var enlistedAs = enlisted as EnlistedObject; + if (enlistedAs == null) throw new Exception("An item with a different type has already been enlisted with the same key."); + return enlistedAs.Item; + } + var enlistedOfT = new EnlistedObject(creator(), actionTimes, action); + Enlisted[key] = enlistedOfT; + return enlistedOfT.Item; + } + + /// + public void Enlist(string key, ActionTime actionTimes, Action action) + { + IEnlistedObject enlisted; + if (Enlisted.TryGetValue(key, out enlisted)) + { + var enlistedAs = enlisted as EnlistedObject; + if (enlistedAs == null) throw new Exception("An item with a different type has already been enlisted with the same key."); + return; + } + var enlistedOfT = new EnlistedObject(null, actionTimes, (actionTime, completed, item) => action(actionTime, completed)); + Enlisted[key] = enlistedOfT; + } } } diff --git a/src/Umbraco.Tests/Scoping/ScopeTests.cs b/src/Umbraco.Tests/Scoping/ScopeTests.cs index d76c14e189..e1a1f7e278 100644 --- a/src/Umbraco.Tests/Scoping/ScopeTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeTests.cs @@ -421,7 +421,7 @@ namespace Umbraco.Tests.Scoping [TestCase(true)] [TestCase(false)] - public void ScopeAction(bool complete) + public void ScopeEnlist(bool complete) { var scopeProvider = DatabaseContext.ScopeProvider; @@ -430,7 +430,7 @@ namespace Umbraco.Tests.Scoping Assert.IsNull(scopeProvider.AmbientScope); using (var scope = scopeProvider.CreateScope()) { - ((Scope) scope).OnExit("name", x => completed = x); + scope.Enlist("name", ActionTime.BeforeDispose, (a, c) => { completed = c; }); if (complete) scope.Complete(); } diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index ce75bd6fbb..67aff14b21 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -759,9 +759,9 @@ namespace umbraco var releaser = instance._xmlLock.Lock(); return new SafeXmlReaderWriter(instance, releaser, writer, true); }, + ActionTime.BeforeDispose, (actionTime, completed, item) => // action { - if (actionTime != ActionTime.BeforeDispose) return; item.DisposeForReal(completed); });