Don't eagerly acquire distributed (SQL) locks (#10171)

This commit is contained in:
Shannon Deminick
2021-09-13 21:09:47 +10:00
committed by GitHub
parent b17cc47632
commit 5fadb238ee
14 changed files with 712 additions and 245 deletions

View File

@@ -0,0 +1,60 @@
using System.Collections.Generic;
namespace Umbraco.Core.Collections
{
/// <summary>
/// Collection that can be both a queue and a stack.
/// </summary>
/// <typeparam name="T"></typeparam>
public class StackQueue<T>
{
private readonly LinkedList<T> _linkedList = new LinkedList<T>();
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;
}
}
}
}

View File

@@ -28,8 +28,8 @@ namespace Umbraco.Core
public static IGridConfig Grids(this Configs configs)
=> configs.GetConfig<IGridConfig>();
internal static CoreDebug CoreDebug(this Configs configs)
=> configs.GetConfig<CoreDebug>();
public static ICoreDebug CoreDebug(this Configs configs)
=> configs.GetConfig<ICoreDebug>();
public static void AddCoreConfigs(this Configs configs)
{
@@ -39,7 +39,7 @@ namespace Umbraco.Core
configs.Add<IUmbracoSettingsSection>("umbracoConfiguration/settings");
configs.Add<IHealthChecks>("umbracoConfiguration/HealthChecks");
configs.Add(() => new CoreDebug());
configs.Add<ICoreDebug>(() => new CoreDebug());
// GridConfig depends on runtime caches, manifest parsers... and cannot be available during composition
configs.Add<IGridConfig>(factory => new GridConfig(

View File

@@ -2,7 +2,7 @@
namespace Umbraco.Core.Configuration
{
internal class CoreDebug
internal class CoreDebug : ICoreDebug
{
public CoreDebug()
{

View File

@@ -0,0 +1,8 @@
namespace Umbraco.Core.Configuration
{
public interface ICoreDebug
{
bool DumpOnTimeoutThreadAbort { get; }
bool LogUncompletedScopes { get; }
}
}

View File

@@ -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
/// <remarks>Not thread-safe obviously.</remarks>
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<int> _readLocks;
private HashSet<int> _writeLocks;
internal Dictionary<Guid, Dictionary<int, int>> ReadLocks;
internal Dictionary<Guid, Dictionary<int, int>> WriteLocks;
private Dictionary<Guid, Dictionary<int, int>> _readLocksDictionary;
private Dictionary<Guid, Dictionary<int, int>> _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)
{ }
/// <summary>
/// Used for testing. Ensures and gets any queued read locks.
/// </summary>
/// <returns></returns>
internal Dictionary<Guid, Dictionary<int, int>> GetReadLocks()
{
EnsureDbLocks();
// always delegate to root/parent scope.
if (ParentScope is not null)
{
return ParentScope.GetReadLocks();
}
else
{
return _readLocksDictionary;
}
}
/// <summary>
/// Used for testing. Ensures and gets and queued write locks.
/// </summary>
/// <returns></returns>
internal Dictionary<Guid, Dictionary<int, int>> 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<Scope, string>("Uncompleted Child Scope at\r\n {StackTrace}", Environment.StackTrace);
}
_completed = false;
}
}
/// <summary>
/// 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.
/// </summary>
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<int>();
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<Scope>(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;
/// <summary>
/// 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;
}
}
}
}
}
/// <inheritdoc />
public void ReadLock(params int[] lockIds) => ReadLockInner(InstanceId, null, lockIds);
public void EagerReadLock(params int[] lockIds) => EagerReadLockInner(Database, InstanceId, null, lockIds);
/// <inheritdoc />
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);
/// <inheritdoc />
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);
/// <inheritdoc />
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);
/// <inheritdoc />
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));
}
}
/// <summary>
/// Handles acquiring a read lock, will delegate it to the parent if there are any.
@@ -614,17 +875,17 @@ namespace Umbraco.Core.Scoping
/// <param name="instanceId">Instance ID of the requesting scope.</param>
/// <param name="timeout">Optional database timeout in milliseconds.</param>
/// <param name="lockIds">Array of lock object identifiers.</param>
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
/// <param name="instanceId">Instance ID of the requesting scope.</param>
/// <param name="timeout">Optional database timeout in milliseconds.</param>
/// <param name="lockIds">Array of lock object identifiers.</param>
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
/// <param name="obtainLockTimeout">Delegate used to request the lock from the database with a timeout.</param>
/// <param name="timeout">Optional timeout parameter to specify a timeout.</param>
/// <param name="lockIds">Lock identifiers to lock on.</param>
private void LockInner(Guid instanceId, ref Dictionary<Guid, Dictionary<int, int>> locks, ref HashSet<int> locksSet,
Action<int> obtainLock, Action<int, TimeSpan> obtainLockTimeout, TimeSpan? timeout = null,
private void LockInner(IUmbracoDatabase db, Guid instanceId, ref Dictionary<Guid, Dictionary<int, int>> locks, ref HashSet<int> locksSet,
Action<IUmbracoDatabase, int> obtainLock, Action<IUmbracoDatabase, int, TimeSpan> 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.
/// </summary>
/// <param name="lockId">Lock object identifier to lock.</param>
private void ObtainReadLock(int lockId)
private void ObtainReadLock(IUmbracoDatabase db, int lockId)
{
Database.SqlContext.SqlSyntax.ReadLock(Database, lockId);
SqlContext.SqlSyntax.ReadLock(db, lockId);
}
/// <summary>
@@ -718,24 +979,24 @@ namespace Umbraco.Core.Scoping
/// </summary>
/// <param name="lockId">Lock object identifier to lock.</param>
/// <param name="timeout">TimeSpan specifying the timout period.</param>
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);
}
/// <summary>
/// Obtains an ordinary write lock.
/// </summary>
/// <param name="lockId">Lock object identifier to lock.</param>
private void ObtainWriteLock(int lockId)
private void ObtainWriteLock(IUmbracoDatabase db, int lockId)
{
Database.SqlContext.SqlSyntax.WriteLock(Database, lockId);
SqlContext.SqlSyntax.WriteLock(db, lockId);
}
/// <summary>
@@ -743,15 +1004,15 @@ namespace Umbraco.Core.Scoping
/// </summary>
/// <param name="lockId">Lock object identifier to lock.</param>
/// <param name="timeout">TimeSpan specifying the timout period.</param>
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);
}
}
}

