U4-9322 - manage isolation levels, cleanup tests
This commit is contained in:
@@ -5,14 +5,8 @@ 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.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.Services;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Ignore = NUnit.Framework.IgnoreAttribute;
|
||||
@@ -24,81 +18,32 @@ namespace Umbraco.Tests.Persistence
|
||||
[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);
|
||||
var scopeProvider = new ScopeProvider(_dbFactory);
|
||||
//overwrite the local object
|
||||
ApplicationContext.DatabaseContext = new DatabaseContext(scopeProvider, 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
|
||||
using (var scope = ApplicationContext.ScopeProvider.CreateScope(IsolationLevel.RepeatableRead))
|
||||
{
|
||||
var database = scope.Database;
|
||||
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();
|
||||
scope.Complete();
|
||||
}
|
||||
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
|
||||
using (var scope = ApplicationContext.ScopeProvider.CreateScope(IsolationLevel.RepeatableRead))
|
||||
{
|
||||
var database = scope.Database;
|
||||
database.AcquireLockNodeReadLock(Constants.Locks.Servers);
|
||||
}
|
||||
finally
|
||||
{
|
||||
database.CompleteTransaction();
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,40 +53,39 @@ namespace Umbraco.Tests.Persistence
|
||||
var threads = new List<Thread>();
|
||||
var locker = new object();
|
||||
var acquired = 0;
|
||||
var maxAcquired = 0;
|
||||
var m2 = new ManualResetEventSlim(false);
|
||||
var m1 = new ManualResetEventSlim(false);
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
threads.Add(new Thread(() =>
|
||||
{
|
||||
var database = DatabaseContext.Database;
|
||||
database.BeginTransaction(IsolationLevel.RepeatableRead);
|
||||
try
|
||||
// each thread gets its own scope, because it has its own LCC, hence its own database
|
||||
using (var scope = ApplicationContext.ScopeProvider.CreateScope(IsolationLevel.RepeatableRead))
|
||||
{
|
||||
var database = scope.Database;
|
||||
database.AcquireLockNodeReadLock(Constants.Locks.Servers);
|
||||
lock (locker)
|
||||
{
|
||||
acquired++;
|
||||
if (acquired == 5) m2.Set();
|
||||
}
|
||||
Thread.Sleep(500);
|
||||
lock (locker)
|
||||
{
|
||||
if (maxAcquired < acquired) maxAcquired = acquired;
|
||||
}
|
||||
Thread.Sleep(500);
|
||||
m1.Wait();
|
||||
lock (locker)
|
||||
{
|
||||
acquired--;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
database.CompleteTransaction();
|
||||
scope.Complete();
|
||||
}
|
||||
}));
|
||||
}
|
||||
foreach (var thread in threads) thread.Start();
|
||||
m2.Wait();
|
||||
// all threads have locked in parallel
|
||||
var maxAcquired = acquired;
|
||||
m1.Set();
|
||||
foreach (var thread in threads) thread.Join();
|
||||
Assert.AreEqual(5, maxAcquired);
|
||||
Assert.AreEqual(0, acquired);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -149,41 +93,51 @@ namespace Umbraco.Tests.Persistence
|
||||
{
|
||||
var threads = new List<Thread>();
|
||||
var locker = new object();
|
||||
var entered = 0;
|
||||
var ms = new AutoResetEvent[5];
|
||||
for (var i = 0; i < 5; i++) ms[i] = new AutoResetEvent(false);
|
||||
var m1 = new ManualResetEventSlim(false);
|
||||
var acquired = 0;
|
||||
var maxAcquired = 0;
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
var ic = i;
|
||||
threads.Add(new Thread(() =>
|
||||
{
|
||||
var database = DatabaseContext.Database;
|
||||
database.BeginTransaction(IsolationLevel.RepeatableRead);
|
||||
try
|
||||
// each thread gets its own scope, because it has its own LCC, hence its own database
|
||||
using (var scope = ApplicationContext.ScopeProvider.CreateScope(IsolationLevel.RepeatableRead))
|
||||
{
|
||||
var database = scope.Database;
|
||||
lock (locker)
|
||||
{
|
||||
entered++;
|
||||
if (entered == 5) m1.Set();
|
||||
}
|
||||
ms[ic].WaitOne();
|
||||
database.AcquireLockNodeWriteLock(Constants.Locks.Servers);
|
||||
lock (locker)
|
||||
{
|
||||
acquired++;
|
||||
}
|
||||
Thread.Sleep(500);
|
||||
lock (locker)
|
||||
{
|
||||
if (maxAcquired < acquired) maxAcquired = acquired;
|
||||
}
|
||||
Thread.Sleep(500);
|
||||
ms[ic].WaitOne();
|
||||
lock (locker)
|
||||
{
|
||||
acquired--;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
database.CompleteTransaction();
|
||||
scope.Complete();
|
||||
}
|
||||
}));
|
||||
}
|
||||
foreach (var thread in threads) thread.Start();
|
||||
m1.Wait();
|
||||
// all threads have entered
|
||||
ms[0].Set(); // let 0 go
|
||||
Thread.Sleep(100);
|
||||
for (var i = 1; i < 5; i++) ms[i].Set(); // let others go
|
||||
Thread.Sleep(500);
|
||||
// only 1 thread has locked
|
||||
Assert.AreEqual(1, acquired);
|
||||
for (var i = 0; i < 5; i++) ms[i].Set(); // let all go
|
||||
foreach (var thread in threads) thread.Join();
|
||||
Assert.AreEqual(1, maxAcquired);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -193,52 +147,56 @@ namespace Umbraco.Tests.Persistence
|
||||
|
||||
var thread1 = new Thread(() =>
|
||||
{
|
||||
var database = DatabaseContext.Database;
|
||||
database.BeginTransaction(IsolationLevel.RepeatableRead);
|
||||
try
|
||||
// each thread gets its own scope, because it has its own LCC, hence its own database
|
||||
using (var scope = ApplicationContext.ScopeProvider.CreateScope(IsolationLevel.RepeatableRead))
|
||||
{
|
||||
database.AcquireLockNodeWriteLock(1);
|
||||
Thread.Sleep(1000);
|
||||
database.AcquireLockNodeWriteLock(2);
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e1 = e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
database.CompleteTransaction();
|
||||
var database = scope.Database;
|
||||
Console.WriteLine("Thread 1 db " + database.InstanceSid);
|
||||
try
|
||||
{
|
||||
database.AcquireLockNodeWriteLock(1);
|
||||
Thread.Sleep(100);
|
||||
database.AcquireLockNodeWriteLock(2);
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e1 = e;
|
||||
}
|
||||
scope.Complete();
|
||||
}
|
||||
});
|
||||
var thread2 = new Thread(() =>
|
||||
{
|
||||
var database = DatabaseContext.Database;
|
||||
database.BeginTransaction(IsolationLevel.RepeatableRead);
|
||||
try
|
||||
// each thread gets its own scope, because it has its own LCC, hence its own database
|
||||
using (var scope = ApplicationContext.ScopeProvider.CreateScope(IsolationLevel.RepeatableRead))
|
||||
{
|
||||
database.AcquireLockNodeWriteLock(2);
|
||||
Thread.Sleep(1000);
|
||||
database.AcquireLockNodeWriteLock(1);
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e2 = e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
database.CompleteTransaction();
|
||||
var database = scope.Database;
|
||||
Console.WriteLine("Thread 2 db " + database.InstanceSid);
|
||||
try
|
||||
{
|
||||
database.AcquireLockNodeWriteLock(2);
|
||||
Thread.Sleep(100);
|
||||
database.AcquireLockNodeWriteLock(1);
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e2 = e;
|
||||
}
|
||||
scope.Complete();
|
||||
}
|
||||
});
|
||||
thread1.Start();
|
||||
thread2.Start();
|
||||
thread1.Join();
|
||||
thread2.Join();
|
||||
Assert.IsNotNull(e1);
|
||||
Assert.IsNotNull(e2);
|
||||
Assert.IsInstanceOf<SqlCeLockTimeoutException>(e1);
|
||||
Assert.IsInstanceOf<SqlCeLockTimeoutException>(e2);
|
||||
var oneIsNotNull = e1 != null || e2 != null;
|
||||
Assert.IsTrue(oneIsNotNull);
|
||||
if (e1 != null)
|
||||
Assert.IsInstanceOf<SqlCeLockTimeoutException>(e1);
|
||||
if (e2 != null)
|
||||
Assert.IsInstanceOf<SqlCeLockTimeoutException>(e2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -248,51 +206,51 @@ namespace Umbraco.Tests.Persistence
|
||||
|
||||
var thread1 = new Thread(() =>
|
||||
{
|
||||
var database = DatabaseContext.Database;
|
||||
database.BeginTransaction(IsolationLevel.RepeatableRead);
|
||||
try
|
||||
// each thread gets its own scope, because it has its own LCC, hence its own database
|
||||
using (var scope = ApplicationContext.ScopeProvider.CreateScope(IsolationLevel.RepeatableRead))
|
||||
{
|
||||
database.AcquireLockNodeWriteLock(1);
|
||||
var info = database.Query<dynamic>("SELECT * FROM sys.lock_information;");
|
||||
Console.WriteLine("LOCKS:");
|
||||
foreach (var row in info)
|
||||
Console.WriteLine(string.Format("> {0} {1} {2} {3} {4} {5} {6}", 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 database = scope.Database;
|
||||
try
|
||||
{
|
||||
database.AcquireLockNodeWriteLock(1);
|
||||
var info = database.Query<dynamic>("SELECT * FROM sys.lock_information;");
|
||||
Console.WriteLine("LOCKS:");
|
||||
foreach (var row in info)
|
||||
Console.WriteLine(string.Format("> {0} {1} {2} {3} {4} {5} {6}", 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;
|
||||
}
|
||||
scope.Complete();
|
||||
}
|
||||
});
|
||||
var thread2 = new Thread(() =>
|
||||
{
|
||||
var database = DatabaseContext.Database;
|
||||
database.BeginTransaction(IsolationLevel.RepeatableRead);
|
||||
try
|
||||
// each thread gets its own scope, because it has its own LCC, hence its own database
|
||||
using (var scope = ApplicationContext.ScopeProvider.CreateScope(IsolationLevel.RepeatableRead))
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
database.AcquireLockNodeWriteLock(2);
|
||||
var info = database.Query<dynamic>("SELECT * FROM sys.lock_information;");
|
||||
Console.WriteLine("LOCKS:");
|
||||
foreach (var row in info)
|
||||
Console.WriteLine(string.Format("> {0} {1} {2} {3} {4} {5} {6}", 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();
|
||||
var database = scope.Database;
|
||||
try
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
database.AcquireLockNodeWriteLock(2);
|
||||
var info = database.Query<dynamic>("SELECT * FROM sys.lock_information;");
|
||||
Console.WriteLine("LOCKS:");
|
||||
foreach (var row in info)
|
||||
Console.WriteLine(string.Format("> {0} {1} {2} {3} {4} {5} {6}", 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;
|
||||
}
|
||||
scope.Complete();
|
||||
}
|
||||
});
|
||||
thread1.Start();
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
|
||||
namespace Umbraco.Tests.Persistence.Repositories
|
||||
{
|
||||
[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)]
|
||||
[TestFixture]
|
||||
public class LockedRepositoryTests : BaseDatabaseFactoryTest
|
||||
{
|
||||
private static IServerRegistrationRepository CreateRepository(IDatabaseUnitOfWork uow, ILogger logger, CacheHelper cacheHelper, ISqlSyntaxProvider sqlSyntax)
|
||||
{
|
||||
return new ServerRegistrationRepository(
|
||||
uow,
|
||||
cacheHelper.StaticCache,
|
||||
logger, sqlSyntax);
|
||||
}
|
||||
|
||||
protected override CacheHelper CreateCacheHelper()
|
||||
{
|
||||
// ServerRegistrationRepository wants a real cache else weird things happen
|
||||
//return CacheHelper.CreateDisabledCacheHelper();
|
||||
return new CacheHelper(
|
||||
new ObjectCacheRuntimeCacheProvider(),
|
||||
new StaticCacheProvider(),
|
||||
new NullCacheProvider(),
|
||||
new IsolatedRuntimeCache(type => new ObjectCacheRuntimeCacheProvider()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NoOuterScopeJustWorks()
|
||||
{
|
||||
var uowProvider = new PetaPocoUnitOfWorkProvider(Logger);
|
||||
var sqlSyntax = ApplicationContext.DatabaseContext.SqlSyntax;
|
||||
|
||||
var lrepo = new LockingRepository<IServerRegistrationRepository>(uowProvider,
|
||||
x => CreateRepository(x, Logger, CacheHelper, sqlSyntax),
|
||||
new[] { Constants.Locks.Servers }, new[] { Constants.Locks.Servers });
|
||||
|
||||
IServerRegistration reg = null;
|
||||
|
||||
lrepo.WithWriteLocked(xrepo =>
|
||||
{
|
||||
xrepo.Repository.AddOrUpdate(reg = new ServerRegistration("a1234", "i1234", DateTime.Now));
|
||||
|
||||
// no need - autocommit by default
|
||||
//xrepo.UnitOfWork.Commit();
|
||||
});
|
||||
|
||||
Assert.IsNull(((ScopeProvider) ApplicationContext.ScopeProvider).AmbientScope);
|
||||
|
||||
Assert.AreNotEqual(0, reg.Id);
|
||||
|
||||
// that's cheating somehow because it will not really hit the DB because of the cache
|
||||
var reg2 = lrepo.WithReadLocked(xrepo =>
|
||||
{
|
||||
return xrepo.Repository.Get(reg.Id);
|
||||
});
|
||||
|
||||
Assert.IsNull(((ScopeProvider) ApplicationContext.ScopeProvider).AmbientScope);
|
||||
|
||||
Assert.IsNotNull(reg2);
|
||||
Assert.AreEqual("a1234", reg2.ServerAddress);
|
||||
Assert.AreEqual("i1234", reg2.ServerIdentity);
|
||||
|
||||
// this really makes sure there's something in database
|
||||
using (var scope = ApplicationContext.ScopeProvider.CreateScope())
|
||||
{
|
||||
var reg3 = scope.Database.Fetch<dynamic>("SELECT * FROM umbracoServer WHERE id=@id", new { id = reg2.Id }).FirstOrDefault();
|
||||
Assert.IsNotNull(reg3);
|
||||
Assert.AreEqual("a1234", reg3.address);
|
||||
Assert.AreEqual("i1234", reg3.computerName);
|
||||
}
|
||||
|
||||
Assert.IsNull(((ScopeProvider) ApplicationContext.ScopeProvider).AmbientScope);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OuterScopeBadIsolationLevel()
|
||||
{
|
||||
var uowProvider = new PetaPocoUnitOfWorkProvider(Logger);
|
||||
var sqlSyntax = ApplicationContext.DatabaseContext.SqlSyntax;
|
||||
|
||||
var lrepo = new LockingRepository<IServerRegistrationRepository>(uowProvider,
|
||||
x => CreateRepository(x, Logger, CacheHelper, sqlSyntax),
|
||||
new[] { Constants.Locks.Servers }, new[] { Constants.Locks.Servers });
|
||||
|
||||
// this creates a IsolationLevel.Unspecified scope
|
||||
using (var scope = ApplicationContext.ScopeProvider.CreateScope())
|
||||
{
|
||||
// so outer scope creates IsolationLevel.ReadCommitted (default) transaction
|
||||
// then WithReadLocked creates a IsolationLevel.RepeatableRead scope, which
|
||||
// fails - levels conflict
|
||||
|
||||
try
|
||||
{
|
||||
lrepo.WithReadLocked(xrepo =>
|
||||
{
|
||||
xrepo.Repository.DeactiveStaleServers(TimeSpan.Zero);
|
||||
});
|
||||
Assert.Fail("Expected: Exception.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.AreEqual("Scope requires isolation level RepeatableRead, but got ReadCommitted from parent.", e.Message);
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OuterScopeGoodIsolationLevel()
|
||||
{
|
||||
var uowProvider = new PetaPocoUnitOfWorkProvider(Logger);
|
||||
var sqlSyntax = ApplicationContext.DatabaseContext.SqlSyntax;
|
||||
|
||||
var lrepo = new LockingRepository<IServerRegistrationRepository>(uowProvider,
|
||||
x => CreateRepository(x, Logger, CacheHelper, sqlSyntax),
|
||||
new[] { Constants.Locks.Servers }, new[] { Constants.Locks.Servers });
|
||||
|
||||
// this creates a IsolationLevel.RepeatableRead scope
|
||||
using (var scope = ApplicationContext.ScopeProvider.CreateScope(IsolationLevel.RepeatableRead))
|
||||
{
|
||||
// so outer scope creates IsolationLevel.RepeatableRead transaction
|
||||
// then WithReadLocked creates a IsolationLevel.RepeatableRead scope, which
|
||||
// suceeds - no level conflict
|
||||
|
||||
lrepo.WithReadLocked(xrepo =>
|
||||
{
|
||||
xrepo.Repository.DeactiveStaleServers(TimeSpan.Zero);
|
||||
});
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,11 +24,6 @@ namespace Umbraco.Tests.Scoping
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
//// initialization leaves a NoScope around, remove it
|
||||
//var scope = DatabaseContext.ScopeProvider.AmbientScope;
|
||||
//Assert.IsNotNull(scope);
|
||||
//Assert.IsInstanceOf<NoScope>(scope);
|
||||
//scope.Dispose();
|
||||
Assert.IsNull(DatabaseContext.ScopeProvider.AmbientScope); // gone
|
||||
}
|
||||
|
||||
|
||||
@@ -16,11 +16,6 @@ namespace Umbraco.Tests.Scoping
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
//// initialization leaves a NoScope around, remove it
|
||||
//var scope = DatabaseContext.ScopeProvider.AmbientScope;
|
||||
//Assert.IsNotNull(scope);
|
||||
//Assert.IsInstanceOf<NoScope>(scope);
|
||||
//scope.Dispose();
|
||||
Assert.IsNull(DatabaseContext.ScopeProvider.AmbientScope); // gone
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
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.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
using umbraco.editorControls.tinyMCE3;
|
||||
using umbraco.interfaces;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Tests.Services
|
||||
{
|
||||
@@ -27,40 +16,10 @@ namespace Umbraco.Tests.Services
|
||||
[TestFixture, RequiresSTA]
|
||||
public class ThreadSafetyServiceTest : BaseDatabaseFactoryTest
|
||||
{
|
||||
private PerThreadPetaPocoUnitOfWorkProvider _uowProvider;
|
||||
private 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 PerThreadDatabaseFactory(Logger);
|
||||
var scopeProvider = new ScopeProvider(_dbFactory);
|
||||
//overwrite the local object
|
||||
ApplicationContext.DatabaseContext = new DatabaseContext(scopeProvider, 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 PerThreadPetaPocoUnitOfWorkProvider(_dbFactory);
|
||||
var evtMsgs = new TransientMessagesFactory();
|
||||
ApplicationContext.Services = new ServiceContext(
|
||||
repositoryFactory,
|
||||
_uowProvider,
|
||||
new FileUnitOfWorkProvider(),
|
||||
new PublishingStrategy(evtMsgs, Logger),
|
||||
cacheHelper,
|
||||
Logger,
|
||||
evtMsgs);
|
||||
|
||||
CreateTestData();
|
||||
}
|
||||
@@ -70,10 +29,6 @@ namespace Umbraco.Tests.Services
|
||||
{
|
||||
_error = null;
|
||||
|
||||
//dispose!
|
||||
_dbFactory.Dispose();
|
||||
_uowProvider.Dispose();
|
||||
|
||||
base.TearDown();
|
||||
}
|
||||
|
||||
@@ -89,10 +44,10 @@ namespace Umbraco.Tests.Services
|
||||
{
|
||||
//we will mimick the ServiceContext in that each repository in a service (i.e. ContentService) is a singleton
|
||||
var contentService = (ContentService)ServiceContext.ContentService;
|
||||
|
||||
|
||||
var threads = new List<Thread>();
|
||||
|
||||
Debug.WriteLine("Starting test...");
|
||||
|
||||
Debug.WriteLine("Starting...");
|
||||
|
||||
for (var i = 0; i < MaxThreadCount; i++)
|
||||
{
|
||||
@@ -100,27 +55,33 @@ namespace Umbraco.Tests.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.WriteLine("Created content on thread: " + Thread.CurrentThread.ManagedThreadId);
|
||||
|
||||
//create 2 content items
|
||||
Debug.WriteLine("[{0}] Running...", Thread.CurrentThread.ManagedThreadId);
|
||||
|
||||
using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope())
|
||||
{
|
||||
var database = scope.Database;
|
||||
Debug.WriteLine("[{0}] Database {1}.", Thread.CurrentThread.ManagedThreadId, database.InstanceSid);
|
||||
}
|
||||
|
||||
//create 2 content items
|
||||
|
||||
string name1 = "test" + Guid.NewGuid();
|
||||
var content1 = contentService.CreateContent(name1, -1, "umbTextpage", 0);
|
||||
|
||||
Debug.WriteLine("Saving content1 on thread: " + Thread.CurrentThread.ManagedThreadId);
|
||||
|
||||
Debug.WriteLine("[{0}] Saving content #1.", Thread.CurrentThread.ManagedThreadId);
|
||||
contentService.Save(content1);
|
||||
|
||||
Thread.Sleep(100); //quick pause for maximum overlap!
|
||||
|
||||
string name2 = "test" + Guid.NewGuid();
|
||||
var content2 = contentService.CreateContent(name2, -1, "umbTextpage", 0);
|
||||
Debug.WriteLine("Saving content2 on thread: " + Thread.CurrentThread.ManagedThreadId);
|
||||
contentService.Save(content2);
|
||||
Debug.WriteLine("[{0}] Saving content #2.", Thread.CurrentThread.ManagedThreadId);
|
||||
contentService.Save(content2);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
{
|
||||
_error = e;
|
||||
}
|
||||
}
|
||||
});
|
||||
threads.Add(t);
|
||||
}
|
||||
@@ -144,7 +105,7 @@ namespace Umbraco.Tests.Services
|
||||
{
|
||||
throw new Exception("Error!", _error);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -155,7 +116,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
var threads = new List<Thread>();
|
||||
|
||||
Debug.WriteLine("Starting test...");
|
||||
Debug.WriteLine("Starting...");
|
||||
|
||||
for (var i = 0; i < MaxThreadCount; i++)
|
||||
{
|
||||
@@ -163,21 +124,27 @@ namespace Umbraco.Tests.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.WriteLine("Created content on thread: " + Thread.CurrentThread.ManagedThreadId);
|
||||
Debug.WriteLine("[{0}] Running...", Thread.CurrentThread.ManagedThreadId);
|
||||
|
||||
//create 2 content items
|
||||
using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope())
|
||||
{
|
||||
var database = scope.Database;
|
||||
Debug.WriteLine("[{0}] Database {1}.", Thread.CurrentThread.ManagedThreadId, database.InstanceSid);
|
||||
}
|
||||
|
||||
//create 2 content items
|
||||
|
||||
string name1 = "test" + Guid.NewGuid();
|
||||
var folder1 = mediaService.CreateMedia(name1, -1, Constants.Conventions.MediaTypes.Folder, 0);
|
||||
Debug.WriteLine("Saving folder1 on thread: " + Thread.CurrentThread.ManagedThreadId);
|
||||
mediaService.Save(folder1, 0);
|
||||
Debug.WriteLine("[{0}] Saving content #1.", Thread.CurrentThread.ManagedThreadId);
|
||||
mediaService.Save(folder1, 0);
|
||||
|
||||
Thread.Sleep(100); //quick pause for maximum overlap!
|
||||
|
||||
string name = "test" + Guid.NewGuid();
|
||||
var folder2 = mediaService.CreateMedia(name, -1, Constants.Conventions.MediaTypes.Folder, 0);
|
||||
Debug.WriteLine("Saving folder2 on thread: " + Thread.CurrentThread.ManagedThreadId);
|
||||
mediaService.Save(folder2, 0);
|
||||
Debug.WriteLine("[{0}] Saving content #2.", Thread.CurrentThread.ManagedThreadId);
|
||||
mediaService.Save(folder2, 0);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -208,69 +175,13 @@ namespace Umbraco.Tests.Services
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void CreateTestData()
|
||||
{
|
||||
//Create and Save ContentType "umbTextpage" -> 1045
|
||||
ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage");
|
||||
contentType.Key = new Guid("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522");
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Database object per thread, this mimics the web context which is per HttpContext and is required for the multi-threaded test
|
||||
/// </summary>
|
||||
internal class PerThreadDatabaseFactory : DisposableObject, IDatabaseFactory2
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public PerThreadDatabaseFactory(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<int, UmbracoDatabase> _databases = new ConcurrentDictionary<int, UmbracoDatabase>();
|
||||
|
||||
public UmbracoDatabase CreateDatabase()
|
||||
{
|
||||
var db = _databases.GetOrAdd(
|
||||
Thread.CurrentThread.ManagedThreadId,
|
||||
i => new UmbracoDatabase(Constants.System.UmbracoConnectionName, _logger));
|
||||
return db;
|
||||
}
|
||||
|
||||
public UmbracoDatabase CreateNewDatabase()
|
||||
{
|
||||
return new UmbracoDatabase(Constants.System.UmbracoConnectionName, _logger);
|
||||
}
|
||||
|
||||
protected override void DisposeResources()
|
||||
{
|
||||
//dispose the databases
|
||||
_databases.ForEach(x => x.Value.Dispose());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a UOW with a Database object per thread
|
||||
/// </summary>
|
||||
internal class PerThreadPetaPocoUnitOfWorkProvider : DisposableObject, IDatabaseUnitOfWorkProvider
|
||||
{
|
||||
private readonly ScopeProvider _scopeProvider;
|
||||
|
||||
public PerThreadPetaPocoUnitOfWorkProvider(PerThreadDatabaseFactory dbFactory)
|
||||
{
|
||||
_scopeProvider = new ScopeProvider(dbFactory);
|
||||
}
|
||||
|
||||
public IDatabaseUnitOfWork GetUnitOfWork()
|
||||
{
|
||||
//Create or get a database instance for this thread.
|
||||
return new PetaPocoUnitOfWork(_scopeProvider);
|
||||
}
|
||||
|
||||
protected override void DisposeResources()
|
||||
{ }
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,6 +165,7 @@
|
||||
<Compile Include="Persistence\Migrations\MigrationStartupHandlerTests.cs" />
|
||||
<Compile Include="Persistence\PetaPocoCachesTest.cs" />
|
||||
<Compile Include="Persistence\PetaPocoExpressionsTests.cs" />
|
||||
<Compile Include="Persistence\Repositories\LockedRepositoryTests.cs" />
|
||||
<Compile Include="Persistence\Repositories\RedirectUrlRepositoryTests.cs" />
|
||||
<Compile Include="Scheduling\DeployTest.cs" />
|
||||
<Compile Include="Routing\NiceUrlRoutesTests.cs" />
|
||||
|
||||
Reference in New Issue
Block a user