Don't eagerly acquire distributed (SQL) locks (#10171)
This commit is contained in:
60
src/Umbraco.Core/Collections/StackQueue.cs
Normal file
60
src/Umbraco.Core/Collections/StackQueue.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Umbraco.Core.Configuration
|
||||
{
|
||||
internal class CoreDebug
|
||||
internal class CoreDebug : ICoreDebug
|
||||
{
|
||||
public CoreDebug()
|
||||
{
|
||||
|
||||
8
src/Umbraco.Core/Configuration/ICoreDebug.cs
Normal file
8
src/Umbraco.Core/Configuration/ICoreDebug.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Core.Configuration
|
||||
{
|
||||
public interface ICoreDebug
|
||||
{
|
||||
bool DumpOnTimeoutThreadAbort { get; }
|
||||
bool LogUncompletedScopes { get; }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
74
src/Umbraco.Tests/Collections/StackQueueTests.cs
Normal file
74
src/Umbraco.Tests/Collections/StackQueueTests.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>()));
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user