U4-9322 - manage isolation levels, cleanup tests

This commit is contained in:
Stephan
2017-01-18 14:53:24 +01:00
parent 1f636d05d1
commit ca777b26de
17 changed files with 434 additions and 374 deletions

View File

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

View File

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

View File

@@ -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
}

View File

@@ -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
}

View File

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

View File

@@ -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" />