From 36ba30fcbdabd2e9f896f200d098561f1ab0efa0 Mon Sep 17 00:00:00 2001 From: Erik-Jan Westendorp Date: Wed, 1 Sep 2021 15:34:15 +0200 Subject: [PATCH 1/9] Close dialog if response has notifications --- .../src/views/content/content.delete.controller.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js index a0eb80b2dd..c3a4cfb1c5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js @@ -68,6 +68,10 @@ function ContentDeleteController($scope, $timeout, contentResource, treeService, // TODO: All YSOD handling should be done with an interceptor overlayService.ysod(err); } + + if(err.data && err.data.notifications && err.data.notifications.length > 0) { + navigationService.hideDialog(); + } }); }; From 598b5cf0fca77c4095abbb0d25970b25f563b2c4 Mon Sep 17 00:00:00 2001 From: Erik-Jan Westendorp Date: Wed, 1 Sep 2021 15:36:52 +0200 Subject: [PATCH 2/9] Close dialog if show ysod dialog --- .../src/views/content/content.delete.controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js index c3a4cfb1c5..060c0ec7d9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js @@ -67,6 +67,7 @@ function ContentDeleteController($scope, $timeout, contentResource, treeService, if (err.status && err.status >= 500) { // TODO: All YSOD handling should be done with an interceptor overlayService.ysod(err); + navigationService.hideDialog(); } if(err.data && err.data.notifications && err.data.notifications.length > 0) { From b17cc476327057a8ca9ad524f5c947eee147abeb Mon Sep 17 00:00:00 2001 From: Chad Currie Date: Sun, 5 Sep 2021 14:54:06 +1200 Subject: [PATCH 3/9] Fix message template to not mix string interpolating and formatting. --- src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 1de53e9c6e..127fbd0edb 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -717,7 +717,7 @@ namespace Umbraco.Web.PublishedCache.NuCache previousNode = null; // there is no previous sibling } - _logger.Debug($"Set {thisNode.Id} with parent {thisNode.ParentContentId}"); + _logger.Debug("Set {Id} with parent {ParentContentId}", thisNode.Id, thisNode.ParentContentId); SetValueLocked(_contentNodes, thisNode.Id, thisNode); // if we are initializing from the database source ensure the local db is updated From cacd4716018febae1021b350e1534b26b89d9bfa Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 13 Sep 2021 13:03:29 +0200 Subject: [PATCH 4/9] Updating Angular and jQuery to the latest available version --- src/Umbraco.Web.UI.Client/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 9cf6cd850b..35f2d65890 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "ace-builds": "1.4.2", - "angular": "1.8.0", + "angular": "1.8.2", "angular-animate": "1.7.5", "angular-aria": "1.7.9", "angular-chart.js": "^1.1.1", @@ -35,7 +35,7 @@ "diff": "3.5.0", "flatpickr": "4.6.9", "font-awesome": "4.7.0", - "jquery": "^3.5.1", + "jquery": "^3.6.0", "jquery-ui-dist": "1.12.1", "jquery-ui-touch-punch": "0.2.3", "lazyload-js": "1.0.0", From 5fadb238ee14b8e848c88a3da0bfac0419a2d5ad Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Mon, 13 Sep 2021 21:09:47 +1000 Subject: [PATCH 5/9] Don't eagerly acquire distributed (SQL) locks (#10171) --- src/Umbraco.Core/Collections/StackQueue.cs | 60 +++ src/Umbraco.Core/ConfigsExtensions.cs | 6 +- src/Umbraco.Core/Configuration/CoreDebug.cs | 2 +- src/Umbraco.Core/Configuration/ICoreDebug.cs | 8 + src/Umbraco.Core/Scoping/Scope.cs | 373 +++++++++++++++--- src/Umbraco.Core/Scoping/ScopeProvider.cs | 11 +- src/Umbraco.Core/Umbraco.Core.csproj | 2 + .../Collections/StackQueueTests.cs | 74 ++++ .../Components/ComponentTests.cs | 3 +- src/Umbraco.Tests/Persistence/LocksTests.cs | 68 +++- .../Persistence/UnitOfWorkTests.cs | 17 +- src/Umbraco.Tests/Scoping/ScopeUnitTests.cs | 330 +++++++++------- src/Umbraco.Tests/TestHelpers/TestObjects.cs | 2 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 14 files changed, 712 insertions(+), 245 deletions(-) create mode 100644 src/Umbraco.Core/Collections/StackQueue.cs create mode 100644 src/Umbraco.Core/Configuration/ICoreDebug.cs create mode 100644 src/Umbraco.Tests/Collections/StackQueueTests.cs diff --git a/src/Umbraco.Core/Collections/StackQueue.cs b/src/Umbraco.Core/Collections/StackQueue.cs new file mode 100644 index 0000000000..9bf9c365f0 --- /dev/null +++ b/src/Umbraco.Core/Collections/StackQueue.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Collections +{ + /// + /// Collection that can be both a queue and a stack. + /// + /// + public class StackQueue + { + private readonly LinkedList _linkedList = new LinkedList(); + + public void Clear() + { + _linkedList.Clear(); + } + + public void Push(T obj) + { + _linkedList.AddFirst(obj); + } + + public void Enqueue(T obj) + { + _linkedList.AddFirst(obj); + } + + public T Pop() + { + var obj = _linkedList.First.Value; + _linkedList.RemoveFirst(); + return obj; + } + + public T Dequeue() + { + var obj = _linkedList.Last.Value; + _linkedList.RemoveLast(); + return obj; + } + + public T PeekStack() + { + return _linkedList.First.Value; + } + + public T PeekQueue() + { + return _linkedList.Last.Value; + } + + public int Count + { + get + { + return _linkedList.Count; + } + } + } +} diff --git a/src/Umbraco.Core/ConfigsExtensions.cs b/src/Umbraco.Core/ConfigsExtensions.cs index 10594fc970..25c69899c0 100644 --- a/src/Umbraco.Core/ConfigsExtensions.cs +++ b/src/Umbraco.Core/ConfigsExtensions.cs @@ -28,8 +28,8 @@ namespace Umbraco.Core public static IGridConfig Grids(this Configs configs) => configs.GetConfig(); - internal static CoreDebug CoreDebug(this Configs configs) - => configs.GetConfig(); + public static ICoreDebug CoreDebug(this Configs configs) + => configs.GetConfig(); public static void AddCoreConfigs(this Configs configs) { @@ -39,7 +39,7 @@ namespace Umbraco.Core configs.Add("umbracoConfiguration/settings"); configs.Add("umbracoConfiguration/HealthChecks"); - configs.Add(() => new CoreDebug()); + configs.Add(() => new CoreDebug()); // GridConfig depends on runtime caches, manifest parsers... and cannot be available during composition configs.Add(factory => new GridConfig( diff --git a/src/Umbraco.Core/Configuration/CoreDebug.cs b/src/Umbraco.Core/Configuration/CoreDebug.cs index b24e8a3329..c9f4f81417 100644 --- a/src/Umbraco.Core/Configuration/CoreDebug.cs +++ b/src/Umbraco.Core/Configuration/CoreDebug.cs @@ -2,7 +2,7 @@ namespace Umbraco.Core.Configuration { - internal class CoreDebug + internal class CoreDebug : ICoreDebug { public CoreDebug() { diff --git a/src/Umbraco.Core/Configuration/ICoreDebug.cs b/src/Umbraco.Core/Configuration/ICoreDebug.cs new file mode 100644 index 0000000000..2ddb066f26 --- /dev/null +++ b/src/Umbraco.Core/Configuration/ICoreDebug.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Configuration +{ + public interface ICoreDebug + { + bool DumpOnTimeoutThreadAbort { get; } + bool LogUncompletedScopes { get; } + } +} diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs index 4c058cbdb7..c5e59b4a7c 100644 --- a/src/Umbraco.Core/Scoping/Scope.cs +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; using System.Data; +using System.Linq; using System.Text; using Umbraco.Core.Cache; +using Umbraco.Core.Collections; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -18,9 +21,15 @@ namespace Umbraco.Core.Scoping /// Not thread-safe obviously. internal class Scope : IScope2 { + private enum LockType + { + ReadLock, + WriteLock + } + private readonly ScopeProvider _scopeProvider; private readonly ILogger _logger; - + private readonly ICoreDebug _coreDebug; private readonly IsolationLevel _isolationLevel; private readonly RepositoryCacheMode _repositoryCacheMode; private readonly bool? _scopeFileSystem; @@ -37,14 +46,20 @@ namespace Umbraco.Core.Scoping private IEventDispatcher _eventDispatcher; private object _dictionaryLocker; + private readonly object _lockQueueLocker = new object(); + + // This is all used to safely track read/write locks at given Scope levels so that + // when we dispose we can verify that everything has been cleaned up correctly. private HashSet _readLocks; private HashSet _writeLocks; - internal Dictionary> ReadLocks; - internal Dictionary> WriteLocks; + private Dictionary> _readLocksDictionary; + private Dictionary> _writeLocksDictionary; + + private StackQueue<(LockType lockType, TimeSpan timeout, Guid instanceId, int lockId)> _queuedLocks; // initializes a new scope private Scope(ScopeProvider scopeProvider, - ILogger logger, FileSystems fileSystems, Scope parent, ScopeContext scopeContext, bool detachable, + ILogger logger, FileSystems fileSystems, Scope parent, ScopeContext scopeContext, bool detachable, ICoreDebug coreDebug, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IEventDispatcher eventDispatcher = null, @@ -54,7 +69,6 @@ namespace Umbraco.Core.Scoping { _scopeProvider = scopeProvider; _logger = logger; - Context = scopeContext; _isolationLevel = isolationLevel; @@ -63,9 +77,8 @@ namespace Umbraco.Core.Scoping _scopeFileSystem = scopeFileSystems; _callContext = callContext; _autoComplete = autoComplete; - Detachable = detachable; - + _coreDebug = coreDebug; _dictionaryLocker = new object(); #if DEBUG_SCOPES @@ -120,31 +133,77 @@ namespace Umbraco.Core.Scoping // initializes a new scope public Scope(ScopeProvider scopeProvider, - ILogger logger, FileSystems fileSystems, bool detachable, ScopeContext scopeContext, + ILogger logger, FileSystems fileSystems, bool detachable, ScopeContext scopeContext, ICoreDebug coreDebug, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, logger, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) + : this(scopeProvider, logger, fileSystems, null, scopeContext, detachable, coreDebug, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) { } // initializes a new scope in a nested scopes chain, with its parent public Scope(ScopeProvider scopeProvider, - ILogger logger, FileSystems fileSystems, Scope parent, + ILogger logger, FileSystems fileSystems, Scope parent, ICoreDebug coreDebug, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, logger, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) + : this(scopeProvider, logger, fileSystems, parent, null, false, coreDebug, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) { } + /// + /// Used for testing. Ensures and gets any queued read locks. + /// + /// + internal Dictionary> GetReadLocks() + { + EnsureDbLocks(); + // always delegate to root/parent scope. + if (ParentScope is not null) + { + return ParentScope.GetReadLocks(); + } + else + { + return _readLocksDictionary; + } + } + + /// + /// Used for testing. Ensures and gets and queued write locks. + /// + /// + internal Dictionary> GetWriteLocks() + { + EnsureDbLocks(); + // always delegate to root/parent scope. + if (ParentScope is not null) + { + return ParentScope.GetWriteLocks(); + } + else + { + return _writeLocksDictionary; + } + } + public Guid InstanceId { get; } = Guid.NewGuid(); - public ISqlContext SqlContext => _scopeProvider.SqlContext; + public ISqlContext SqlContext + { + get + { + if (_scopeProvider.SqlContext == null) + { + throw new InvalidOperationException($"The {nameof(_scopeProvider.SqlContext)} on the scope provider is null"); + } + return _scopeProvider.SqlContext; + } + } // a value indicating whether to force call-context public bool CallContext @@ -214,7 +273,7 @@ namespace Umbraco.Core.Scoping { if (_isolationLevel != IsolationLevel.Unspecified) return _isolationLevel; if (ParentScope != null) return ParentScope.IsolationLevel; - return Database.SqlContext.SqlSyntax.DefaultIsolationLevel; + return SqlContext.SqlSyntax.DefaultIsolationLevel; } } @@ -226,7 +285,24 @@ namespace Umbraco.Core.Scoping EnsureNotDisposed(); if (_database != null) + { + // If the database has already been resolved, we are already in a + // transaction, but it's possible that more locks have been requested + // so acquire them. + + // TODO: This is the issue we face with non-eager locks. If locks are + // requested after the Database property is resolved, those locks may + // not get executed because the developer may be using their local Database variable + // instead of accessing via scope.Database. + // In our case within Umbraco, I don't think this ever occurs, all locks are requested + // up-front, however, that might not be the case for others. + // The other way to deal with this would be to bake this callback somehow into the + // UmbracoDatabase instance directly and ensure it's called when OnExecutingCommand + // (so long as the executing command isn't a lock command itself!) + // If we could do that, that would be the ultimate lazy executed locks. + EnsureDbLocks(); return _database; + } if (ParentScope != null) { @@ -244,6 +320,7 @@ namespace Umbraco.Core.Scoping try { _database.BeginTransaction(IsolationLevel); + EnsureDbLocks(); return _database; } catch @@ -260,7 +337,16 @@ namespace Umbraco.Core.Scoping get { EnsureNotDisposed(); - return ParentScope == null ? _database : ParentScope.DatabaseOrNull; + if (ParentScope == null) + { + if (_database != null) + { + EnsureDbLocks(); + } + return _database; + } + + return ParentScope.DatabaseOrNull; } } @@ -320,13 +406,92 @@ namespace Umbraco.Core.Scoping // if child did not complete we cannot complete if (completed.HasValue == false || completed.Value == false) { - if (LogUncompletedScopes) + if (_coreDebug.LogUncompletedScopes) + { _logger.Debug("Uncompleted Child Scope at\r\n {StackTrace}", Environment.StackTrace); + } _completed = false; } } + /// + /// When we require a ReadLock or a WriteLock we don't immediately request these locks from the database, + /// instead we only request them when necessary (lazily). + /// To do this, we queue requests for read/write locks. + /// This is so that if there's a request for either of these + /// locks, but the service/repository returns an item from the cache, we don't end up making a DB call to make the + /// read/write lock. + /// This executes the queue of requested locks in order in an efficient way lazily whenever the database instance is + /// resolved. + /// + private void EnsureDbLocks() + { + // always delegate to the root parent + if (ParentScope is not null) + { + ParentScope.EnsureDbLocks(); + } + else + { + lock (_lockQueueLocker) + { + if (_queuedLocks?.Count > 0) + { + var currentType = LockType.ReadLock; + var currentTimeout = TimeSpan.Zero; + var currentInstanceId = InstanceId; + var collectedIds = new HashSet(); + + var i = 0; + while (_queuedLocks.Count > 0) + { + var (lockType, timeout, instanceId, lockId) = _queuedLocks.Dequeue(); + + if (i == 0) + { + currentType = lockType; + currentTimeout = timeout; + currentInstanceId = instanceId; + } + else if (lockType != currentType || timeout != currentTimeout || instanceId != currentInstanceId) + { + // the lock type, instanceId or timeout switched. + // process the lock ids collected + switch (currentType) + { + case LockType.ReadLock: + EagerReadLockInner(_database, currentInstanceId, currentTimeout == TimeSpan.Zero ? null : currentTimeout, collectedIds.ToArray()); + break; + case LockType.WriteLock: + EagerWriteLockInner(_database, currentInstanceId, currentTimeout == TimeSpan.Zero ? null : currentTimeout, collectedIds.ToArray()); + break; + } + // clear the collected and set new type + collectedIds.Clear(); + currentType = lockType; + currentTimeout = timeout; + currentInstanceId = instanceId; + } + collectedIds.Add(lockId); + i++; + } + + // process the remaining + switch (currentType) + { + case LockType.ReadLock: + EagerReadLockInner(_database, currentInstanceId, currentTimeout == TimeSpan.Zero ? null : currentTimeout, collectedIds.ToArray()); + break; + case LockType.WriteLock: + EagerWriteLockInner(_database, currentInstanceId, currentTimeout == TimeSpan.Zero ? null : currentTimeout, collectedIds.ToArray()); + break; + } + } + } + } + } + private void EnsureNotDisposed() { if (_disposed) @@ -366,7 +531,7 @@ namespace Umbraco.Core.Scoping { // We're the parent scope, make sure that locks of all scopes has been cleared // Since we're only reading we don't have to be in a lock - if (ReadLocks?.Count > 0 || WriteLocks?.Count > 0) + if (_readLocksDictionary?.Count > 0 || _writeLocksDictionary?.Count > 0) { var exception = new InvalidOperationException($"All scopes has not been disposed from parent scope: {InstanceId}, see log for more details."); _logger.Error(exception, GenerateUnclearedScopesLogMessage()); @@ -389,6 +554,11 @@ namespace Umbraco.Core.Scoping else DisposeLastScope(); + lock (_lockQueueLocker) + { + _queuedLocks?.Clear(); + } + _disposed = true; GC.SuppressFinalize(this); } @@ -402,8 +572,8 @@ namespace Umbraco.Core.Scoping // Dump the dicts into a message for the locks. StringBuilder builder = new StringBuilder(); builder.AppendLine($"Lock counters aren't empty, suggesting a scope hasn't been properly disposed, parent id: {InstanceId}"); - WriteLockDictionaryToString(ReadLocks, builder, "read locks"); - WriteLockDictionaryToString(WriteLocks, builder, "write locks"); + WriteLockDictionaryToString(_readLocksDictionary, builder, "read locks"); + WriteLockDictionaryToString(_writeLocksDictionary, builder, "write locks"); return builder.ToString(); } @@ -478,7 +648,7 @@ namespace Umbraco.Core.Scoping // to ensure we don't leave a scope around, etc private void RobustExit(bool completed, bool onException) { - if (onException) completed = false; + if (onException) completed = false; TryFinally(() => { @@ -540,14 +710,6 @@ namespace Umbraco.Core.Scoping } } - // backing field for LogUncompletedScopes - private static bool? _logUncompletedScopes; - - // caching config - // true if Umbraco.CoreDebug.LogUncompletedScope appSetting is set to "true" - private static bool LogUncompletedScopes => (_logUncompletedScopes - ?? (_logUncompletedScopes = Current.Configs.CoreDebug().LogUncompletedScopes)).Value; - /// /// Increment the counter of a locks dictionary, either ReadLocks or WriteLocks, /// for a specific scope instance and lock identifier. Must be called within a lock. @@ -590,23 +752,122 @@ namespace Umbraco.Core.Scoping { lock (_dictionaryLocker) { - ReadLocks?.Remove(instanceId); - WriteLocks?.Remove(instanceId); + _readLocksDictionary?.Remove(instanceId); + _writeLocksDictionary?.Remove(instanceId); + + // remove any queued locks for this instance that weren't used. + while (_queuedLocks?.Count > 0) + { + // It's safe to assume that the locks on the top of the stack belong to this instance, + // since any child scopes that might have added locks to the stack must be disposed before we try and dispose this instance. + var top = _queuedLocks.PeekStack(); + if (top.instanceId == instanceId) + { + _queuedLocks.Pop(); + } + else + { + break; + } + } } } } - /// - public void ReadLock(params int[] lockIds) => ReadLockInner(InstanceId, null, lockIds); + public void EagerReadLock(params int[] lockIds) => EagerReadLockInner(Database, InstanceId, null, lockIds); /// - public void ReadLock(TimeSpan timeout, int lockId) => ReadLockInner(InstanceId, timeout, lockId); + public void ReadLock(params int[] lockIds) => LazyReadLockInner(InstanceId, lockIds); + + public void EagerReadLock(TimeSpan timeout, int lockId) => EagerReadLockInner(Database, InstanceId, timeout, lockId); /// - public void WriteLock(params int[] lockIds) => WriteLockInner(InstanceId, null, lockIds); + public void ReadLock(TimeSpan timeout, int lockId) => LazyReadLockInner(InstanceId, timeout, lockId); + + public void EagerWriteLock(params int[] lockIds) => EagerWriteLockInner(Database, InstanceId, null, lockIds); /// - public void WriteLock(TimeSpan timeout, int lockId) => WriteLockInner(InstanceId, timeout, lockId); + public void WriteLock(params int[] lockIds) => LazyWriteLockInner(InstanceId, lockIds); + + public void EagerWriteLock(TimeSpan timeout, int lockId) => EagerWriteLockInner(Database, InstanceId, timeout, lockId); + + /// + public void WriteLock(TimeSpan timeout, int lockId) => LazyWriteLockInner(InstanceId, timeout, lockId); + + public void LazyReadLockInner(Guid instanceId, params int[] lockIds) + { + if (ParentScope != null) + { + ParentScope.LazyReadLockInner(instanceId, lockIds); + } + else + { + LazyLockInner(LockType.ReadLock, instanceId, lockIds); + } + } + + public void LazyReadLockInner(Guid instanceId, TimeSpan timeout, int lockId) + { + if (ParentScope != null) + { + ParentScope.LazyReadLockInner(instanceId, timeout, lockId); + } + else + { + LazyLockInner(LockType.ReadLock, instanceId, timeout, lockId); + } + } + + public void LazyWriteLockInner(Guid instanceId, params int[] lockIds) + { + if (ParentScope != null) + { + ParentScope.LazyWriteLockInner(instanceId, lockIds); + } + else + { + LazyLockInner(LockType.WriteLock, instanceId, lockIds); + } + } + + public void LazyWriteLockInner(Guid instanceId, TimeSpan timeout, int lockId) + { + if (ParentScope != null) + { + ParentScope.LazyWriteLockInner(instanceId, timeout, lockId); + } + else + { + LazyLockInner(LockType.WriteLock, instanceId, timeout, lockId); + } + } + + private void LazyLockInner(LockType lockType, Guid instanceId, params int[] lockIds) + { + lock (_lockQueueLocker) + { + if (_queuedLocks == null) + { + _queuedLocks = new StackQueue<(LockType, TimeSpan, Guid, int)>(); + } + foreach (var lockId in lockIds) + { + _queuedLocks.Enqueue((lockType, TimeSpan.Zero, instanceId, lockId)); + } + } + } + + private void LazyLockInner(LockType lockType, Guid instanceId, TimeSpan timeout, int lockId) + { + lock (_lockQueueLocker) + { + if (_queuedLocks == null) + { + _queuedLocks = new StackQueue<(LockType, TimeSpan, Guid, int)>(); + } + _queuedLocks.Enqueue((lockType, timeout, instanceId, lockId)); + } + } /// /// Handles acquiring a read lock, will delegate it to the parent if there are any. @@ -614,17 +875,17 @@ namespace Umbraco.Core.Scoping /// Instance ID of the requesting scope. /// Optional database timeout in milliseconds. /// Array of lock object identifiers. - private void ReadLockInner(Guid instanceId, TimeSpan? timeout = null, params int[] lockIds) + private void EagerReadLockInner(IUmbracoDatabase db, Guid instanceId, TimeSpan? timeout = null, params int[] lockIds) { if (ParentScope is not null) { // If we have a parent we delegate lock creation to parent. - ParentScope.ReadLockInner(instanceId, timeout, lockIds); + ParentScope.EagerReadLockInner(db, instanceId, timeout, lockIds); } else { // We are the outermost scope, handle the lock request. - LockInner(instanceId, ref ReadLocks, ref _readLocks, ObtainReadLock, ObtainTimeoutReadLock, timeout, lockIds); + LockInner(db, instanceId, ref _readLocksDictionary, ref _readLocks, ObtainReadLock, ObtainTimeoutReadLock, timeout, lockIds); } } @@ -634,17 +895,17 @@ namespace Umbraco.Core.Scoping /// Instance ID of the requesting scope. /// Optional database timeout in milliseconds. /// Array of lock object identifiers. - private void WriteLockInner(Guid instanceId, TimeSpan? timeout = null, params int[] lockIds) + private void EagerWriteLockInner(IUmbracoDatabase db, Guid instanceId, TimeSpan? timeout = null, params int[] lockIds) { if (ParentScope is not null) { // If we have a parent we delegate lock creation to parent. - ParentScope.WriteLockInner(instanceId, timeout, lockIds); + ParentScope.EagerWriteLockInner(db, instanceId, timeout, lockIds); } else { // We are the outermost scope, handle the lock request. - LockInner(instanceId, ref WriteLocks, ref _writeLocks, ObtainWriteLock, ObtainTimeoutWriteLock, timeout, lockIds); + LockInner(db, instanceId, ref _writeLocksDictionary, ref _writeLocks, ObtainWriteLock, ObtainTimeoutWriteLock, timeout, lockIds); } } @@ -658,8 +919,8 @@ namespace Umbraco.Core.Scoping /// Delegate used to request the lock from the database with a timeout. /// Optional timeout parameter to specify a timeout. /// Lock identifiers to lock on. - private void LockInner(Guid instanceId, ref Dictionary> locks, ref HashSet locksSet, - Action obtainLock, Action obtainLockTimeout, TimeSpan? timeout = null, + private void LockInner(IUmbracoDatabase db, Guid instanceId, ref Dictionary> locks, ref HashSet locksSet, + Action obtainLock, Action obtainLockTimeout, TimeSpan? timeout = null, params int[] lockIds) { lock (_dictionaryLocker) @@ -677,12 +938,12 @@ namespace Umbraco.Core.Scoping if (timeout is null) { // We just want an ordinary lock. - obtainLock(lockId); + obtainLock(db, lockId); } else { // We want a lock with a custom timeout - obtainLockTimeout(lockId, timeout.Value); + obtainLockTimeout(db, lockId, timeout.Value); } } catch @@ -708,9 +969,9 @@ namespace Umbraco.Core.Scoping /// Obtains an ordinary read lock. /// /// Lock object identifier to lock. - private void ObtainReadLock(int lockId) + private void ObtainReadLock(IUmbracoDatabase db, int lockId) { - Database.SqlContext.SqlSyntax.ReadLock(Database, lockId); + SqlContext.SqlSyntax.ReadLock(db, lockId); } /// @@ -718,24 +979,24 @@ namespace Umbraco.Core.Scoping /// /// Lock object identifier to lock. /// TimeSpan specifying the timout period. - private void ObtainTimeoutReadLock(int lockId, TimeSpan timeout) + private void ObtainTimeoutReadLock(IUmbracoDatabase db, int lockId, TimeSpan timeout) { - var syntax2 = Database.SqlContext.SqlSyntax as ISqlSyntaxProvider2; + var syntax2 = SqlContext.SqlSyntax as ISqlSyntaxProvider2; if (syntax2 is null) { - throw new InvalidOperationException($"{Database.SqlContext.SqlSyntax.GetType()} is not of type {typeof(ISqlSyntaxProvider2)}"); + throw new InvalidOperationException($"{SqlContext.SqlSyntax.GetType()} is not of type {typeof(ISqlSyntaxProvider2)}"); } - syntax2.ReadLock(Database, timeout, lockId); + syntax2.ReadLock(db, timeout, lockId); } /// /// Obtains an ordinary write lock. /// /// Lock object identifier to lock. - private void ObtainWriteLock(int lockId) + private void ObtainWriteLock(IUmbracoDatabase db, int lockId) { - Database.SqlContext.SqlSyntax.WriteLock(Database, lockId); + SqlContext.SqlSyntax.WriteLock(db, lockId); } /// @@ -743,15 +1004,15 @@ namespace Umbraco.Core.Scoping /// /// Lock object identifier to lock. /// TimeSpan specifying the timout period. - private void ObtainTimeoutWriteLock(int lockId, TimeSpan timeout) + private void ObtainTimeoutWriteLock(IUmbracoDatabase db, int lockId, TimeSpan timeout) { - var syntax2 = Database.SqlContext.SqlSyntax as ISqlSyntaxProvider2; + var syntax2 = SqlContext.SqlSyntax as ISqlSyntaxProvider2; if (syntax2 is null) { - throw new InvalidOperationException($"{Database.SqlContext.SqlSyntax.GetType()} is not of type {typeof(ISqlSyntaxProvider2)}"); + throw new InvalidOperationException($"{SqlContext.SqlSyntax.GetType()} is not of type {typeof(ISqlSyntaxProvider2)}"); } - syntax2.WriteLock(Database, timeout, lockId); + syntax2.WriteLock(db, timeout, lockId); } } } diff --git a/src/Umbraco.Core/Scoping/ScopeProvider.cs b/src/Umbraco.Core/Scoping/ScopeProvider.cs index a1cc128181..c07e89d29f 100644 --- a/src/Umbraco.Core/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/ScopeProvider.cs @@ -6,6 +6,7 @@ using System.Data; using System.Runtime.Remoting.Messaging; using System.Web; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -23,13 +24,15 @@ namespace Umbraco.Core.Scoping internal class ScopeProvider : IScopeProvider, IScopeAccessor { private readonly ILogger _logger; + private readonly ICoreDebug _coreDebug; private readonly FileSystems _fileSystems; - public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, ILogger logger) + public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, ILogger logger, ICoreDebug coreDebug) { DatabaseFactory = databaseFactory; _fileSystems = fileSystems; _logger = logger; + _coreDebug = coreDebug; // take control of the FileSystems _fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems; @@ -327,7 +330,7 @@ namespace Umbraco.Core.Scoping IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null) { - return new Scope(this, _logger, _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); + return new Scope(this, _logger, _fileSystems, true, null, _coreDebug, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); } /// @@ -383,13 +386,13 @@ namespace Umbraco.Core.Scoping { var ambientContext = AmbientContext; var newContext = ambientContext == null ? new ScopeContext() : null; - var scope = new Scope(this, _logger, _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); + var scope = new Scope(this, _logger, _fileSystems, false, newContext, _coreDebug, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); // assign only if scope creation did not throw! SetAmbient(scope, newContext ?? ambientContext); return scope; } - var nested = new Scope(this, _logger, _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); + var nested = new Scope(this, _logger, _fileSystems, ambientScope, _coreDebug, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); SetAmbient(nested, AmbientContext); return nested; } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index f2b57e8c96..7ad209efdf 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -128,6 +128,8 @@ --> + + diff --git a/src/Umbraco.Tests/Collections/StackQueueTests.cs b/src/Umbraco.Tests/Collections/StackQueueTests.cs new file mode 100644 index 0000000000..52d0370f0d --- /dev/null +++ b/src/Umbraco.Tests/Collections/StackQueueTests.cs @@ -0,0 +1,74 @@ +using NUnit.Framework; +using Umbraco.Core.Collections; + +namespace Umbraco.Tests.Collections +{ + [TestFixture] + public class StackQueueTests + { + [Test] + public void Queue() + { + var sq = new StackQueue(); + for (int i = 0; i < 3; i++) + { + sq.Enqueue(i); + } + + var expected = 0; + while(sq.Count > 0) + { + var next = sq.Dequeue(); + Assert.AreEqual(expected, next); + expected++; + } + } + + [Test] + public void Stack() + { + var sq = new StackQueue(); + for (int i = 0; i < 3; i++) + { + sq.Push(i); + } + + var expected = 2; + while (sq.Count > 0) + { + var next = sq.Pop(); + Assert.AreEqual(expected, next); + expected--; + } + } + + [Test] + public void Stack_And_Queue() + { + var sq = new StackQueue(); + for (int i = 0; i < 5; i++) + { + if (i % 2 == 0) + { + sq.Push(i); + } + else + { + sq.Enqueue(i); + } + } + + // 4 (push) + // 3 (enqueue) + // 2 (push) + // 1 (enqueue) + // 0 (push) + + Assert.AreEqual(4, sq.Pop()); + Assert.AreEqual(0, sq.Dequeue()); + Assert.AreEqual(3, sq.Pop()); + Assert.AreEqual(1, sq.Dequeue()); + Assert.AreEqual(2, sq.Pop()); + } + } +} diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 042cac1281..b9a9b7dcb4 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; @@ -30,7 +31,7 @@ namespace Umbraco.Tests.Components var logger = Mock.Of(); var f = new UmbracoDatabaseFactory(logger, new Lazy(() => new MapperCollection(Enumerable.Empty()))); var fs = new FileSystems(mock.Object, logger); - var p = new ScopeProvider(f, fs, logger); + var p = new ScopeProvider(f, fs, logger, Mock.Of(x => x.LogUncompletedScopes == true)); mock.Setup(x => x.GetInstance(typeof (ILogger))).Returns(logger); mock.Setup(x => x.GetInstance(typeof (IProfilingLogger))).Returns(new ProfilingLogger(Mock.Of(), Mock.Of())); diff --git a/src/Umbraco.Tests/Persistence/LocksTests.cs b/src/Umbraco.Tests/Persistence/LocksTests.cs index 8872329284..d0837f5e62 100644 --- a/src/Umbraco.Tests/Persistence/LocksTests.cs +++ b/src/Umbraco.Tests/Persistence/LocksTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using NPoco; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; @@ -36,9 +37,9 @@ namespace Umbraco.Tests.Persistence [Test] public void SingleReadLockTest() { - using (var scope = ScopeProvider.CreateScope()) + using (var scope = (Scope)ScopeProvider.CreateScope()) { - scope.ReadLock(Constants.Locks.Servers); + scope.EagerReadLock(Constants.Locks.Servers); scope.Complete(); } } @@ -59,11 +60,11 @@ namespace Umbraco.Tests.Persistence var ic = i; // capture threads[i] = new Thread(() => { - using (var scope = ScopeProvider.CreateScope()) + using (var scope = (Scope)ScopeProvider.CreateScope()) { try { - scope.ReadLock(Constants.Locks.Servers); + scope.EagerReadLock(Constants.Locks.Servers); lock (locker) { acquired++; @@ -104,6 +105,35 @@ namespace Umbraco.Tests.Persistence Assert.IsNull(exceptions[i]); } + [Test] + public void GivenNonEagerLocking_WhenNoDbIsAccessed_ThenNoSqlIsExecuted() + { + var sqlCount = 0; + + using (var scope = (Scope)ScopeProvider.CreateScope()) + { + var db = (UmbracoDatabase)scope.Database; + try + { + db.EnableSqlCount = true; + + // Issue a lock request, but we are using non-eager + // locks so this only queues the request. + // The lock will not be issued unless we resolve + // scope.Database + scope.WriteLock(Constants.Locks.Servers); + + sqlCount = db.SqlCount; + } + finally + { + db.EnableSqlCount = false; + } + } + + Assert.AreEqual(0, sqlCount); + } + [Test] public void ConcurrentWritersTest() { @@ -122,7 +152,7 @@ namespace Umbraco.Tests.Persistence var ic = i; // capture threads[i] = new Thread(() => { - using (var scope = ScopeProvider.CreateScope()) + using (var scope = (Scope)ScopeProvider.CreateScope()) { try { @@ -132,7 +162,7 @@ namespace Umbraco.Tests.Persistence if (entered == threadCount) m1.Set(); } ms[ic].WaitOne(); - scope.WriteLock(Constants.Locks.Servers); + scope.EagerWriteLock(Constants.Locks.Servers); lock (locker) { acquired++; @@ -161,8 +191,10 @@ namespace Umbraco.Tests.Persistence m1.Wait(); // all threads have entered ms[0].Set(); // let 0 go + // TODO: This timing is flaky Thread.Sleep(100); for (var i = 1; i < threadCount; i++) ms[i].Set(); // let others go + // TODO: This timing is flaky Thread.Sleep(500); // only 1 thread has locked Assert.AreEqual(1, acquired); @@ -216,13 +248,13 @@ namespace Umbraco.Tests.Persistence private void DeadLockTestThread(int id1, int id2, EventWaitHandle myEv, WaitHandle otherEv, ref Exception exception) { - using (var scope = ScopeProvider.CreateScope()) + using (var scope = (Scope)ScopeProvider.CreateScope()) { try { otherEv.WaitOne(); Console.WriteLine($"[{id1}] WAIT {id1}"); - scope.WriteLock(id1); + scope.EagerWriteLock(id1); Console.WriteLine($"[{id1}] GRANT {id1}"); WriteLocks(scope.Database); myEv.Set(); @@ -233,7 +265,7 @@ namespace Umbraco.Tests.Persistence Thread.Sleep(200); // cannot wait due to deadlock... just give it a bit of time Console.WriteLine($"[{id1}] WAIT {id2}"); - scope.WriteLock(id2); + scope.EagerWriteLock(id2); Console.WriteLine($"[{id1}] GRANT {id2}"); WriteLocks(scope.Database); } @@ -288,7 +320,7 @@ namespace Umbraco.Tests.Persistence Console.WriteLine("Write lock A"); // This will acquire right away - realScope.WriteLock(TimeSpan.FromMilliseconds(2000), Constants.Locks.ContentTree); + realScope.EagerWriteLock(TimeSpan.FromMilliseconds(2000), Constants.Locks.ContentTree); Thread.Sleep(6000); // Wait longer than the Read Lock B timeout scope.Complete(); Console.WriteLine("Finished Write lock A"); @@ -308,7 +340,7 @@ namespace Umbraco.Tests.Persistence // This will wait for the write lock to release but it isn't going to wait long // enough so an exception will be thrown. Assert.Throws(() => - realScope.ReadLock(TimeSpan.FromMilliseconds(3000), Constants.Locks.ContentTree)); + realScope.EagerReadLock(TimeSpan.FromMilliseconds(3000), Constants.Locks.ContentTree)); scope.Complete(); Console.WriteLine("Finished Read lock B"); @@ -326,7 +358,7 @@ namespace Umbraco.Tests.Persistence // This will wait for the write lock to release but it isn't going to wait long // enough so an exception will be thrown. Assert.Throws(() => - realScope.WriteLock(TimeSpan.FromMilliseconds(3000), Constants.Locks.ContentTree)); + realScope.EagerWriteLock(TimeSpan.FromMilliseconds(3000), Constants.Locks.ContentTree)); scope.Complete(); Console.WriteLine("Finished Write lock C"); @@ -349,7 +381,7 @@ namespace Umbraco.Tests.Persistence Console.WriteLine("Write lock A"); // This will acquire right away - realScope.WriteLock(TimeSpan.FromMilliseconds(2000), Constants.Locks.ContentTree); + realScope.EagerWriteLock(TimeSpan.FromMilliseconds(2000), Constants.Locks.ContentTree); Thread.Sleep(4000); // Wait less than the Read Lock B timeout scope.Complete(); Interlocked.Increment(ref locksCompleted); @@ -369,7 +401,7 @@ namespace Umbraco.Tests.Persistence // This will wait for the write lock to release Assert.DoesNotThrow(() => - realScope.ReadLock(TimeSpan.FromMilliseconds(6000), Constants.Locks.ContentTree)); + realScope.EagerReadLock(TimeSpan.FromMilliseconds(6000), Constants.Locks.ContentTree)); Assert.GreaterOrEqual(locksCompleted, 1); @@ -389,7 +421,7 @@ namespace Umbraco.Tests.Persistence // This will wait for the write lock to release Assert.DoesNotThrow(() => - realScope.ReadLock(TimeSpan.FromMilliseconds(6000), Constants.Locks.ContentTree)); + realScope.EagerReadLock(TimeSpan.FromMilliseconds(6000), Constants.Locks.ContentTree)); Assert.GreaterOrEqual(locksCompleted, 1); @@ -417,7 +449,7 @@ namespace Umbraco.Tests.Persistence Console.WriteLine("Write lock A"); // TODO: In theory this would throw - realScope.WriteLock(TimeSpan.FromMilliseconds(3000), Constants.Locks.ContentTree); + realScope.EagerWriteLock(TimeSpan.FromMilliseconds(3000), Constants.Locks.ContentTree); scope.Complete(); Console.WriteLine("Finished Write lock A"); } @@ -425,13 +457,13 @@ namespace Umbraco.Tests.Persistence private void NoDeadLockTestThread(int id, EventWaitHandle myEv, WaitHandle otherEv, ref Exception exception) { - using (var scope = ScopeProvider.CreateScope()) + using (var scope = (Scope)ScopeProvider.CreateScope()) { try { otherEv.WaitOne(); Console.WriteLine($"[{id}] WAIT {id}"); - scope.WriteLock(id); + scope.EagerWriteLock(id); Console.WriteLine($"[{id}] GRANT {id}"); WriteLocks(scope.Database); myEv.Set(); diff --git a/src/Umbraco.Tests/Persistence/UnitOfWorkTests.cs b/src/Umbraco.Tests/Persistence/UnitOfWorkTests.cs index 02ad6b3971..2b60807088 100644 --- a/src/Umbraco.Tests/Persistence/UnitOfWorkTests.cs +++ b/src/Umbraco.Tests/Persistence/UnitOfWorkTests.cs @@ -1,6 +1,7 @@ using System; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -16,9 +17,9 @@ namespace Umbraco.Tests.Persistence var provider = TestObjects.GetScopeProvider(Logger); Assert.Throws(() => { - using (var scope = provider.CreateScope()) + using (var scope = (Scope)provider.CreateScope()) { - scope.ReadLock(-666); + scope.EagerReadLock(-666); scope.Complete(); } }); @@ -28,9 +29,9 @@ namespace Umbraco.Tests.Persistence public void ReadLockExisting() { var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = (Scope)provider.CreateScope()) { - scope.ReadLock(Constants.Locks.Servers); + scope.EagerReadLock(Constants.Locks.Servers); scope.Complete(); } } @@ -41,9 +42,9 @@ namespace Umbraco.Tests.Persistence var provider = TestObjects.GetScopeProvider(Logger); Assert.Throws(() => { - using (var scope = provider.CreateScope()) + using (var scope = (Scope)provider.CreateScope()) { - scope.WriteLock(-666); + scope.EagerWriteLock(-666); scope.Complete(); } }); @@ -53,9 +54,9 @@ namespace Umbraco.Tests.Persistence public void WriteLockExisting() { var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = (Scope)provider.CreateScope()) { - scope.WriteLock(Constants.Locks.Servers); + scope.EagerWriteLock(Constants.Locks.Servers); scope.Complete(); } } diff --git a/src/Umbraco.Tests/Scoping/ScopeUnitTests.cs b/src/Umbraco.Tests/Scoping/ScopeUnitTests.cs index 038376f71c..a776a5bae6 100644 --- a/src/Umbraco.Tests/Scoping/ScopeUnitTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeUnitTests.cs @@ -5,6 +5,7 @@ using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; @@ -34,6 +35,7 @@ namespace Umbraco.Tests.Scoping // Setup mock of database factory to return mock of database. databaseFactory.Setup(x => x.CreateDatabase()).Returns(database.Object); + databaseFactory.Setup(x => x.SqlContext).Returns(sqlContext.Object); // Setup mock of database to return mock of sql SqlContext database.Setup(x => x.SqlContext).Returns(sqlContext.Object); @@ -41,7 +43,40 @@ namespace Umbraco.Tests.Scoping // Setup mock of ISqlContext to return syntaxProviderMock sqlContext.Setup(x => x.SqlSyntax).Returns(syntaxProviderMock.Object); - return new ScopeProvider(databaseFactory.Object, fileSystem, logger); + return new ScopeProvider(databaseFactory.Object, fileSystem, logger, Mock.Of(x => x.LogUncompletedScopes == true)); + } + + [Test] + public void Unused_Lazy_Locks_Cleared_At_Child_Scope() + { + var scopeProvider = GetScopeProvider(out var syntaxProviderMock); + + var outerScope = (Scope)scopeProvider.CreateScope(); + outerScope.ReadLock(Constants.Locks.Domains); + outerScope.ReadLock(Constants.Locks.Languages); + + using (var innerScope1 = (Scope)scopeProvider.CreateScope()) + { + innerScope1.ReadLock(Constants.Locks.Domains); + innerScope1.ReadLock(Constants.Locks.Languages); + + innerScope1.Complete(); + } + + using (var innerScope2 = (Scope)scopeProvider.CreateScope()) + { + innerScope2.ReadLock(Constants.Locks.Domains); + innerScope2.ReadLock(Constants.Locks.Languages); + + // force resolving the locks + var locks = innerScope2.GetReadLocks(); + + innerScope2.Complete(); + } + + outerScope.Complete(); + + Assert.DoesNotThrow(() => outerScope.Dispose()); } [Test] @@ -49,20 +84,20 @@ namespace Umbraco.Tests.Scoping { var scopeProvider = GetScopeProvider(out var syntaxProviderMock); - using (var outerScope = scopeProvider.CreateScope()) + using (var outerScope = (Scope)scopeProvider.CreateScope()) { - outerScope.WriteLock(Constants.Locks.Domains); - outerScope.WriteLock(Constants.Locks.Languages); + outerScope.EagerWriteLock(Constants.Locks.Domains); + outerScope.EagerWriteLock(Constants.Locks.Languages); - using (var innerScope1 = scopeProvider.CreateScope()) + using (var innerScope1 = (Scope)scopeProvider.CreateScope()) { - innerScope1.WriteLock(Constants.Locks.Domains); - innerScope1.WriteLock(Constants.Locks.Languages); + innerScope1.EagerWriteLock(Constants.Locks.Domains); + innerScope1.EagerWriteLock(Constants.Locks.Languages); - using (var innerScope2 = scopeProvider.CreateScope()) + using (var innerScope2 = (Scope)scopeProvider.CreateScope()) { - innerScope2.WriteLock(Constants.Locks.Domains); - innerScope2.WriteLock(Constants.Locks.Languages); + innerScope2.EagerWriteLock(Constants.Locks.Domains); + innerScope2.EagerWriteLock(Constants.Locks.Languages); innerScope2.Complete(); } innerScope1.Complete(); @@ -79,18 +114,18 @@ namespace Umbraco.Tests.Scoping { var scopeProvider = GetScopeProvider(out var syntaxProviderMock); - using (var outerScope = scopeProvider.CreateScope()) + using (var outerScope = (Scope)scopeProvider.CreateScope()) { - outerScope.WriteLock(Constants.Locks.Languages); + outerScope.EagerWriteLock(Constants.Locks.Languages); - using (var innerScope = scopeProvider.CreateScope()) + using (var innerScope = (Scope)scopeProvider.CreateScope()) { - innerScope.WriteLock(Constants.Locks.Languages); - innerScope.WriteLock(Constants.Locks.ContentTree); + innerScope.EagerWriteLock(Constants.Locks.Languages); + innerScope.EagerWriteLock(Constants.Locks.ContentTree); innerScope.Complete(); } - outerScope.WriteLock(Constants.Locks.ContentTree); + outerScope.EagerWriteLock(Constants.Locks.ContentTree); outerScope.Complete(); } @@ -99,27 +134,25 @@ namespace Umbraco.Tests.Scoping } [Test] - public void WriteLock_With_Timeout_Acquired_Only_Once_Per_Key(){ + public void WriteLock_With_Timeout_Acquired_Only_Once_Per_Key() + { var scopeProvider = GetScopeProvider(out var syntaxProviderMock); var timeout = TimeSpan.FromMilliseconds(10000); - using (var outerScope = scopeProvider.CreateScope()) + using (var outerScope = (Scope)scopeProvider.CreateScope()) { - var realScope = (Scope) outerScope; - realScope.WriteLock(timeout, Constants.Locks.Domains); - realScope.WriteLock(timeout, Constants.Locks.Languages); + outerScope.EagerWriteLock(timeout, Constants.Locks.Domains); + outerScope.EagerWriteLock(timeout, Constants.Locks.Languages); - using (var innerScope1 = scopeProvider.CreateScope()) + using (var innerScope1 = (Scope)scopeProvider.CreateScope()) { - var realInnerScope1 = (Scope) outerScope; - realInnerScope1.WriteLock(timeout, Constants.Locks.Domains); - realInnerScope1.WriteLock(timeout, Constants.Locks.Languages); + innerScope1.EagerWriteLock(timeout, Constants.Locks.Domains); + innerScope1.EagerWriteLock(timeout, Constants.Locks.Languages); - using (var innerScope2 = scopeProvider.CreateScope()) + using (var innerScope2 = (Scope)scopeProvider.CreateScope()) { - var realInnerScope2 = (Scope) innerScope2; - realInnerScope2.WriteLock(timeout, Constants.Locks.Domains); - realInnerScope2.WriteLock(timeout, Constants.Locks.Languages); + innerScope2.EagerWriteLock(timeout, Constants.Locks.Domains); + innerScope2.EagerWriteLock(timeout, Constants.Locks.Languages); innerScope2.Complete(); } innerScope1.Complete(); @@ -137,20 +170,20 @@ namespace Umbraco.Tests.Scoping { var scopeProvider = GetScopeProvider(out var syntaxProviderMock); - using (var outerScope = scopeProvider.CreateScope()) + using (var outerScope = (Scope)scopeProvider.CreateScope()) { - outerScope.ReadLock(Constants.Locks.Domains); - outerScope.ReadLock(Constants.Locks.Languages); + outerScope.EagerReadLock(Constants.Locks.Domains); + outerScope.EagerReadLock(Constants.Locks.Languages); - using (var innerScope1 = scopeProvider.CreateScope()) + using (var innerScope1 = (Scope)scopeProvider.CreateScope()) { - innerScope1.ReadLock(Constants.Locks.Domains); - innerScope1.ReadLock(Constants.Locks.Languages); + innerScope1.EagerReadLock(Constants.Locks.Domains); + innerScope1.EagerReadLock(Constants.Locks.Languages); - using (var innerScope2 = scopeProvider.CreateScope()) + using (var innerScope2 = (Scope)scopeProvider.CreateScope()) { - innerScope2.ReadLock(Constants.Locks.Domains); - innerScope2.ReadLock(Constants.Locks.Languages); + innerScope2.EagerReadLock(Constants.Locks.Domains); + innerScope2.EagerReadLock(Constants.Locks.Languages); innerScope2.Complete(); } @@ -171,23 +204,20 @@ namespace Umbraco.Tests.Scoping var scopeProvider = GetScopeProvider(out var syntaxProviderMock); var timeOut = TimeSpan.FromMilliseconds(10000); - using (var outerScope = scopeProvider.CreateScope()) + using (var outerScope = (Scope)scopeProvider.CreateScope()) { - var realOuterScope = (Scope) outerScope; - realOuterScope.ReadLock(timeOut, Constants.Locks.Domains); - realOuterScope.ReadLock(timeOut, Constants.Locks.Languages); + outerScope.EagerReadLock(timeOut, Constants.Locks.Domains); + outerScope.EagerReadLock(timeOut, Constants.Locks.Languages); - using (var innerScope1 = scopeProvider.CreateScope()) + using (var innerScope1 = (Scope)scopeProvider.CreateScope()) { - var realInnerScope1 = (Scope) innerScope1; - realInnerScope1.ReadLock(timeOut, Constants.Locks.Domains); - realInnerScope1.ReadLock(timeOut, Constants.Locks.Languages); + innerScope1.EagerReadLock(timeOut, Constants.Locks.Domains); + innerScope1.EagerReadLock(timeOut, Constants.Locks.Languages); - using (var innerScope2 = scopeProvider.CreateScope()) + using (var innerScope2 = (Scope)scopeProvider.CreateScope()) { - var realInnerScope2 = (Scope) innerScope2; - realInnerScope2.ReadLock(timeOut, Constants.Locks.Domains); - realInnerScope2.ReadLock(timeOut, Constants.Locks.Languages); + innerScope2.EagerReadLock(timeOut, Constants.Locks.Domains); + innerScope2.EagerReadLock(timeOut, Constants.Locks.Languages); innerScope2.Complete(); } @@ -211,10 +241,10 @@ namespace Umbraco.Tests.Scoping { outerScope.ReadLock(Constants.Locks.Languages); - using (var innerScope = scopeProvider.CreateScope()) + using (var innerScope = (Scope)scopeProvider.CreateScope()) { - innerScope.ReadLock(Constants.Locks.Languages); - innerScope.ReadLock(Constants.Locks.ContentTree); + innerScope.EagerReadLock(Constants.Locks.Languages); + innerScope.EagerReadLock(Constants.Locks.ContentTree); innerScope.Complete(); } @@ -232,28 +262,27 @@ namespace Umbraco.Tests.Scoping var scopeProvider = GetScopeProvider(out var syntaxProviderMock); Guid innerscopeId; - using (var outerscope = scopeProvider.CreateScope()) + using (var outerscope = (Scope)scopeProvider.CreateScope()) { - var realOuterScope = (Scope) outerscope; - outerscope.WriteLock(Constants.Locks.ContentTree); - outerscope.WriteLock(Constants.Locks.ContentTree); - Assert.AreEqual(2, realOuterScope.WriteLocks[outerscope.InstanceId][Constants.Locks.ContentTree]); + outerscope.EagerWriteLock(Constants.Locks.ContentTree); + outerscope.EagerWriteLock(Constants.Locks.ContentTree); + Assert.AreEqual(2, outerscope.GetWriteLocks()[outerscope.InstanceId][Constants.Locks.ContentTree]); - using (var innerScope = scopeProvider.CreateScope()) + using (var innerScope = (Scope)scopeProvider.CreateScope()) { innerscopeId = innerScope.InstanceId; - innerScope.WriteLock(Constants.Locks.ContentTree); - innerScope.WriteLock(Constants.Locks.ContentTree); - Assert.AreEqual(2, realOuterScope.WriteLocks[outerscope.InstanceId][Constants.Locks.ContentTree]); - Assert.AreEqual(2, realOuterScope.WriteLocks[innerscopeId][Constants.Locks.ContentTree]); + innerScope.EagerWriteLock(Constants.Locks.ContentTree); + innerScope.EagerWriteLock(Constants.Locks.ContentTree); + Assert.AreEqual(2, outerscope.GetWriteLocks()[outerscope.InstanceId][Constants.Locks.ContentTree]); + Assert.AreEqual(2, outerscope.GetWriteLocks()[innerscopeId][Constants.Locks.ContentTree]); - innerScope.WriteLock(Constants.Locks.Languages); - innerScope.WriteLock(Constants.Locks.Languages); - Assert.AreEqual(2, realOuterScope.WriteLocks[innerScope.InstanceId][Constants.Locks.Languages]); + innerScope.EagerWriteLock(Constants.Locks.Languages); + innerScope.EagerWriteLock(Constants.Locks.Languages); + Assert.AreEqual(2, outerscope.GetWriteLocks()[innerScope.InstanceId][Constants.Locks.Languages]); innerScope.Complete(); } - Assert.AreEqual(2, realOuterScope.WriteLocks[realOuterScope.InstanceId][Constants.Locks.ContentTree]); - Assert.IsFalse(realOuterScope.WriteLocks.ContainsKey(innerscopeId)); + Assert.AreEqual(2, outerscope.GetWriteLocks()[outerscope.InstanceId][Constants.Locks.ContentTree]); + Assert.IsFalse(outerscope.GetWriteLocks().ContainsKey(innerscopeId)); outerscope.Complete(); } } @@ -264,28 +293,27 @@ namespace Umbraco.Tests.Scoping var scopeProvider = GetScopeProvider(out var syntaxProviderMock); Guid innerscopeId; - using (var outerscope = scopeProvider.CreateScope()) + using (var outerscope = (Scope)scopeProvider.CreateScope()) { - var realOuterScope = (Scope) outerscope; - outerscope.ReadLock(Constants.Locks.ContentTree); - outerscope.ReadLock(Constants.Locks.ContentTree); - Assert.AreEqual(2, realOuterScope.ReadLocks[outerscope.InstanceId][Constants.Locks.ContentTree]); + outerscope.EagerReadLock(Constants.Locks.ContentTree); + outerscope.EagerReadLock(Constants.Locks.ContentTree); + Assert.AreEqual(2, outerscope.GetReadLocks()[outerscope.InstanceId][Constants.Locks.ContentTree]); - using (var innerScope = scopeProvider.CreateScope()) + using (var innerScope = (Scope)scopeProvider.CreateScope()) { innerscopeId = innerScope.InstanceId; - innerScope.ReadLock(Constants.Locks.ContentTree); - innerScope.ReadLock(Constants.Locks.ContentTree); - Assert.AreEqual(2, realOuterScope.ReadLocks[outerscope.InstanceId][Constants.Locks.ContentTree]); - Assert.AreEqual(2, realOuterScope.ReadLocks[innerScope.InstanceId][Constants.Locks.ContentTree]); + innerScope.EagerReadLock(Constants.Locks.ContentTree); + innerScope.EagerReadLock(Constants.Locks.ContentTree); + Assert.AreEqual(2, outerscope.GetReadLocks()[outerscope.InstanceId][Constants.Locks.ContentTree]); + Assert.AreEqual(2, outerscope.GetReadLocks()[innerScope.InstanceId][Constants.Locks.ContentTree]); - innerScope.ReadLock(Constants.Locks.Languages); - innerScope.ReadLock(Constants.Locks.Languages); - Assert.AreEqual(2, realOuterScope.ReadLocks[innerScope.InstanceId][Constants.Locks.Languages]); + innerScope.EagerReadLock(Constants.Locks.Languages); + innerScope.EagerReadLock(Constants.Locks.Languages); + Assert.AreEqual(2, outerscope.GetReadLocks()[innerScope.InstanceId][Constants.Locks.Languages]); innerScope.Complete(); } - Assert.AreEqual(2, realOuterScope.ReadLocks[outerscope.InstanceId][Constants.Locks.ContentTree]); - Assert.IsFalse(realOuterScope.ReadLocks.ContainsKey(innerscopeId)); + Assert.AreEqual(2, outerscope.GetReadLocks()[outerscope.InstanceId][Constants.Locks.ContentTree]); + Assert.IsFalse(outerscope.GetReadLocks().ContainsKey(innerscopeId)); outerscope.Complete(); @@ -300,12 +328,12 @@ namespace Umbraco.Tests.Scoping using (var parentScope = scopeProvider.CreateScope()) { - var realParentScope = (Scope) parentScope; + var realParentScope = (Scope)parentScope; parentScope.WriteLock(Constants.Locks.ContentTree); parentScope.WriteLock(Constants.Locks.ContentTypes); - Assert.AreEqual(1, realParentScope.WriteLocks[realParentScope.InstanceId][Constants.Locks.ContentTree], $"parentScope after locks acquired: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.WriteLocks[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"parentScope after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[realParentScope.InstanceId][Constants.Locks.ContentTree], $"parentScope after locks acquired: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"parentScope after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); using (var innerScope1 = scopeProvider.CreateScope()) { @@ -314,11 +342,11 @@ namespace Umbraco.Tests.Scoping innerScope1.WriteLock(Constants.Locks.ContentTypes); innerScope1.WriteLock(Constants.Locks.Languages); - Assert.AreEqual(1, realParentScope.WriteLocks[realParentScope.InstanceId][Constants.Locks.ContentTree], $"innerScope1, parent instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.WriteLocks[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"innerScope1, parent instance, after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); - Assert.AreEqual(1, realParentScope.WriteLocks[innerScope1.InstanceId][Constants.Locks.ContentTree], $"innerScope1, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.WriteLocks[innerScope1.InstanceId][Constants.Locks.ContentTypes], $"innerScope1, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); - Assert.AreEqual(1, realParentScope.WriteLocks[innerScope1.InstanceId][Constants.Locks.Languages], $"innerScope1, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.Languages)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[realParentScope.InstanceId][Constants.Locks.ContentTree], $"innerScope1, parent instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"innerScope1, parent instance, after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[innerScope1.InstanceId][Constants.Locks.ContentTree], $"innerScope1, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[innerScope1.InstanceId][Constants.Locks.ContentTypes], $"innerScope1, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[innerScope1.InstanceId][Constants.Locks.Languages], $"innerScope1, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.Languages)}"); using (var innerScope2 = scopeProvider.CreateScope()) { @@ -326,29 +354,29 @@ namespace Umbraco.Tests.Scoping innerScope2.WriteLock(Constants.Locks.ContentTree); innerScope2.WriteLock(Constants.Locks.MediaTypes); - Assert.AreEqual(1, realParentScope.WriteLocks[realParentScope.InstanceId][Constants.Locks.ContentTree], $"innerScope2, parent instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.WriteLocks[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"innerScope2, parent instance, after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); - Assert.AreEqual(1, realParentScope.WriteLocks[innerScope1.InstanceId][Constants.Locks.ContentTree], $"innerScope2, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.WriteLocks[innerScope1.InstanceId][Constants.Locks.ContentTypes], $"innerScope2, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); - Assert.AreEqual(1, realParentScope.WriteLocks[innerScope1.InstanceId][Constants.Locks.Languages], $"innerScope2, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.Languages)}"); - Assert.AreEqual(1, realParentScope.WriteLocks[innerScope2.InstanceId][Constants.Locks.ContentTree], $"innerScope2, innerScope2 instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.WriteLocks[innerScope2.InstanceId][Constants.Locks.MediaTypes], $"innerScope2, innerScope2 instance, after locks acquired: {nameof(Constants.Locks.MediaTypes)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[realParentScope.InstanceId][Constants.Locks.ContentTree], $"innerScope2, parent instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"innerScope2, parent instance, after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[innerScope1.InstanceId][Constants.Locks.ContentTree], $"innerScope2, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[innerScope1.InstanceId][Constants.Locks.ContentTypes], $"innerScope2, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[innerScope1.InstanceId][Constants.Locks.Languages], $"innerScope2, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.Languages)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[innerScope2.InstanceId][Constants.Locks.ContentTree], $"innerScope2, innerScope2 instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[innerScope2.InstanceId][Constants.Locks.MediaTypes], $"innerScope2, innerScope2 instance, after locks acquired: {nameof(Constants.Locks.MediaTypes)}"); innerScope2.Complete(); } - Assert.AreEqual(1, realParentScope.WriteLocks[realParentScope.InstanceId][Constants.Locks.ContentTree], $"innerScope1, parent instance, after innserScope2 disposed: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.WriteLocks[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"innerScope1, parent instance, after innserScope2 disposed: {nameof(Constants.Locks.ContentTypes)}"); - Assert.AreEqual(1, realParentScope.WriteLocks[innerScope1.InstanceId][Constants.Locks.ContentTree], $"innerScope1, innerScope1 instance, after innserScope2 disposed: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.WriteLocks[innerScope1.InstanceId][Constants.Locks.ContentTypes], $"innerScope1, innerScope1 instance, after innserScope2 disposed: {nameof(Constants.Locks.ContentTypes)}"); - Assert.AreEqual(1, realParentScope.WriteLocks[innerScope1.InstanceId][Constants.Locks.Languages], $"innerScope1, innerScope1 instance, after innserScope2 disposed: {nameof(Constants.Locks.Languages)}"); - Assert.IsFalse(realParentScope.WriteLocks.ContainsKey(innerScope2Id)); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[realParentScope.InstanceId][Constants.Locks.ContentTree], $"innerScope1, parent instance, after innserScope2 disposed: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"innerScope1, parent instance, after innserScope2 disposed: {nameof(Constants.Locks.ContentTypes)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[innerScope1.InstanceId][Constants.Locks.ContentTree], $"innerScope1, innerScope1 instance, after innserScope2 disposed: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[innerScope1.InstanceId][Constants.Locks.ContentTypes], $"innerScope1, innerScope1 instance, after innserScope2 disposed: {nameof(Constants.Locks.ContentTypes)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[innerScope1.InstanceId][Constants.Locks.Languages], $"innerScope1, innerScope1 instance, after innserScope2 disposed: {nameof(Constants.Locks.Languages)}"); + Assert.IsFalse(realParentScope.GetWriteLocks().ContainsKey(innerScope2Id)); innerScope1.Complete(); } - Assert.AreEqual(1, realParentScope.WriteLocks[realParentScope.InstanceId][Constants.Locks.ContentTree], $"parentScope after inner scopes disposed: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.WriteLocks[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"parentScope after inner scopes disposed: {nameof(Constants.Locks.ContentTypes)}"); - Assert.IsFalse(realParentScope.WriteLocks.ContainsKey(innerScope2Id)); - Assert.IsFalse(realParentScope.WriteLocks.ContainsKey(innerScope1Id)); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[realParentScope.InstanceId][Constants.Locks.ContentTree], $"parentScope after inner scopes disposed: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetWriteLocks()[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"parentScope after inner scopes disposed: {nameof(Constants.Locks.ContentTypes)}"); + Assert.IsFalse(realParentScope.GetWriteLocks().ContainsKey(innerScope2Id)); + Assert.IsFalse(realParentScope.GetWriteLocks().ContainsKey(innerScope1Id)); parentScope.Complete(); } @@ -362,11 +390,11 @@ namespace Umbraco.Tests.Scoping using (var parentScope = scopeProvider.CreateScope()) { - var realParentScope = (Scope) parentScope; + var realParentScope = (Scope)parentScope; parentScope.ReadLock(Constants.Locks.ContentTree); parentScope.ReadLock(Constants.Locks.ContentTypes); - Assert.AreEqual(1, realParentScope.ReadLocks[realParentScope.InstanceId][Constants.Locks.ContentTree], $"parentScope after locks acquired: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.ReadLocks[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"parentScope after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[realParentScope.InstanceId][Constants.Locks.ContentTree], $"parentScope after locks acquired: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"parentScope after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); using (var innserScope1 = scopeProvider.CreateScope()) { @@ -374,42 +402,42 @@ namespace Umbraco.Tests.Scoping innserScope1.ReadLock(Constants.Locks.ContentTree); innserScope1.ReadLock(Constants.Locks.ContentTypes); innserScope1.ReadLock(Constants.Locks.Languages); - Assert.AreEqual(1, realParentScope.ReadLocks[realParentScope.InstanceId][Constants.Locks.ContentTree], $"innerScope1, parent instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.ReadLocks[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"innerScope1, parent instance, after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); - Assert.AreEqual(1, realParentScope.ReadLocks[innserScope1.InstanceId][Constants.Locks.ContentTree], $"innerScope1, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.ReadLocks[innserScope1.InstanceId][Constants.Locks.ContentTypes], $"innerScope1, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); - Assert.AreEqual(1, realParentScope.ReadLocks[innserScope1.InstanceId][Constants.Locks.Languages], $"innerScope1, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.Languages)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[realParentScope.InstanceId][Constants.Locks.ContentTree], $"innerScope1, parent instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"innerScope1, parent instance, after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[innserScope1.InstanceId][Constants.Locks.ContentTree], $"innerScope1, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[innserScope1.InstanceId][Constants.Locks.ContentTypes], $"innerScope1, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[innserScope1.InstanceId][Constants.Locks.Languages], $"innerScope1, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.Languages)}"); using (var innerScope2 = scopeProvider.CreateScope()) { innerScope2Id = innerScope2.InstanceId; innerScope2.ReadLock(Constants.Locks.ContentTree); innerScope2.ReadLock(Constants.Locks.MediaTypes); - Assert.AreEqual(1, realParentScope.ReadLocks[realParentScope.InstanceId][Constants.Locks.ContentTree], $"innerScope2, parent instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.ReadLocks[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"innerScope2, parent instance, after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); - Assert.AreEqual(1, realParentScope.ReadLocks[innserScope1.InstanceId][Constants.Locks.ContentTree], $"innerScope2, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.ReadLocks[innserScope1.InstanceId][Constants.Locks.ContentTypes], $"innerScope2, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); - Assert.AreEqual(1, realParentScope.ReadLocks[innserScope1.InstanceId][Constants.Locks.Languages], $"innerScope2, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.Languages)}"); - Assert.AreEqual(1, realParentScope.ReadLocks[innerScope2.InstanceId][Constants.Locks.ContentTree], $"innerScope2, innerScope2 instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.ReadLocks[innerScope2.InstanceId][Constants.Locks.MediaTypes], $"innerScope2, innerScope2 instance, after locks acquired: {nameof(Constants.Locks.MediaTypes)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[realParentScope.InstanceId][Constants.Locks.ContentTree], $"innerScope2, parent instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"innerScope2, parent instance, after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[innserScope1.InstanceId][Constants.Locks.ContentTree], $"innerScope2, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[innserScope1.InstanceId][Constants.Locks.ContentTypes], $"innerScope2, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.ContentTypes)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[innserScope1.InstanceId][Constants.Locks.Languages], $"innerScope2, innerScope1 instance, after locks acquired: {nameof(Constants.Locks.Languages)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[innerScope2.InstanceId][Constants.Locks.ContentTree], $"innerScope2, innerScope2 instance, after locks acquired: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[innerScope2.InstanceId][Constants.Locks.MediaTypes], $"innerScope2, innerScope2 instance, after locks acquired: {nameof(Constants.Locks.MediaTypes)}"); innerScope2.Complete(); } - Assert.AreEqual(1, realParentScope.ReadLocks[realParentScope.InstanceId][Constants.Locks.ContentTree], $"innerScope1, parent instance, after innerScope2 disposed: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.ReadLocks[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"innerScope1, parent instance, after innerScope2 disposed: {nameof(Constants.Locks.ContentTypes)}"); - Assert.AreEqual(1, realParentScope.ReadLocks[innserScope1.InstanceId][Constants.Locks.ContentTree], $"innerScope1, innerScope1 instance, after innerScope2 disposed: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.ReadLocks[innserScope1.InstanceId][Constants.Locks.ContentTypes], $"innerScope1, innerScope1 instance, after innerScope2 disposed: {nameof(Constants.Locks.ContentTypes)}"); - Assert.AreEqual(1, realParentScope.ReadLocks[innserScope1.InstanceId][Constants.Locks.Languages], $"innerScope1, innerScope1 instance, after innerScope2 disposed: {nameof(Constants.Locks.Languages)}"); - Assert.IsFalse(realParentScope.ReadLocks.ContainsKey(innerScope2Id)); + Assert.AreEqual(1, realParentScope.GetReadLocks()[realParentScope.InstanceId][Constants.Locks.ContentTree], $"innerScope1, parent instance, after innerScope2 disposed: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"innerScope1, parent instance, after innerScope2 disposed: {nameof(Constants.Locks.ContentTypes)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[innserScope1.InstanceId][Constants.Locks.ContentTree], $"innerScope1, innerScope1 instance, after innerScope2 disposed: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[innserScope1.InstanceId][Constants.Locks.ContentTypes], $"innerScope1, innerScope1 instance, after innerScope2 disposed: {nameof(Constants.Locks.ContentTypes)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[innserScope1.InstanceId][Constants.Locks.Languages], $"innerScope1, innerScope1 instance, after innerScope2 disposed: {nameof(Constants.Locks.Languages)}"); + Assert.IsFalse(realParentScope.GetReadLocks().ContainsKey(innerScope2Id)); innserScope1.Complete(); } - Assert.AreEqual(1, realParentScope.ReadLocks[realParentScope.InstanceId][Constants.Locks.ContentTree], $"parentScope after innerScope1 disposed: {nameof(Constants.Locks.ContentTree)}"); - Assert.AreEqual(1, realParentScope.ReadLocks[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"parentScope after innerScope1 disposed: {nameof(Constants.Locks.ContentTypes)}"); - Assert.IsFalse(realParentScope.ReadLocks.ContainsKey(innerScope2Id)); - Assert.IsFalse(realParentScope.ReadLocks.ContainsKey(innerScope1Id)); + Assert.AreEqual(1, realParentScope.GetReadLocks()[realParentScope.InstanceId][Constants.Locks.ContentTree], $"parentScope after innerScope1 disposed: {nameof(Constants.Locks.ContentTree)}"); + Assert.AreEqual(1, realParentScope.GetReadLocks()[realParentScope.InstanceId][Constants.Locks.ContentTypes], $"parentScope after innerScope1 disposed: {nameof(Constants.Locks.ContentTypes)}"); + Assert.IsFalse(realParentScope.GetReadLocks().ContainsKey(innerScope2Id)); + Assert.IsFalse(realParentScope.GetReadLocks().ContainsKey(innerScope1Id)); parentScope.Complete(); } @@ -421,12 +449,10 @@ namespace Umbraco.Tests.Scoping var scopeProvider = GetScopeProvider(out var syntaxProviderMock); syntaxProviderMock.Setup(x => x.WriteLock(It.IsAny(), It.IsAny())).Throws(new Exception("Boom")); - using (var scope = scopeProvider.CreateScope()) + using (var scope = (Scope)scopeProvider.CreateScope()) { - var realScope = (Scope) scope; - - Assert.Throws(() => scope.WriteLock(Constants.Locks.Languages)); - Assert.IsFalse(realScope.WriteLocks[scope.InstanceId].ContainsKey(Constants.Locks.Languages)); + Assert.Throws(() => scope.EagerWriteLock(Constants.Locks.Languages)); + Assert.IsFalse(scope.GetWriteLocks()[scope.InstanceId].ContainsKey(Constants.Locks.Languages)); scope.Complete(); } } @@ -437,12 +463,10 @@ namespace Umbraco.Tests.Scoping var scopeProvider = GetScopeProvider(out var syntaxProviderMock); syntaxProviderMock.Setup(x => x.ReadLock(It.IsAny(), It.IsAny())).Throws(new Exception("Boom")); - using (var scope = scopeProvider.CreateScope()) + using (var scope = (Scope)scopeProvider.CreateScope()) { - var realScope = (Scope) scope; - - Assert.Throws(() => scope.ReadLock(Constants.Locks.Languages)); - Assert.IsFalse(realScope.ReadLocks[scope.InstanceId].ContainsKey(Constants.Locks.Languages)); + Assert.Throws(() => scope.EagerReadLock(Constants.Locks.Languages)); + Assert.IsFalse(scope.GetReadLocks()[scope.InstanceId].ContainsKey(Constants.Locks.Languages)); scope.Complete(); } } @@ -451,7 +475,7 @@ namespace Umbraco.Tests.Scoping public void Scope_Throws_If_ReadLocks_Not_Cleared() { var scopeprovider = GetScopeProvider(out var syntaxProviderMock); - var scope = (Scope) scopeprovider.CreateScope(); + var scope = (Scope)scopeprovider.CreateScope(); try { @@ -460,14 +484,14 @@ namespace Umbraco.Tests.Scoping var readDict = new Dictionary(); readDict[Constants.Locks.Languages] = 1; - scope.ReadLocks[Guid.NewGuid()] = readDict; + scope.GetReadLocks()[Guid.NewGuid()] = readDict; Assert.Throws(() => scope.Dispose()); } finally { // We have to clear so we can properly dispose the scope, otherwise it'll mess with other tests. - scope.ReadLocks?.Clear(); + scope.GetReadLocks()?.Clear(); scope.Dispose(); } } @@ -476,7 +500,7 @@ namespace Umbraco.Tests.Scoping public void Scope_Throws_If_WriteLocks_Not_Cleared() { var scopeprovider = GetScopeProvider(out var syntaxProviderMock); - var scope = (Scope) scopeprovider.CreateScope(); + var scope = (Scope)scopeprovider.CreateScope(); try { @@ -485,14 +509,14 @@ namespace Umbraco.Tests.Scoping var writeDict = new Dictionary(); writeDict[Constants.Locks.Languages] = 1; - scope.WriteLocks[Guid.NewGuid()] = writeDict; + scope.GetWriteLocks()[Guid.NewGuid()] = writeDict; Assert.Throws(() => scope.Dispose()); } finally { // We have to clear so we can properly dispose the scope, otherwise it'll mess with other tests. - scope.WriteLocks?.Clear(); + scope.GetWriteLocks()?.Clear(); scope.Dispose(); } } @@ -504,8 +528,8 @@ namespace Umbraco.Tests.Scoping using (var scope = scopeProvider.CreateScope()) { - var realScope = (Scope) scope; - Assert.IsNull(realScope.WriteLocks); + var realScope = (Scope)scope; + Assert.IsNull(realScope.GetWriteLocks()); } } @@ -516,8 +540,8 @@ namespace Umbraco.Tests.Scoping using (var scope = scopeProvider.CreateScope()) { - var realScope = (Scope) scope; - Assert.IsNull(realScope.ReadLocks); + var realScope = (Scope)scope; + Assert.IsNull(realScope.GetReadLocks()); } } } diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index cc1cfa6a1d..0740c947f6 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -242,7 +242,7 @@ namespace Umbraco.Tests.TestHelpers } fileSystems = fileSystems ?? new FileSystems(Current.Factory, logger); - var scopeProvider = new ScopeProvider(databaseFactory, fileSystems, logger); + var scopeProvider = new ScopeProvider(databaseFactory, fileSystems, logger, Mock.Of(x => x.LogUncompletedScopes == true)); return scopeProvider; } } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 7bb90e9f93..3f4daa4df2 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -116,6 +116,7 @@ + From fb466f4309b6b89eaef96439186718bbfcd19f83 Mon Sep 17 00:00:00 2001 From: Jeavon Leopold Date: Thu, 9 Sep 2021 13:01:25 +0100 Subject: [PATCH 6/9] Fix string interpolation for health check messages --- .../HealthCheck/Checks/Security/BaseHttpHeaderCheck.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/BaseHttpHeaderCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/BaseHttpHeaderCheck.cs index 6bb32d9d74..fea674e123 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Security/BaseHttpHeaderCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Security/BaseHttpHeaderCheck.cs @@ -87,8 +87,8 @@ namespace Umbraco.Web.HealthCheck.Checks.Security } message = success - ? TextService.Localize($"healthcheck", "{_localizedTextPrefix}CheckHeaderFound") - : TextService.Localize($"healthcheck", "{_localizedTextPrefix}CheckHeaderNotFound"); + ? TextService.Localize($"healthcheck", $"{_localizedTextPrefix}CheckHeaderFound") + : TextService.Localize($"healthcheck", $"{_localizedTextPrefix}CheckHeaderNotFound"); } catch (Exception ex) { @@ -101,7 +101,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Security actions.Add(new HealthCheckAction(SetHeaderInConfigAction, Id) { Name = TextService.Localize("healthcheck", "setHeaderInConfig"), - Description = TextService.Localize($"healthcheck", "{_localizedTextPrefix}SetHeaderInConfigDescription") + Description = TextService.Localize($"healthcheck", $"{_localizedTextPrefix}SetHeaderInConfigDescription") }); } From 25d482f42ff26ed5de3af7a9332456c119cb1c92 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 13 Sep 2021 14:41:21 +0200 Subject: [PATCH 7/9] Add package-lock.json updates --- src/Umbraco.Web.UI.Client/package-lock.json | 331 ++++++++++++++++---- 1 file changed, 267 insertions(+), 64 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index b2d811df89..61e0299f3a 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -11,6 +11,12 @@ "@babel/highlight": "^7.0.0" } }, + "@babel/compat-data": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", + "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", + "dev": true + }, "@babel/core": { "version": "7.6.4", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.4.tgz", @@ -131,6 +137,57 @@ "@babel/types": "^7.4.4" } }, + "@babel/helper-compilation-targets": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", + "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.15.0", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", + "semver": "^6.3.0" + }, + "dependencies": { + "browserslist": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.0.tgz", + "integrity": "sha512-g2BJ2a0nEYvEFQC208q8mVAhfNwpZ5Mu8BwgtCdZKO3qx98HChmeg448fPdUzld8aFmfLgVh7yymqV+q1lJZ5g==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001254", + "colorette": "^1.3.0", + "electron-to-chromium": "^1.3.830", + "escalade": "^3.1.1", + "node-releases": "^1.1.75" + } + }, + "caniuse-lite": { + "version": "1.0.30001257", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001257.tgz", + "integrity": "sha512-JN49KplOgHSXpIsVSF+LUyhD8PUp6xPpAXeRrrcBh4KBeP7W864jHn6RvzJgDlrReyeVjMFJL3PLpPvKIxlIHA==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.836", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.836.tgz", + "integrity": "sha512-Ney3pHOJBWkG/AqYjrW0hr2AUCsao+2uvq9HUlRP8OlpSdk/zOHOUJP7eu0icDvePC9DlgffuelP4TnOJmMRUg==", + "dev": true + }, + "node-releases": { + "version": "1.1.75", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz", + "integrity": "sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "@babel/helper-define-map": { "version": "7.5.5", "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz", @@ -281,6 +338,12 @@ "@babel/types": "^7.4.4" } }, + "@babel/helper-validator-option": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "dev": true + }, "@babel/helper-wrap-function": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", @@ -353,13 +416,42 @@ } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.6.2.tgz", - "integrity": "sha512-LDBXlmADCsMZV1Y9OQwMc0MyGZ8Ta/zlD9N67BfQT8uYwkRswiu2hU6nJKrjrt/58aH/vqfQlR/9yId/7A2gWw==", + "version": "7.13.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz", + "integrity": "sha512-DhB2EuB1Ih7S3/IRX5AFVgZ16k3EzfRbq97CxAVI1KSYcW+lexV8VZb7G7L8zuPVSdQMRn0kiBpf/Yzu9ZKH0g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.2.0" + "@babel/compat-data": "^7.13.8", + "@babel/helper-compilation-targets": "^7.13.8", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.13.0" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "dev": true + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.15.4.tgz", + "integrity": "sha512-9WB/GUTO6lvJU3XQsSr6J/WKvBC2hcs4Pew8YxZagi6GkTdniyqp8On5kqdK8MN0LMeu0mGbhPN+O049NV/9FQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + } } }, "@babel/plugin-proposal-optional-catch-binding": { @@ -1129,9 +1221,9 @@ "dev": true }, "angular": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.0.tgz", - "integrity": "sha512-VdaMx+Qk0Skla7B5gw77a8hzlcOakwF8mjlW13DpIWIDlfqwAbSSLfd8N/qZnzEmQF4jC4iofInd3gE7vL8ZZg==" + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.2.tgz", + "integrity": "sha512-IauMOej2xEe7/7Ennahkbb5qd/HFADiNuLSESz9Q27inmi32zB0lnAsFeLEWcox3Gd1F6YhNd1CP7/9IukJ0Gw==" }, "angular-animate": { "version": "1.7.5", @@ -1836,7 +1928,8 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", - "dev": true + "dev": true, + "optional": true }, "base64id": { "version": "1.0.0", @@ -2083,6 +2176,7 @@ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", "dev": true, + "optional": true, "requires": { "p-finally": "^1.0.0" } @@ -2124,6 +2218,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha1-oWCRFxcQPAdBDO9j71Gzl8Alr5w=", "dev": true, + "optional": true, "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" @@ -2133,13 +2228,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -2155,6 +2252,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -2295,6 +2393,7 @@ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz", "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==", "dev": true, + "optional": true, "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" @@ -2320,7 +2419,8 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true + "dev": true, + "optional": true }, "buffer-equal": { "version": "1.0.0", @@ -2511,6 +2611,7 @@ "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", "dev": true, + "optional": true, "requires": { "get-proxy": "^2.0.0", "isurl": "^1.0.0-alpha5", @@ -2881,6 +2982,12 @@ "integrity": "sha1-k4NDeaHMmgxh+C9S8NBDIiUb1aI=", "dev": true }, + "colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true + }, "colornames": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", @@ -2944,6 +3051,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", "dev": true, + "optional": true, "requires": { "graceful-readlink": ">= 1.0.0" } @@ -3038,6 +3146,7 @@ "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", "dev": true, + "optional": true, "requires": { "ini": "^1.3.4", "proto-list": "~1.2.1" @@ -3093,6 +3202,7 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", "dev": true, + "optional": true, "requires": { "safe-buffer": "5.1.2" } @@ -3536,6 +3646,7 @@ "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.0.tgz", "integrity": "sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=", "dev": true, + "optional": true, "requires": { "decompress-tar": "^4.0.0", "decompress-tarbz2": "^4.0.0", @@ -3552,6 +3663,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, + "optional": true, "requires": { "pify": "^3.0.0" }, @@ -3560,7 +3672,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "dev": true, + "optional": true } } } @@ -3571,6 +3684,7 @@ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", "dev": true, + "optional": true, "requires": { "mimic-response": "^1.0.0" } @@ -3580,6 +3694,7 @@ "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", "dev": true, + "optional": true, "requires": { "file-type": "^5.2.0", "is-stream": "^1.1.0", @@ -3590,7 +3705,8 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", - "dev": true + "dev": true, + "optional": true } } }, @@ -3599,6 +3715,7 @@ "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", "dev": true, + "optional": true, "requires": { "decompress-tar": "^4.1.0", "file-type": "^6.1.0", @@ -3611,7 +3728,8 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", - "dev": true + "dev": true, + "optional": true } } }, @@ -3620,6 +3738,7 @@ "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", "dev": true, + "optional": true, "requires": { "decompress-tar": "^4.1.1", "file-type": "^5.2.0", @@ -3630,7 +3749,8 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", - "dev": true + "dev": true, + "optional": true } } }, @@ -3639,6 +3759,7 @@ "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", "integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=", "dev": true, + "optional": true, "requires": { "file-type": "^3.8.0", "get-stream": "^2.2.0", @@ -3650,13 +3771,15 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", - "dev": true + "dev": true, + "optional": true }, "get-stream": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=", "dev": true, + "optional": true, "requires": { "object-assign": "^4.0.1", "pinkie-promise": "^2.0.0" @@ -3666,7 +3789,8 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "dev": true, + "optional": true } } }, @@ -3948,7 +4072,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "dev": true, + "optional": true } } }, @@ -3965,7 +4090,8 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true + "dev": true, + "optional": true }, "duplexify": { "version": "3.7.1", @@ -4292,6 +4418,12 @@ "es6-symbol": "^3.1.1" } }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -4568,6 +4700,7 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "dev": true, + "optional": true, "requires": { "cross-spawn": "^5.0.1", "get-stream": "^3.0.0", @@ -4583,6 +4716,7 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, + "optional": true, "requires": { "lru-cache": "^4.0.1", "shebang-command": "^1.2.0", @@ -4722,6 +4856,7 @@ "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", "dev": true, + "optional": true, "requires": { "mime-db": "^1.28.0" } @@ -4731,6 +4866,7 @@ "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", "dev": true, + "optional": true, "requires": { "ext-list": "^2.0.0", "sort-keys-length": "^1.0.0" @@ -4965,6 +5101,7 @@ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "dev": true, + "optional": true, "requires": { "pend": "~1.2.0" } @@ -5003,13 +5140,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", - "dev": true + "dev": true, + "optional": true }, "filenamify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", "integrity": "sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==", "dev": true, + "optional": true, "requires": { "filename-reserved-regex": "^2.0.0", "strip-outer": "^1.0.0", @@ -5196,9 +5335,9 @@ } }, "flatpickr": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.5.2.tgz", - "integrity": "sha1-R8itRyoJbl+350uAmwcDU1OD8g0=" + "version": "4.6.9", + "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.9.tgz", + "integrity": "sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw==" }, "flatted": { "version": "2.0.1", @@ -5370,7 +5509,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", - "dev": true + "dev": true, + "optional": true }, "fs-mkdirp-stream": { "version": "1.0.0", @@ -5417,7 +5557,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -5438,12 +5579,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5458,17 +5601,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5585,7 +5731,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5597,6 +5744,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5611,6 +5759,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5618,12 +5767,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5642,6 +5793,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5722,7 +5874,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5734,6 +5887,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5819,7 +5973,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -5855,6 +6010,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5874,6 +6030,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5917,12 +6074,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -5949,6 +6108,7 @@ "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", "dev": true, + "optional": true, "requires": { "npm-conf": "^1.1.0" } @@ -5957,13 +6117,15 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true + "dev": true, + "optional": true }, "get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true + "dev": true, + "optional": true }, "get-value": { "version": "2.0.6", @@ -6278,7 +6440,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true + "dev": true, + "optional": true }, "growly": { "version": "1.3.0", @@ -7049,7 +7212,8 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", - "dev": true + "dev": true, + "optional": true }, "has-symbols": { "version": "1.0.0", @@ -7062,6 +7226,7 @@ "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", "dev": true, + "optional": true, "requires": { "has-symbol-support-x": "^1.4.1" } @@ -7261,7 +7426,8 @@ "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", - "dev": true + "dev": true, + "optional": true }, "ignore": { "version": "4.0.6", @@ -7391,6 +7557,7 @@ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "dev": true, + "optional": true, "requires": { "repeating": "^2.0.0" } @@ -7718,6 +7885,7 @@ "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7770,7 +7938,8 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", "integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=", - "dev": true + "dev": true, + "optional": true }, "is-negated-glob": { "version": "1.0.0", @@ -7808,13 +7977,15 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", - "dev": true + "dev": true, + "optional": true }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true + "dev": true, + "optional": true }, "is-plain-object": { "version": "2.0.4", @@ -7884,13 +8055,15 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", - "dev": true + "dev": true, + "optional": true }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "dev": true, + "optional": true }, "is-svg": { "version": "3.0.0", @@ -7987,6 +8160,7 @@ "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", "dev": true, + "optional": true, "requires": { "has-to-string-tag-x": "^1.2.0", "is-object": "^1.0.1" @@ -8011,9 +8185,9 @@ } }, "jquery": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", - "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==" + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", + "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" }, "jquery-ui-dist": { "version": "1.12.1", @@ -8899,7 +9073,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", - "dev": true + "dev": true, + "optional": true }, "lpad-align": { "version": "1.1.2", @@ -8969,7 +9144,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true + "dev": true, + "optional": true }, "map-visit": { "version": "1.0.0", @@ -9139,7 +9315,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true + "dev": true, + "optional": true }, "minimatch": { "version": "3.0.4", @@ -9362,9 +9539,9 @@ "dev": true }, "nouislider": { - "version": "14.6.4", - "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.6.4.tgz", - "integrity": "sha512-PVCGYl+aC7/nVEbW61ypJWfuW3UCpvctz/luxpt4byxxli1FFyjBX9NIiy4Yak9AaO6a5BkPGfFYMCW4eg3eeQ==" + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-15.4.0.tgz", + "integrity": "sha512-AV7UMhGhZ4Mj6ToMT812Ib8OJ4tAXR2/Um7C4l4ZvvsqujF0WpQTpqqHJ+9xt4174R7ueQOUrBR4yakJpAIPCA==" }, "now-and-later": { "version": "2.0.1", @@ -12474,6 +12651,7 @@ "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", "dev": true, + "optional": true, "requires": { "config-chain": "^1.1.11", "pify": "^3.0.0" @@ -12483,7 +12661,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "dev": true, + "optional": true } } }, @@ -12492,6 +12671,7 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, + "optional": true, "requires": { "path-key": "^2.0.0" } @@ -12853,7 +13033,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "dev": true, + "optional": true }, "p-is-promise": { "version": "1.1.0", @@ -12890,6 +13071,7 @@ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", "dev": true, + "optional": true, "requires": { "p-finally": "^1.0.0" } @@ -13080,7 +13262,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true + "dev": true, + "optional": true }, "performance-now": { "version": "2.1.0", @@ -13588,7 +13771,8 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", - "dev": true + "dev": true, + "optional": true }, "prr": { "version": "1.0.1", @@ -13944,6 +14128,7 @@ "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, + "optional": true, "requires": { "is-finite": "^1.0.0" } @@ -14305,6 +14490,7 @@ "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz", "integrity": "sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=", "dev": true, + "optional": true, "requires": { "commander": "~2.8.1" } @@ -14707,6 +14893,7 @@ "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", "dev": true, + "optional": true, "requires": { "is-plain-obj": "^1.0.0" } @@ -14716,6 +14903,7 @@ "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", "integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=", "dev": true, + "optional": true, "requires": { "sort-keys": "^1.0.0" } @@ -15046,6 +15234,7 @@ "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", "dev": true, + "optional": true, "requires": { "is-natural-number": "^4.0.1" } @@ -15054,7 +15243,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true + "dev": true, + "optional": true }, "strip-indent": { "version": "1.0.1", @@ -15077,6 +15267,7 @@ "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", "dev": true, + "optional": true, "requires": { "escape-string-regexp": "^1.0.2" } @@ -15202,6 +15393,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", "integrity": "sha1-jqVdqzeXIlPZqa+Q/c1VmuQ1xVU=", "dev": true, + "optional": true, "requires": { "bl": "^1.0.0", "buffer-alloc": "^1.2.0", @@ -15216,13 +15408,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -15238,6 +15432,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -15248,13 +15443,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", - "dev": true + "dev": true, + "optional": true }, "tempfile": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-2.0.0.tgz", "integrity": "sha1-awRGhWqbERTRhW/8vlCczLCXcmU=", "dev": true, + "optional": true, "requires": { "temp-dir": "^1.0.0", "uuid": "^3.0.1" @@ -15349,7 +15546,8 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "dev": true + "dev": true, + "optional": true }, "timers-ext": { "version": "0.1.7", @@ -15406,7 +15604,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", - "dev": true + "dev": true, + "optional": true }, "to-fast-properties": { "version": "2.0.0", @@ -15510,6 +15709,7 @@ "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", "dev": true, + "optional": true, "requires": { "escape-string-regexp": "^1.0.2" } @@ -15646,6 +15846,7 @@ "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.3.3.tgz", "integrity": "sha512-fUlAF7U9Ah1Q6EieQ4x4zLNejrRvDWUYmxXUpN3uziFYCHapjWFaCAnreY9bGgxzaMCFAPPpYNng57CypwJVhg==", "dev": true, + "optional": true, "requires": { "buffer": "^5.2.1", "through": "^2.3.8" @@ -15846,7 +16047,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", - "dev": true + "dev": true, + "optional": true }, "use": { "version": "3.1.1", @@ -16342,6 +16544,7 @@ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "dev": true, + "optional": true, "requires": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" From eb7b570002a6804be2e58d7537aa5d77c833a780 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Wed, 18 Aug 2021 09:43:53 +0200 Subject: [PATCH 8/9] Include MediaService extensions --- src/Umbraco.Core/Umbraco.Core.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 7ad209efdf..77eeaaa853 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1502,6 +1502,7 @@ + @@ -1664,4 +1665,4 @@ - \ No newline at end of file + From c6a1765c95a949bf272b3bc8bcce1174e5850f21 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 13 Sep 2021 20:29:41 +0200 Subject: [PATCH 9/9] set form to dirty when add tab, delete tab, add group, and delete group --- .../directives/components/umbgroupsbuilder.directive.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index 68460f4982..0fd6bba091 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -534,6 +534,8 @@ scope.openTabAlias = tab.alias; + notifyChanged(); + scope.$broadcast('umbOverflowChecker.checkOverflow'); scope.$broadcast('umbOverflowChecker.scrollTo', { position: 'end' }); }; @@ -571,6 +573,8 @@ scope.$broadcast('umbOverflowChecker.checkOverflow'); + notifyChanged(); + overlayService.close(); } }); @@ -712,6 +716,8 @@ scope.model.groups = [...scope.model.groups, group]; scope.activateGroup(group); + + notifyChanged(); }; scope.activateGroup = selectedGroup => { @@ -758,6 +764,7 @@ scope.model.groups.splice(index, 1); overlayService.close(); + notifyChanged(); } }); }); @@ -1023,7 +1030,6 @@ return (result.length > 0); } - eventBindings.push(scope.$watch('model', (newValue, oldValue) => { if (newValue !== undefined && newValue.groups !== undefined) { activate();