View File

@@ -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);
}
/// <inheritdoc />
@@ -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;
}

View File

@@ -128,6 +128,8 @@
</Compile>
-->
<Compile Include="AssemblyExtensions.cs" />
<Compile Include="Collections\StackQueue.cs" />
<Compile Include="Configuration\ICoreDebug.cs" />
<Compile Include="Constants-CharArrays.cs" />
<Compile Include="Collections\EventClearingObservableCollection.cs" />
<Compile Include="Constants-SqlTemplates.cs" />

View File

@@ -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<int>();
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<int>();
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<int>();
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());
}
}
}

View File

@@ -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<ILogger>();
var f = new UmbracoDatabaseFactory(logger, new Lazy<IMapperCollection>(() => new MapperCollection(Enumerable.Empty<BaseMapper>())));
var fs = new FileSystems(mock.Object, logger);
var p = new ScopeProvider(f, fs, logger);
var p = new ScopeProvider(f, fs, logger, Mock.Of<ICoreDebug>(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<ILogger>(), Mock.Of<IProfiler>()));

View File

@@ -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<SqlCeLockTimeoutException>(() =>
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<SqlCeLockTimeoutException>(() =>
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();

View File

@@ -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<ArgumentException>(() =>
{
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<ArgumentException>(() =>
{
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();
}
}

View File

@@ -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<ICoreDebug>(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<IDatabase>(), It.IsAny<int[]>())).Throws(new Exception("Boom"));
using (var scope = scopeProvider.CreateScope())
using (var scope = (Scope)scopeProvider.CreateScope())
{
var realScope = (Scope) scope;
Assert.Throws<Exception>(() => scope.WriteLock(Constants.Locks.Languages));
Assert.IsFalse(realScope.WriteLocks[scope.InstanceId].ContainsKey(Constants.Locks.Languages));
Assert.Throws<Exception>(() => 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<IDatabase>(), It.IsAny<int[]>())).Throws(new Exception("Boom"));
using (var scope = scopeProvider.CreateScope())
using (var scope = (Scope)scopeProvider.CreateScope())
{
var realScope = (Scope) scope;
Assert.Throws<Exception>(() => scope.ReadLock(Constants.Locks.Languages));
Assert.IsFalse(realScope.ReadLocks[scope.InstanceId].ContainsKey(Constants.Locks.Languages));
Assert.Throws<Exception>(() => 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<int, int>();
readDict[Constants.Locks.Languages] = 1;
scope.ReadLocks[Guid.NewGuid()] = readDict;
scope.GetReadLocks()[Guid.NewGuid()] = readDict;
Assert.Throws<InvalidOperationException>(() => 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<int, int>();
writeDict[Constants.Locks.Languages] = 1;
scope.WriteLocks[Guid.NewGuid()] = writeDict;
scope.GetWriteLocks()[Guid.NewGuid()] = writeDict;
Assert.Throws<InvalidOperationException>(() => 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());
}
}
}

View File

@@ -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<ICoreDebug>(x => x.LogUncompletedScopes == true));
return scopeProvider;
}
}

View File

@@ -116,6 +116,7 @@
<Compile Include="Cache\SnapDictionaryTests.cs" />
<Compile Include="Clr\ReflectionUtilitiesTests.cs" />
<Compile Include="Collections\OrderedHashSetTests.cs" />
<Compile Include="Collections\StackQueueTests.cs" />
<Compile Include="Composing\CompositionTests.cs" />
<Compile Include="Composing\LightInjectValidation.cs" />
<Compile Include="Composing\ContainerConformingTests.cs" />