From c58cffe4a2dc32b99a1394776bd0c7e4b0d810fa Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 24 Oct 2016 14:46:04 +0200 Subject: [PATCH] Add tests for locks --- src/Umbraco.Tests/Persistence/LocksTests.cs | 300 ++++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 2 files changed, 301 insertions(+) create mode 100644 src/Umbraco.Tests/Persistence/LocksTests.cs diff --git a/src/Umbraco.Tests/Persistence/LocksTests.cs b/src/Umbraco.Tests/Persistence/LocksTests.cs new file mode 100644 index 0000000000..e973b14614 --- /dev/null +++ b/src/Umbraco.Tests/Persistence/LocksTests.cs @@ -0,0 +1,300 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SqlServerCe; +using System.Threading; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Events; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Publishing; +using Umbraco.Core.Services; +using Umbraco.Tests.Services; +using Umbraco.Tests.TestHelpers; +using Ignore = NUnit.Framework.IgnoreAttribute; + +namespace Umbraco.Tests.Persistence +{ + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [TestFixture] + [Ignore("Takes too much time.")] + public class LocksTests : BaseDatabaseFactoryTest + { + private ThreadSafetyServiceTest.PerThreadPetaPocoUnitOfWorkProvider _uowProvider; + private ThreadSafetyServiceTest.PerThreadDatabaseFactory _dbFactory; + + [SetUp] + public override void Initialize() + { + base.Initialize(); + + //we need to use our own custom IDatabaseFactory for the DatabaseContext because we MUST ensure that + //a Database instance is created per thread, whereas the default implementation which will work in an HttpContext + //threading environment, or a single apartment threading environment will not work for this test because + //it is multi-threaded. + _dbFactory = new ThreadSafetyServiceTest.PerThreadDatabaseFactory(Logger); + //overwrite the local object + ApplicationContext.DatabaseContext = new DatabaseContext(_dbFactory, Logger, new SqlCeSyntaxProvider(), Constants.DatabaseProviders.SqlCe); + + //disable cache + var cacheHelper = CacheHelper.CreateDisabledCacheHelper(); + + //here we are going to override the ServiceContext because normally with our test cases we use a + //global Database object but this is NOT how it should work in the web world or in any multi threaded scenario. + //we need a new Database object for each thread. + var repositoryFactory = new RepositoryFactory(cacheHelper, Logger, SqlSyntax, SettingsForTests.GenerateMockSettings()); + _uowProvider = new ThreadSafetyServiceTest.PerThreadPetaPocoUnitOfWorkProvider(_dbFactory); + var evtMsgs = new TransientMessagesFactory(); + ApplicationContext.Services = new ServiceContext( + repositoryFactory, + _uowProvider, + new FileUnitOfWorkProvider(), + new PublishingStrategy(evtMsgs, Logger), + cacheHelper, + Logger, + evtMsgs); + + // create a few lock objects + var database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + try + { + database.Execute("SET IDENTITY_INSERT umbracoLock ON"); + database.Insert("umbracoLock", "id", false, new LockDto { Id = 1, Name = "Lock.1" }); + database.Insert("umbracoLock", "id", false, new LockDto { Id = 2, Name = "Lock.2" }); + database.Insert("umbracoLock", "id", false, new LockDto { Id = 3, Name = "Lock.3" }); + database.Execute("SET IDENTITY_INSERT umbracoLock OFF"); + database.CompleteTransaction(); + } + catch + { + database.AbortTransaction(); + } + } + + [TearDown] + public override void TearDown() + { + //dispose! + _dbFactory.Dispose(); + _uowProvider.Dispose(); + + base.TearDown(); + } + + [Test] + public void Test() + { + var database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + try + { + database.AcquireLockNodeReadLock(Constants.Locks.Servers); + } + finally + { + database.CompleteTransaction(); + } + } + + [Test] + public void ConcurrentReadersTest() + { + var threads = new List(); + var locker = new object(); + var acquired = 0; + var maxAcquired = 0; + for (var i = 0; i < 5; i++) + { + threads.Add(new Thread(() => + { + var database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + try + { + database.AcquireLockNodeReadLock(Constants.Locks.Servers); + lock (locker) + { + acquired++; + } + Thread.Sleep(500); + lock (locker) + { + if (maxAcquired < acquired) maxAcquired = acquired; + } + Thread.Sleep(500); + lock (locker) + { + acquired--; + } + } + finally + { + database.CompleteTransaction(); + } + })); + } + foreach (var thread in threads) thread.Start(); + foreach (var thread in threads) thread.Join(); + Assert.AreEqual(5, maxAcquired); + } + + [Test] + public void ConcurrentWritersTest() + { + var threads = new List(); + var locker = new object(); + var acquired = 0; + var maxAcquired = 0; + for (var i = 0; i < 5; i++) + { + threads.Add(new Thread(() => + { + var database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + try + { + database.AcquireLockNodeWriteLock(Constants.Locks.Servers); + lock (locker) + { + acquired++; + } + Thread.Sleep(500); + lock (locker) + { + if (maxAcquired < acquired) maxAcquired = acquired; + } + Thread.Sleep(500); + lock (locker) + { + acquired--; + } + } + finally + { + database.CompleteTransaction(); + } + })); + } + foreach (var thread in threads) thread.Start(); + foreach (var thread in threads) thread.Join(); + Assert.AreEqual(1, maxAcquired); + } + + [Test] + public void DeadLockTest() + { + Exception e1 = null, e2 = null; + + var thread1 = new Thread(() => + { + var database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + try + { + database.AcquireLockNodeWriteLock(1); + Thread.Sleep(1000); + database.AcquireLockNodeWriteLock(2); + Thread.Sleep(1000); + } + catch (Exception e) + { + e1 = e; + } + finally + { + database.CompleteTransaction(); + } + }); + var thread2 = new Thread(() => + { + var database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + try + { + database.AcquireLockNodeWriteLock(2); + Thread.Sleep(1000); + database.AcquireLockNodeWriteLock(1); + Thread.Sleep(1000); + } + catch (Exception e) + { + e2 = e; + } + finally + { + database.CompleteTransaction(); + } + }); + thread1.Start(); + thread2.Start(); + thread1.Join(); + thread2.Join(); + Assert.IsNotNull(e1); + Assert.IsNotNull(e2); + Assert.IsInstanceOf(e1); + Assert.IsInstanceOf(e2); + } + + [Test] + public void NoDeadLockTest() + { + Exception e1 = null, e2 = null; + + var thread1 = new Thread(() => + { + var database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + try + { + database.AcquireLockNodeWriteLock(1); + var info = database.Query("SELECT * FROM sys.lock_information;"); + Console.WriteLine("LOCKS:"); + foreach (var row in info) + Console.WriteLine($"> {row.request_spid} {row.resource_type} {row.resource_description} {row.request_mode} {row.resource_table} {row.resource_table_id} {row.request_status}"); + Thread.Sleep(6000); + } + catch (Exception e) + { + e1 = e; + } + finally + { + database.CompleteTransaction(); + } + }); + var thread2 = new Thread(() => + { + var database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + try + { + Thread.Sleep(1000); + database.AcquireLockNodeWriteLock(2); + var info = database.Query("SELECT * FROM sys.lock_information;"); + Console.WriteLine("LOCKS:"); + foreach (var row in info) + Console.WriteLine($"> {row.request_spid} {row.resource_type} {row.resource_description} {row.request_mode} {row.resource_table} {row.resource_table_id} {row.request_status}"); + Thread.Sleep(1000); + } + catch (Exception e) + { + e2 = e; + } + finally + { + database.CompleteTransaction(); + } + }); + thread1.Start(); + thread2.Start(); + thread1.Join(); + thread2.Join(); + Assert.IsNull(e1); + Assert.IsNull(e2); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 042fd6ff6f..25ed563e2d 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -176,6 +176,7 @@ +