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 @@
+