Merge branch 'v8/dev' into v8/contrib
This commit is contained in:
@@ -18,5 +18,5 @@ using System.Resources;
|
||||
[assembly: AssemblyVersion("8.0.0")]
|
||||
|
||||
// these are FYI and changed automatically
|
||||
[assembly: AssemblyFileVersion("8.12.0")]
|
||||
[assembly: AssemblyInformationalVersion("8.12.0")]
|
||||
[assembly: AssemblyFileVersion("8.12.1")]
|
||||
[assembly: AssemblyInformationalVersion("8.12.1")]
|
||||
|
||||
@@ -409,26 +409,34 @@ namespace Umbraco.Core.Configuration
|
||||
{
|
||||
if (_sqlWriteLockTimeOut != default) return _sqlWriteLockTimeOut;
|
||||
|
||||
var timeOut = 5000; // 5 seconds
|
||||
var appSettingSqlWriteLockTimeOut = ConfigurationManager.AppSettings[Constants.AppSettings.SqlWriteLockTimeOut];
|
||||
if(int.TryParse(appSettingSqlWriteLockTimeOut, out var configuredTimeOut))
|
||||
{
|
||||
// Only apply this setting if it's not excessively high or low
|
||||
const int minimumTimeOut = 100;
|
||||
const int maximumTimeOut = 20000;
|
||||
if (configuredTimeOut >= minimumTimeOut && configuredTimeOut <= maximumTimeOut) // between 0.1 and 20 seconds
|
||||
{
|
||||
timeOut = configuredTimeOut;
|
||||
}
|
||||
else
|
||||
{
|
||||
Current.Logger.Warn<GlobalSettings>($"The `{Constants.AppSettings.SqlWriteLockTimeOut}` setting in web.config is not between the minimum of {minimumTimeOut} ms and maximum of {maximumTimeOut} ms, defaulting back to {timeOut}");
|
||||
}
|
||||
}
|
||||
var timeOut = GetSqlWriteLockTimeoutFromConfigFile(Current.Logger);
|
||||
|
||||
_sqlWriteLockTimeOut = timeOut;
|
||||
return _sqlWriteLockTimeOut;
|
||||
}
|
||||
}
|
||||
|
||||
internal static int GetSqlWriteLockTimeoutFromConfigFile(ILogger logger)
|
||||
{
|
||||
var timeOut = 5000; // 5 seconds
|
||||
var appSettingSqlWriteLockTimeOut = ConfigurationManager.AppSettings[Constants.AppSettings.SqlWriteLockTimeOut];
|
||||
if (int.TryParse(appSettingSqlWriteLockTimeOut, out var configuredTimeOut))
|
||||
{
|
||||
// Only apply this setting if it's not excessively high or low
|
||||
const int minimumTimeOut = 100;
|
||||
const int maximumTimeOut = 20000;
|
||||
if (configuredTimeOut >= minimumTimeOut && configuredTimeOut <= maximumTimeOut) // between 0.1 and 20 seconds
|
||||
{
|
||||
timeOut = configuredTimeOut;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn<GlobalSettings>(
|
||||
$"The `{Constants.AppSettings.SqlWriteLockTimeOut}` setting in web.config is not between the minimum of {minimumTimeOut} ms and maximum of {maximumTimeOut} ms, defaulting back to {timeOut}");
|
||||
}
|
||||
}
|
||||
|
||||
return timeOut;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using NPoco;
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
@@ -7,6 +8,7 @@ using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
@@ -18,6 +20,7 @@ namespace Umbraco.Core.Runtime
|
||||
{
|
||||
internal class SqlMainDomLock : IMainDomLock
|
||||
{
|
||||
private readonly TimeSpan _lockTimeout;
|
||||
private string _lockId;
|
||||
private const string MainDomKeyPrefix = "Umbraco.Core.Runtime.SqlMainDom";
|
||||
private const string UpdatedSuffix = "_updated";
|
||||
@@ -40,6 +43,8 @@ namespace Umbraco.Core.Runtime
|
||||
Constants.System.UmbracoConnectionName,
|
||||
_logger,
|
||||
new Lazy<IMapperCollection>(() => new MapperCollection(Enumerable.Empty<BaseMapper>())));
|
||||
|
||||
_lockTimeout = TimeSpan.FromMilliseconds(GlobalSettings.GetSqlWriteLockTimeoutFromConfigFile(logger));
|
||||
}
|
||||
|
||||
public async Task<bool> AcquireLockAsync(int millisecondsTimeout)
|
||||
@@ -198,7 +203,7 @@ namespace Umbraco.Core.Runtime
|
||||
|
||||
db.BeginTransaction(IsolationLevel.ReadCommitted);
|
||||
// get a read lock
|
||||
_sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom);
|
||||
_sqlServerSyntax.ReadLock(db, _lockTimeout, Constants.Locks.MainDom);
|
||||
|
||||
if (!IsMainDomValue(_lockId, db))
|
||||
{
|
||||
@@ -284,7 +289,7 @@ namespace Umbraco.Core.Runtime
|
||||
{
|
||||
transaction = db.GetTransaction(IsolationLevel.ReadCommitted);
|
||||
// get a read lock
|
||||
_sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom);
|
||||
_sqlServerSyntax.ReadLock(db, _lockTimeout, Constants.Locks.MainDom);
|
||||
|
||||
// the row
|
||||
var mainDomRows = db.Fetch<KeyValueDto>("SELECT * FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey });
|
||||
@@ -296,7 +301,7 @@ namespace Umbraco.Core.Runtime
|
||||
// which indicates that we
|
||||
// can acquire it and it has shutdown.
|
||||
|
||||
_sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom);
|
||||
_sqlServerSyntax.WriteLock(db, _lockTimeout, Constants.Locks.MainDom);
|
||||
|
||||
// so now we update the row with our appdomain id
|
||||
InsertLockRecord(_lockId, db);
|
||||
@@ -355,7 +360,7 @@ namespace Umbraco.Core.Runtime
|
||||
{
|
||||
transaction = db.GetTransaction(IsolationLevel.ReadCommitted);
|
||||
|
||||
_sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom);
|
||||
_sqlServerSyntax.WriteLock(db, _lockTimeout, Constants.Locks.MainDom);
|
||||
|
||||
// so now we update the row with our appdomain id
|
||||
InsertLockRecord(_lockId, db);
|
||||
@@ -438,7 +443,7 @@ namespace Umbraco.Core.Runtime
|
||||
db.BeginTransaction(IsolationLevel.ReadCommitted);
|
||||
|
||||
// get a write lock
|
||||
_sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom);
|
||||
_sqlServerSyntax.WriteLock(db, _lockTimeout, Constants.Locks.MainDom);
|
||||
|
||||
// When we are disposed, it means we have released the MainDom lock
|
||||
// and called all MainDom release callbacks, in this case
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
@@ -34,6 +35,13 @@ namespace Umbraco.Core.Scoping
|
||||
private ICompletable _fscope;
|
||||
private IEventDispatcher _eventDispatcher;
|
||||
|
||||
private object _dictionaryLocker;
|
||||
|
||||
// ReadLocks and WriteLocks if we're the outer most scope it's those owned by the entire chain
|
||||
// If we're a child scope it's those that we have requested.
|
||||
internal readonly Dictionary<int, int> ReadLocks;
|
||||
internal readonly Dictionary<int, int> WriteLocks;
|
||||
|
||||
// initializes a new scope
|
||||
private Scope(ScopeProvider scopeProvider,
|
||||
ILogger logger, FileSystems fileSystems, Scope parent, ScopeContext scopeContext, bool detachable,
|
||||
@@ -58,6 +66,10 @@ namespace Umbraco.Core.Scoping
|
||||
|
||||
Detachable = detachable;
|
||||
|
||||
_dictionaryLocker = new object();
|
||||
ReadLocks = new Dictionary<int, int>();
|
||||
WriteLocks = new Dictionary<int, int>();
|
||||
|
||||
#if DEBUG_SCOPES
|
||||
_scopeProvider.RegisterScope(this);
|
||||
Console.WriteLine("create " + InstanceId.ToString("N").Substring(0, 8));
|
||||
@@ -348,6 +360,23 @@ namespace Umbraco.Core.Scoping
|
||||
#endif
|
||||
}
|
||||
|
||||
// Decrement the lock counters on the parent if any.
|
||||
if (ParentScope != null)
|
||||
{
|
||||
lock (_dictionaryLocker)
|
||||
{
|
||||
foreach (var readLockPair in ReadLocks)
|
||||
{
|
||||
DecrementReadLock(readLockPair.Key, readLockPair.Value);
|
||||
}
|
||||
|
||||
foreach (var writeLockPair in WriteLocks)
|
||||
{
|
||||
DecrementWriteLock(writeLockPair.Key, writeLockPair.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var parent = ParentScope;
|
||||
_scopeProvider.AmbientScope = parent; // might be null = this is how scopes are removed from context objects
|
||||
|
||||
@@ -486,31 +515,259 @@ namespace Umbraco.Core.Scoping
|
||||
private static bool LogUncompletedScopes => (_logUncompletedScopes
|
||||
?? (_logUncompletedScopes = Current.Configs.CoreDebug().LogUncompletedScopes)).Value;
|
||||
|
||||
/// <summary>
|
||||
/// Decrements the count of the ReadLocks with a specific lock object identifier we currently hold
|
||||
/// </summary>
|
||||
/// <param name="lockId">Lock object identifier to decrement</param>
|
||||
/// <param name="amountToDecrement">Amount to decrement the lock count with</param>
|
||||
public void DecrementReadLock(int lockId, int amountToDecrement)
|
||||
{
|
||||
// If we aren't the outermost scope, pass it on to the parent.
|
||||
if (ParentScope != null)
|
||||
{
|
||||
ParentScope.DecrementReadLock(lockId, amountToDecrement);
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_dictionaryLocker)
|
||||
{
|
||||
ReadLocks[lockId] -= amountToDecrement;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrements the count of the WriteLocks with a specific lock object identifier we currently hold.
|
||||
/// </summary>
|
||||
/// <param name="lockId">Lock object identifier to decrement.</param>
|
||||
/// <param name="amountToDecrement">Amount to decrement the lock count with</param>
|
||||
public void DecrementWriteLock(int lockId, int amountToDecrement)
|
||||
{
|
||||
// If we aren't the outermost scope, pass it on to the parent.
|
||||
if (ParentScope != null)
|
||||
{
|
||||
ParentScope.DecrementWriteLock(lockId, amountToDecrement);
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_dictionaryLocker)
|
||||
{
|
||||
WriteLocks[lockId] -= amountToDecrement;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increment the count of the read locks we've requested
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should only be done on child scopes since it's then used to decrement the count later.
|
||||
/// </remarks>
|
||||
/// <param name="lockIds"></param>
|
||||
private void IncrementRequestedReadLock(params int[] lockIds)
|
||||
{
|
||||
// We need to keep track of what lockIds we have requested locks for to be able to decrement them.
|
||||
if (ParentScope != null)
|
||||
{
|
||||
foreach (var lockId in lockIds)
|
||||
{
|
||||
lock (_dictionaryLocker)
|
||||
{
|
||||
if (ReadLocks.ContainsKey(lockId))
|
||||
{
|
||||
ReadLocks[lockId] += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReadLocks[lockId] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increment the count of the write locks we've requested
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should only be done on child scopes since it's then used to decrement the count later.
|
||||
/// </remarks>
|
||||
/// <param name="lockIds"></param>
|
||||
private void IncrementRequestedWriteLock(params int[] lockIds)
|
||||
{
|
||||
// We need to keep track of what lockIds we have requested locks for to be able to decrement them.
|
||||
if (ParentScope != null)
|
||||
{
|
||||
foreach (var lockId in lockIds)
|
||||
{
|
||||
lock (_dictionaryLocker)
|
||||
{
|
||||
if (WriteLocks.ContainsKey(lockId))
|
||||
{
|
||||
WriteLocks[lockId] += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLocks[lockId] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReadLock(params int[] lockIds) => Database.SqlContext.SqlSyntax.ReadLock(Database, lockIds);
|
||||
public void ReadLock(params int[] lockIds)
|
||||
{
|
||||
IncrementRequestedReadLock(lockIds);
|
||||
ReadLockInner(null, lockIds);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReadLock(TimeSpan timeout, int lockId)
|
||||
{
|
||||
var syntax2 = Database.SqlContext.SqlSyntax as ISqlSyntaxProvider2;
|
||||
if (syntax2 == null)
|
||||
{
|
||||
throw new InvalidOperationException($"{Database.SqlContext.SqlSyntax.GetType()} is not of type {typeof(ISqlSyntaxProvider2)}");
|
||||
}
|
||||
syntax2.ReadLock(Database, timeout, lockId);
|
||||
IncrementRequestedReadLock(lockId);
|
||||
ReadLockInner(timeout, lockId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteLock(params int[] lockIds) => Database.SqlContext.SqlSyntax.WriteLock(Database, lockIds);
|
||||
public void WriteLock(params int[] lockIds)
|
||||
{
|
||||
IncrementRequestedWriteLock(lockIds);
|
||||
WriteLockInner(null, lockIds);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteLock(TimeSpan timeout, int lockId)
|
||||
{
|
||||
IncrementRequestedWriteLock(lockId);
|
||||
WriteLockInner(timeout, lockId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles acquiring a read lock, will delegate it to the parent if there are any.
|
||||
/// </summary>
|
||||
/// <param name="timeout">Optional database timeout in milliseconds.</param>
|
||||
/// <param name="lockIds">Array of lock object identifiers.</param>
|
||||
internal void ReadLockInner(TimeSpan? timeout = null, params int[] lockIds)
|
||||
{
|
||||
if (ParentScope != null)
|
||||
{
|
||||
// Delegate acquiring the lock to the parent if any.
|
||||
ParentScope.ReadLockInner(timeout, lockIds);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are the parent, then handle the lock request.
|
||||
foreach (var lockId in lockIds)
|
||||
{
|
||||
lock (_dictionaryLocker)
|
||||
{
|
||||
// Only acquire the lock if we haven't done so yet.
|
||||
if (!ReadLocks.ContainsKey(lockId))
|
||||
{
|
||||
if (timeout is null)
|
||||
{
|
||||
// We want a lock with a custom timeout
|
||||
ObtainReadLock(lockId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We just want an ordinary lock.
|
||||
ObtainTimoutReadLock(lockId, timeout.Value);
|
||||
}
|
||||
// Add the lockId as a key to the dict.
|
||||
ReadLocks[lockId] = 0;
|
||||
}
|
||||
|
||||
ReadLocks[lockId] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles acquiring a write lock with a specified timeout, will delegate it to the parent if there are any.
|
||||
/// </summary>
|
||||
/// <param name="timeout">Optional database timeout in milliseconds.</param>
|
||||
/// <param name="lockIds">Array of lock object identifiers.</param>
|
||||
internal void WriteLockInner(TimeSpan? timeout = null, params int[] lockIds)
|
||||
{
|
||||
if (ParentScope != null)
|
||||
{
|
||||
// If we have a parent we delegate lock creation to parent.
|
||||
ParentScope.WriteLockInner(timeout, lockIds);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var lockId in lockIds)
|
||||
{
|
||||
lock (_dictionaryLocker)
|
||||
{
|
||||
// Only acquire lock if we haven't yet (WriteLocks not containing the key)
|
||||
if (!WriteLocks.ContainsKey(lockId))
|
||||
{
|
||||
if (timeout is null)
|
||||
{
|
||||
ObtainWriteLock(lockId);
|
||||
}
|
||||
else
|
||||
{
|
||||
ObtainTimeoutWriteLock(lockId, timeout.Value);
|
||||
}
|
||||
// Add the lockId as a key to the dict.
|
||||
WriteLocks[lockId] = 0;
|
||||
}
|
||||
|
||||
// Increment count of the lock by 1.
|
||||
WriteLocks[lockId] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains an ordinary read lock.
|
||||
/// </summary>
|
||||
/// <param name="lockId">Lock object identifier to lock.</param>
|
||||
private void ObtainReadLock(int lockId)
|
||||
{
|
||||
Database.SqlContext.SqlSyntax.ReadLock(Database, lockId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains a read lock with a custom timeout.
|
||||
/// </summary>
|
||||
/// <param name="lockId">Lock object identifier to lock.</param>
|
||||
/// <param name="timeout">TimeSpan specifying the timout period.</param>
|
||||
private void ObtainTimoutReadLock(int lockId, TimeSpan timeout)
|
||||
{
|
||||
var syntax2 = Database.SqlContext.SqlSyntax as ISqlSyntaxProvider2;
|
||||
if (syntax2 == null)
|
||||
{
|
||||
throw new InvalidOperationException($"{Database.SqlContext.SqlSyntax.GetType()} is not of type {typeof(ISqlSyntaxProvider2)}");
|
||||
}
|
||||
|
||||
syntax2.ReadLock(Database, timeout, lockId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains an ordinary write lock.
|
||||
/// </summary>
|
||||
/// <param name="lockId">Lock object identifier to lock.</param>
|
||||
private void ObtainWriteLock(int lockId)
|
||||
{
|
||||
Database.SqlContext.SqlSyntax.WriteLock(Database, lockId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains a write lock with a custom timeout.
|
||||
/// </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)
|
||||
{
|
||||
var syntax2 = Database.SqlContext.SqlSyntax as ISqlSyntaxProvider2;
|
||||
if (syntax2 == null)
|
||||
{
|
||||
throw new InvalidOperationException($"{Database.SqlContext.SqlSyntax.GetType()} is not of type {typeof(ISqlSyntaxProvider2)}");
|
||||
}
|
||||
|
||||
syntax2.WriteLock(Database, timeout, lockId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlServerCe;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -280,7 +279,7 @@ namespace Umbraco.Tests.Persistence
|
||||
|
||||
[Test]
|
||||
public void Throws_When_Lock_Timeout_Is_Exceeded()
|
||||
{
|
||||
{
|
||||
var t1 = Task.Run(() =>
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
@@ -288,7 +287,7 @@ namespace Umbraco.Tests.Persistence
|
||||
var realScope = (Scope)scope;
|
||||
|
||||
Console.WriteLine("Write lock A");
|
||||
// This will acquire right away
|
||||
// This will acquire right away
|
||||
realScope.WriteLock(TimeSpan.FromMilliseconds(2000), Constants.Locks.ContentTree);
|
||||
Thread.Sleep(6000); // Wait longer than the Read Lock B timeout
|
||||
scope.Complete();
|
||||
@@ -349,7 +348,7 @@ namespace Umbraco.Tests.Persistence
|
||||
var realScope = (Scope)scope;
|
||||
|
||||
Console.WriteLine("Write lock A");
|
||||
// This will acquire right away
|
||||
// This will acquire right away
|
||||
realScope.WriteLock(TimeSpan.FromMilliseconds(2000), Constants.Locks.ContentTree);
|
||||
Thread.Sleep(4000); // Wait less than the Read Lock B timeout
|
||||
scope.Complete();
|
||||
@@ -377,7 +376,7 @@ namespace Umbraco.Tests.Persistence
|
||||
scope.Complete();
|
||||
Interlocked.Increment(ref locksCompleted);
|
||||
Console.WriteLine("Finished Read lock B");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var t3 = Task.Run(() =>
|
||||
|
||||
338
src/Umbraco.Tests/Scoping/ScopeUnitTests.cs
Normal file
338
src/Umbraco.Tests/Scoping/ScopeUnitTests.cs
Normal file
@@ -0,0 +1,338 @@
|
||||
using System;
|
||||
using Moq;
|
||||
using NPoco;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Tests.Scoping
|
||||
{
|
||||
[TestFixture]
|
||||
public class ScopeUnitTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a ScopeProvider with mocked internals.
|
||||
/// </summary>
|
||||
/// <param name="syntaxProviderMock">The mock of the ISqlSyntaxProvider2, used to count method calls.</param>
|
||||
/// <returns></returns>
|
||||
private ScopeProvider GetScopeProvider(out Mock<ISqlSyntaxProvider2> syntaxProviderMock)
|
||||
{
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var fac = Mock.Of<IFactory>();
|
||||
var fileSystem = new FileSystems(fac, logger);
|
||||
var databaseFactory = new Mock<IUmbracoDatabaseFactory>();
|
||||
var database = new Mock<IUmbracoDatabase>();
|
||||
var sqlContext = new Mock<ISqlContext>();
|
||||
syntaxProviderMock = new Mock<ISqlSyntaxProvider2>();
|
||||
|
||||
// Setup mock of database factory to return mock of database.
|
||||
databaseFactory.Setup(x => x.CreateDatabase()).Returns(database.Object);
|
||||
|
||||
// Setup mock of database to return mock of sql SqlContext
|
||||
database.Setup(x => x.SqlContext).Returns(sqlContext.Object);
|
||||
|
||||
// Setup mock of ISqlContext to return syntaxProviderMock
|
||||
sqlContext.Setup(x => x.SqlSyntax).Returns(syntaxProviderMock.Object);
|
||||
|
||||
return new ScopeProvider(databaseFactory.Object, fileSystem, logger);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WriteLock_Acquired_Only_Once_Per_Key()
|
||||
{
|
||||
var scopeProvider = GetScopeProvider(out var syntaxProviderMock);
|
||||
|
||||
using (var outerScope = scopeProvider.CreateScope())
|
||||
{
|
||||
outerScope.WriteLock(Constants.Locks.Domains);
|
||||
outerScope.WriteLock(Constants.Locks.Languages);
|
||||
|
||||
using (var innerScope1 = scopeProvider.CreateScope())
|
||||
{
|
||||
innerScope1.WriteLock(Constants.Locks.Domains);
|
||||
innerScope1.WriteLock(Constants.Locks.Languages);
|
||||
|
||||
using (var innerScope2 = scopeProvider.CreateScope())
|
||||
{
|
||||
innerScope2.WriteLock(Constants.Locks.Domains);
|
||||
innerScope2.WriteLock(Constants.Locks.Languages);
|
||||
innerScope2.Complete();
|
||||
}
|
||||
innerScope1.Complete();
|
||||
}
|
||||
outerScope.Complete();
|
||||
}
|
||||
|
||||
syntaxProviderMock.Verify(x => x.WriteLock(It.IsAny<IDatabase>(), Constants.Locks.Domains), Times.Once);
|
||||
syntaxProviderMock.Verify(x => x.WriteLock(It.IsAny<IDatabase>(), Constants.Locks.Languages), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
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())
|
||||
{
|
||||
var realScope = (Scope) outerScope;
|
||||
realScope.WriteLock(timeout, Constants.Locks.Domains);
|
||||
realScope.WriteLock(timeout, Constants.Locks.Languages);
|
||||
|
||||
using (var innerScope1 = scopeProvider.CreateScope())
|
||||
{
|
||||
var realInnerScope1 = (Scope) outerScope;
|
||||
realInnerScope1.WriteLock(timeout, Constants.Locks.Domains);
|
||||
realInnerScope1.WriteLock(timeout, Constants.Locks.Languages);
|
||||
|
||||
using (var innerScope2 = scopeProvider.CreateScope())
|
||||
{
|
||||
var realInnerScope2 = (Scope) innerScope2;
|
||||
realInnerScope2.WriteLock(timeout, Constants.Locks.Domains);
|
||||
realInnerScope2.WriteLock(timeout, Constants.Locks.Languages);
|
||||
innerScope2.Complete();
|
||||
}
|
||||
innerScope1.Complete();
|
||||
}
|
||||
|
||||
outerScope.Complete();
|
||||
}
|
||||
|
||||
syntaxProviderMock.Verify(x => x.WriteLock(It.IsAny<IDatabase>(), timeout, Constants.Locks.Domains), Times.Once);
|
||||
syntaxProviderMock.Verify(x => x.WriteLock(It.IsAny<IDatabase>(), timeout, Constants.Locks.Languages), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadLock_Acquired_Only_Once_Per_Key()
|
||||
{
|
||||
var scopeProvider = GetScopeProvider(out var syntaxProviderMock);
|
||||
|
||||
using (var outerScope = scopeProvider.CreateScope())
|
||||
{
|
||||
outerScope.ReadLock(Constants.Locks.Domains);
|
||||
outerScope.ReadLock(Constants.Locks.Languages);
|
||||
|
||||
using (var innerScope1 = scopeProvider.CreateScope())
|
||||
{
|
||||
innerScope1.ReadLock(Constants.Locks.Domains);
|
||||
innerScope1.ReadLock(Constants.Locks.Languages);
|
||||
|
||||
using (var innerScope2 = scopeProvider.CreateScope())
|
||||
{
|
||||
innerScope2.ReadLock(Constants.Locks.Domains);
|
||||
innerScope2.ReadLock(Constants.Locks.Languages);
|
||||
|
||||
innerScope2.Complete();
|
||||
}
|
||||
|
||||
innerScope1.Complete();
|
||||
}
|
||||
|
||||
outerScope.Complete();
|
||||
}
|
||||
|
||||
syntaxProviderMock.Verify(x => x.ReadLock(It.IsAny<IDatabase>(), Constants.Locks.Domains), Times.Once);
|
||||
syntaxProviderMock.Verify(x => x.ReadLock(It.IsAny<IDatabase>(), Constants.Locks.Languages), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadLock_With_Timeout_Acquired_Only_Once_Per_Key()
|
||||
{
|
||||
var scopeProvider = GetScopeProvider(out var syntaxProviderMock);
|
||||
var timeOut = TimeSpan.FromMilliseconds(10000);
|
||||
|
||||
using (var outerScope = scopeProvider.CreateScope())
|
||||
{
|
||||
var realOuterScope = (Scope) outerScope;
|
||||
realOuterScope.ReadLock(timeOut, Constants.Locks.Domains);
|
||||
realOuterScope.ReadLock(timeOut, Constants.Locks.Languages);
|
||||
|
||||
using (var innerScope1 = scopeProvider.CreateScope())
|
||||
{
|
||||
var realInnerScope1 = (Scope) innerScope1;
|
||||
realInnerScope1.ReadLock(timeOut, Constants.Locks.Domains);
|
||||
realInnerScope1.ReadLock(timeOut, Constants.Locks.Languages);
|
||||
|
||||
using (var innerScope2 = scopeProvider.CreateScope())
|
||||
{
|
||||
var realInnerScope2 = (Scope) innerScope2;
|
||||
realInnerScope2.ReadLock(timeOut, Constants.Locks.Domains);
|
||||
realInnerScope2.ReadLock(timeOut, Constants.Locks.Languages);
|
||||
|
||||
innerScope2.Complete();
|
||||
}
|
||||
|
||||
innerScope1.Complete();
|
||||
}
|
||||
|
||||
outerScope.Complete();
|
||||
}
|
||||
|
||||
syntaxProviderMock.Verify(x => x.ReadLock(It.IsAny<IDatabase>(), timeOut, Constants.Locks.Domains), Times.Once);
|
||||
syntaxProviderMock.Verify(x => x.ReadLock(It.IsAny<IDatabase>(), timeOut, Constants.Locks.Languages), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WriteLocks_Count_correctly_If_Lock_Requested_Twice_In_Scope()
|
||||
{
|
||||
var scopeProvider = GetScopeProvider(out var syntaxProviderMock);
|
||||
|
||||
using (var outerscope = scopeProvider.CreateScope())
|
||||
{
|
||||
var realOuterScope = (Scope) outerscope;
|
||||
outerscope.WriteLock(Constants.Locks.ContentTree);
|
||||
outerscope.WriteLock(Constants.Locks.ContentTree);
|
||||
Assert.AreEqual(2, realOuterScope.WriteLocks[Constants.Locks.ContentTree]);
|
||||
|
||||
using (var innerScope = scopeProvider.CreateScope())
|
||||
{
|
||||
innerScope.WriteLock(Constants.Locks.ContentTree);
|
||||
innerScope.WriteLock(Constants.Locks.ContentTree);
|
||||
Assert.AreEqual(4, realOuterScope.WriteLocks[Constants.Locks.ContentTree]);
|
||||
|
||||
innerScope.WriteLock(Constants.Locks.Languages);
|
||||
innerScope.WriteLock(Constants.Locks.Languages);
|
||||
Assert.AreEqual(2, realOuterScope.WriteLocks[Constants.Locks.Languages]);
|
||||
innerScope.Complete();
|
||||
}
|
||||
Assert.AreEqual(0, realOuterScope.WriteLocks[Constants.Locks.Languages]);
|
||||
Assert.AreEqual(2, realOuterScope.WriteLocks[Constants.Locks.ContentTree]);
|
||||
outerscope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadLocks_Count_correctly_If_Lock_Requested_Twice_In_Scope()
|
||||
{
|
||||
var scopeProvider = GetScopeProvider(out var syntaxProviderMock);
|
||||
|
||||
using (var outerscope = scopeProvider.CreateScope())
|
||||
{
|
||||
var realOuterScope = (Scope) outerscope;
|
||||
outerscope.ReadLock(Constants.Locks.ContentTree);
|
||||
outerscope.ReadLock(Constants.Locks.ContentTree);
|
||||
Assert.AreEqual(2, realOuterScope.ReadLocks[Constants.Locks.ContentTree]);
|
||||
|
||||
using (var innerScope = scopeProvider.CreateScope())
|
||||
{
|
||||
innerScope.ReadLock(Constants.Locks.ContentTree);
|
||||
innerScope.ReadLock(Constants.Locks.ContentTree);
|
||||
Assert.AreEqual(4, realOuterScope.ReadLocks[Constants.Locks.ContentTree]);
|
||||
|
||||
innerScope.ReadLock(Constants.Locks.Languages);
|
||||
innerScope.ReadLock(Constants.Locks.Languages);
|
||||
Assert.AreEqual(2, realOuterScope.ReadLocks[Constants.Locks.Languages]);
|
||||
innerScope.Complete();
|
||||
}
|
||||
Assert.AreEqual(0, realOuterScope.ReadLocks[Constants.Locks.Languages]);
|
||||
Assert.AreEqual(2, realOuterScope.ReadLocks[Constants.Locks.ContentTree]);
|
||||
outerscope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Nested_Scopes_WriteLocks_Count_Correctly()
|
||||
{
|
||||
var scopeProvider = GetScopeProvider(out var syntaxProviderMock);
|
||||
|
||||
using (var outerScope = scopeProvider.CreateScope())
|
||||
{
|
||||
var parentScope = (Scope) outerScope;
|
||||
outerScope.WriteLock(Constants.Locks.ContentTree);
|
||||
outerScope.WriteLock(Constants.Locks.ContentTypes);
|
||||
|
||||
Assert.AreEqual(1, parentScope.WriteLocks[Constants.Locks.ContentTree], $"parentScope after locks acquired: {nameof(Constants.Locks.ContentTree)}");
|
||||
Assert.AreEqual(1, parentScope.WriteLocks[Constants.Locks.ContentTypes], $"parentScope after locks acquired: {nameof(Constants.Locks.ContentTypes)}");
|
||||
|
||||
using (var innerScope1 = scopeProvider.CreateScope())
|
||||
{
|
||||
innerScope1.WriteLock(Constants.Locks.ContentTree);
|
||||
innerScope1.WriteLock(Constants.Locks.ContentTypes);
|
||||
innerScope1.WriteLock(Constants.Locks.Languages);
|
||||
|
||||
Assert.AreEqual(2, parentScope.WriteLocks[Constants.Locks.ContentTree], $"childScope1 after locks acquired: {nameof(Constants.Locks.ContentTree)}");
|
||||
Assert.AreEqual(2, parentScope.WriteLocks[Constants.Locks.ContentTypes], $"childScope1 after locks acquired: {nameof(Constants.Locks.ContentTypes)}");
|
||||
Assert.AreEqual(1, parentScope.WriteLocks[Constants.Locks.Languages], $"childScope1 after locks acquired: {nameof(Constants.Locks.Languages)}");
|
||||
|
||||
using (var innerScope2 = scopeProvider.CreateScope())
|
||||
{
|
||||
innerScope2.WriteLock(Constants.Locks.ContentTree);
|
||||
innerScope2.WriteLock(Constants.Locks.MediaTypes);
|
||||
|
||||
Assert.AreEqual(3, parentScope.WriteLocks[Constants.Locks.ContentTree], $"childScope2 after locks acquired: {nameof(Constants.Locks.ContentTree)}");
|
||||
Assert.AreEqual(2, parentScope.WriteLocks[Constants.Locks.ContentTypes], $"childScope2 after locks acquired: {nameof(Constants.Locks.ContentTypes)}");
|
||||
Assert.AreEqual(1, parentScope.WriteLocks[Constants.Locks.Languages], $"childScope2 after locks acquired: {nameof(Constants.Locks.Languages)}");
|
||||
Assert.AreEqual(1, parentScope.WriteLocks[Constants.Locks.MediaTypes], $"childScope2 after locks acquired: {nameof(Constants.Locks.MediaTypes)}");
|
||||
|
||||
innerScope2.Complete();
|
||||
}
|
||||
Assert.AreEqual(2, parentScope.WriteLocks[Constants.Locks.ContentTree], $"childScope1 after inner scope disposed: {nameof(Constants.Locks.ContentTree)}");
|
||||
Assert.AreEqual(2, parentScope.WriteLocks[Constants.Locks.ContentTypes], $"childScope1 after inner scope disposed: {nameof(Constants.Locks.ContentTypes)}");
|
||||
Assert.AreEqual(1, parentScope.WriteLocks[Constants.Locks.Languages], $"childScope1 after inner scope disposed: {nameof(Constants.Locks.Languages)}");
|
||||
Assert.AreEqual(0, parentScope.WriteLocks[Constants.Locks.MediaTypes], $"childScope1 after inner scope disposed: {nameof(Constants.Locks.MediaTypes)}");
|
||||
|
||||
innerScope1.Complete();
|
||||
}
|
||||
Assert.AreEqual(1, parentScope.WriteLocks[Constants.Locks.ContentTree], $"parentScope after inner scopes disposed: {nameof(Constants.Locks.ContentTree)}");
|
||||
Assert.AreEqual(1, parentScope.WriteLocks[Constants.Locks.ContentTypes], $"parentScope after inner scopes disposed: {nameof(Constants.Locks.ContentTypes)}");
|
||||
Assert.AreEqual(0, parentScope.WriteLocks[Constants.Locks.Languages], $"parentScope after inner scopes disposed: {nameof(Constants.Locks.Languages)}");
|
||||
Assert.AreEqual(0, parentScope.WriteLocks[Constants.Locks.MediaTypes], $"parentScope after inner scopes disposed: {nameof(Constants.Locks.MediaTypes)}");
|
||||
|
||||
outerScope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Nested_Scopes_ReadLocks_Count_Correctly()
|
||||
{
|
||||
var scopeProvider = GetScopeProvider(out var syntaxProviderMock);
|
||||
|
||||
using (var outerScope = scopeProvider.CreateScope())
|
||||
{
|
||||
var parentScope = (Scope) outerScope;
|
||||
outerScope.ReadLock(Constants.Locks.ContentTree);
|
||||
outerScope.ReadLock(Constants.Locks.ContentTypes);
|
||||
Assert.AreEqual(1, parentScope.ReadLocks[Constants.Locks.ContentTree], $"parentScope after locks acquired: {nameof(Constants.Locks.ContentTree)}");
|
||||
Assert.AreEqual(1, parentScope.ReadLocks[Constants.Locks.ContentTypes], $"parentScope after locks acquired: {nameof(Constants.Locks.ContentTypes)}");
|
||||
|
||||
using (var innserScope1 = scopeProvider.CreateScope())
|
||||
{
|
||||
innserScope1.ReadLock(Constants.Locks.ContentTree);
|
||||
innserScope1.ReadLock(Constants.Locks.ContentTypes);
|
||||
innserScope1.ReadLock(Constants.Locks.Languages);
|
||||
Assert.AreEqual(2, parentScope.ReadLocks[Constants.Locks.ContentTree], $"childScope1 after locks acquired: {nameof(Constants.Locks.ContentTree)}");
|
||||
Assert.AreEqual(2, parentScope.ReadLocks[Constants.Locks.ContentTypes], $"childScope1 after locks acquired: {nameof(Constants.Locks.ContentTypes)}");
|
||||
Assert.AreEqual(1, parentScope.ReadLocks[Constants.Locks.Languages], $"childScope1 after locks acquired: {nameof(Constants.Locks.Languages)}");
|
||||
|
||||
using (var innerScope2 = scopeProvider.CreateScope())
|
||||
{
|
||||
innerScope2.ReadLock(Constants.Locks.ContentTree);
|
||||
innerScope2.ReadLock(Constants.Locks.MediaTypes);
|
||||
Assert.AreEqual(3, parentScope.ReadLocks[Constants.Locks.ContentTree], $"childScope2 after locks acquired: {nameof(Constants.Locks.ContentTree)}");
|
||||
Assert.AreEqual(2, parentScope.ReadLocks[Constants.Locks.ContentTypes], $"childScope2 after locks acquired: {nameof(Constants.Locks.ContentTypes)}");
|
||||
Assert.AreEqual(1, parentScope.ReadLocks[Constants.Locks.Languages], $"childScope2 after locks acquired: {nameof(Constants.Locks.Languages)}");
|
||||
Assert.AreEqual(1, parentScope.ReadLocks[Constants.Locks.MediaTypes], $"childScope2 after locks acquired: {nameof(Constants.Locks.MediaTypes)}");
|
||||
|
||||
innerScope2.Complete();
|
||||
}
|
||||
Assert.AreEqual(2, parentScope.ReadLocks[Constants.Locks.ContentTree], $"childScope1 after inner scope disposed: {nameof(Constants.Locks.ContentTree)}");
|
||||
Assert.AreEqual(2, parentScope.ReadLocks[Constants.Locks.ContentTypes], $"childScope1 after inner scope disposed: {nameof(Constants.Locks.ContentTypes)}");
|
||||
Assert.AreEqual(1, parentScope.ReadLocks[Constants.Locks.Languages], $"childScope1 after inner scope disposed: {nameof(Constants.Locks.Languages)}");
|
||||
Assert.AreEqual(0, parentScope.ReadLocks[Constants.Locks.MediaTypes], $"childScope1 after inner scope disposed: {nameof(Constants.Locks.MediaTypes)}");
|
||||
|
||||
innserScope1.Complete();
|
||||
}
|
||||
Assert.AreEqual(1, parentScope.ReadLocks[Constants.Locks.ContentTree], $"parentScope after inner scopes disposed: {nameof(Constants.Locks.ContentTree)}");
|
||||
Assert.AreEqual(1, parentScope.ReadLocks[Constants.Locks.ContentTypes], $"parentScope after inner scopes disposed: {nameof(Constants.Locks.ContentTypes)}");
|
||||
Assert.AreEqual(0, parentScope.ReadLocks[Constants.Locks.Languages], $"parentScope after inner scopes disposed: {nameof(Constants.Locks.Languages)}");
|
||||
Assert.AreEqual(0, parentScope.ReadLocks[Constants.Locks.MediaTypes], $"parentScope after inner scopes disposed: {nameof(Constants.Locks.MediaTypes)}");
|
||||
|
||||
outerScope.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,6 +163,7 @@
|
||||
<Compile Include="Routing\RoutableDocumentFilterTests.cs" />
|
||||
<Compile Include="Runtimes\StandaloneTests.cs" />
|
||||
<Compile Include="Routing\GetContentUrlsTests.cs" />
|
||||
<Compile Include="Scoping\ScopeUnitTests.cs" />
|
||||
<Compile Include="Services\AmbiguousEventTests.cs" />
|
||||
<Compile Include="Services\ContentServiceEventTests.cs" />
|
||||
<Compile Include="Services\ContentServicePublishBranchTests.cs" />
|
||||
|
||||
@@ -347,9 +347,9 @@
|
||||
<WebProjectProperties>
|
||||
<UseIIS>False</UseIIS>
|
||||
<AutoAssignPort>True</AutoAssignPort>
|
||||
<DevelopmentServerPort>8120</DevelopmentServerPort>
|
||||
<DevelopmentServerPort>8121</DevelopmentServerPort>
|
||||
<DevelopmentServerVPath>/</DevelopmentServerVPath>
|
||||
<IISUrl>http://localhost:8120</IISUrl>
|
||||
<IISUrl>http://localhost:8121</IISUrl>
|
||||
<NTLMAuthentication>False</NTLMAuthentication>
|
||||
<UseCustomServer>False</UseCustomServer>
|
||||
<CustomServerUrl>
|
||||
|
||||
Reference in New Issue
Block a user