Introduce SafeCallContext to fix breaking test
(wondering if using logical call context a breaking change)
This commit is contained in:
@@ -29,20 +29,24 @@ namespace Umbraco.Core.Packaging
|
||||
/// </remarks>
|
||||
public static IEnumerable<string> ScanAssembliesForTypeReference<T>(IEnumerable<byte[]> assemblys, out string[] errorReport)
|
||||
{
|
||||
var appDomain = GetTempAppDomain();
|
||||
var type = typeof(PackageBinaryInspector);
|
||||
try
|
||||
// beware! when toying with domains, use a safe call context!
|
||||
using (new SafeCallContext())
|
||||
{
|
||||
var value = (PackageBinaryInspector)appDomain.CreateInstanceAndUnwrap(
|
||||
type.Assembly.FullName,
|
||||
type.FullName);
|
||||
// do NOT turn PerformScan into static (even if ReSharper says so)!
|
||||
var result = value.PerformScan<T>(assemblys.ToArray(), out errorReport);
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
AppDomain.Unload(appDomain);
|
||||
var appDomain = GetTempAppDomain();
|
||||
var type = typeof(PackageBinaryInspector);
|
||||
try
|
||||
{
|
||||
var value = (PackageBinaryInspector) appDomain.CreateInstanceAndUnwrap(
|
||||
type.Assembly.FullName,
|
||||
type.FullName);
|
||||
// do NOT turn PerformScan into static (even if ReSharper says so)!
|
||||
var result = value.PerformScan<T>(assemblys.ToArray(), out errorReport);
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
AppDomain.Unload(appDomain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,12 +285,8 @@ namespace Umbraco.Core.Packaging
|
||||
PrivateBinPathProbe = AppDomain.CurrentDomain.SetupInformation.PrivateBinPathProbe
|
||||
};
|
||||
|
||||
//create new domain with full trust
|
||||
return AppDomain.CreateDomain(
|
||||
appName,
|
||||
AppDomain.CurrentDomain.Evidence,
|
||||
domainSetup,
|
||||
new PermissionSet(PermissionState.Unrestricted));
|
||||
// create new domain with full trust
|
||||
return AppDomain.CreateDomain(appName, AppDomain.CurrentDomain.Evidence, domainSetup, new PermissionSet(PermissionState.Unrestricted));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
using System.Web;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.Persistence
|
||||
@@ -46,9 +45,12 @@ namespace Umbraco.Core.Persistence
|
||||
/// <param name="logger"></param>
|
||||
public DefaultDatabaseFactory(string connectionStringName, ILogger logger)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException("logger");
|
||||
if (logger == null) throw new ArgumentNullException("logger");
|
||||
Mandate.ParameterNotNullOrEmpty(connectionStringName, "connectionStringName");
|
||||
_connectionStringName = connectionStringName;
|
||||
|
||||
//if (NonContextValue != null) throw new Exception("NonContextValue is not null.");
|
||||
|
||||
_connectionStringName = connectionStringName;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -60,10 +62,13 @@ namespace Umbraco.Core.Persistence
|
||||
/// <param name="logger"></param>
|
||||
public DefaultDatabaseFactory(string connectionString, string providerName, ILogger logger)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException("logger");
|
||||
if (logger == null) throw new ArgumentNullException("logger");
|
||||
Mandate.ParameterNotNullOrEmpty(connectionString, "connectionString");
|
||||
Mandate.ParameterNotNullOrEmpty(providerName, "providerName");
|
||||
ConnectionString = connectionString;
|
||||
|
||||
//if (NonContextValue != null) throw new Exception("NonContextValue is not null.");
|
||||
|
||||
ConnectionString = connectionString;
|
||||
ProviderName = providerName;
|
||||
_logger = logger;
|
||||
}
|
||||
@@ -120,29 +125,14 @@ namespace Umbraco.Core.Persistence
|
||||
else
|
||||
{
|
||||
var db = (UmbracoDatabase) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
|
||||
if (db != null)
|
||||
{
|
||||
db.Dispose();
|
||||
HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory));
|
||||
}
|
||||
if (db != null) db.Dispose();
|
||||
HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisposeResources()
|
||||
{
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
var value = NonContextValue;
|
||||
if (value != null) value.Dispose();
|
||||
NonContextValue = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)))
|
||||
{
|
||||
((UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]).Dispose();
|
||||
}
|
||||
}
|
||||
ReleaseDatabase();
|
||||
}
|
||||
|
||||
internal void ResetForTests()
|
||||
@@ -151,5 +141,56 @@ namespace Umbraco.Core.Persistence
|
||||
if (value != null) value.Dispose();
|
||||
NonContextValue = null;
|
||||
}
|
||||
|
||||
#region SafeCallContext
|
||||
|
||||
// see notes in SafeCallContext - need to do this since we are using
|
||||
// the logical call context...
|
||||
|
||||
static DefaultDatabaseFactory()
|
||||
{
|
||||
SafeCallContext.Register(DetachDatabase, AttachDatabase);
|
||||
}
|
||||
|
||||
// detaches the current database
|
||||
// ie returns the database and remove it from whatever is "context"
|
||||
private static UmbracoDatabase DetachDatabase()
|
||||
{
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
var db = NonContextValue;
|
||||
NonContextValue = null;
|
||||
return db;
|
||||
}
|
||||
else
|
||||
{
|
||||
var db = (UmbracoDatabase) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
|
||||
HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory));
|
||||
return db;
|
||||
}
|
||||
}
|
||||
|
||||
// attach a current database
|
||||
// ie assign it to whatever is "context"
|
||||
// throws if there already is a database
|
||||
private static void AttachDatabase(object o)
|
||||
{
|
||||
if (o == null) return;
|
||||
var database = o as UmbracoDatabase;
|
||||
if (database == null) throw new ArgumentException("Not an UmbracoDatabase.", "o");
|
||||
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
if (NonContextValue != null) throw new InvalidOperationException();
|
||||
NonContextValue = database;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] != null) throw new InvalidOperationException();
|
||||
HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] = database;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using StackExchange.Profiling;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
68
src/Umbraco.Core/SafeCallContext.cs
Normal file
68
src/Umbraco.Core/SafeCallContext.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Umbraco.Core.Persistence;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
internal class SafeCallContext : IDisposable
|
||||
{
|
||||
private static readonly List<Func<object>> EnterFuncs = new List<Func<object>>();
|
||||
private static readonly List<Action<object>> ExitActions = new List<Action<object>>();
|
||||
private static int _count;
|
||||
private readonly List<object> _objects;
|
||||
private bool _disposed;
|
||||
|
||||
public static void Register(Func<object> enterFunc, Action<object> exitAction)
|
||||
{
|
||||
if (enterFunc == null) throw new ArgumentNullException("enterFunc");
|
||||
if (exitAction == null) throw new ArgumentNullException("exitAction");
|
||||
|
||||
lock (EnterFuncs)
|
||||
{
|
||||
if (_count > 0) throw new Exception("Busy.");
|
||||
EnterFuncs.Add(enterFunc);
|
||||
ExitActions.Add(exitAction);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearCallContext()
|
||||
{
|
||||
lock (EnterFuncs)
|
||||
{
|
||||
var ignore = EnterFuncs.Select(x => x()).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
// tried to make the UmbracoDatabase serializable but then it leaks to weird places
|
||||
// in ReSharper and so on, where Umbraco.Core is not available. Tried to serialize
|
||||
// as an object instead but then it comes *back* deserialized into the original context
|
||||
// as an object and of course it breaks everything. Cannot prevent this from flowing,
|
||||
// and ExecutionContext.SuppressFlow() works for threads but not domains. and we'll
|
||||
// have the same issue with anything that toys with logical call context... so this
|
||||
// class let them register & deal with the situation (removing themselves)
|
||||
|
||||
public SafeCallContext()
|
||||
{
|
||||
Interlocked.Increment(ref _count);
|
||||
lock (EnterFuncs)
|
||||
{
|
||||
_count++;
|
||||
_objects = EnterFuncs.Select(x => x()).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("this");
|
||||
_disposed = true;
|
||||
lock (EnterFuncs)
|
||||
{
|
||||
for (var i = 0; i < ExitActions.Count; i++)
|
||||
ExitActions[i](_objects[i]);
|
||||
_count--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -420,6 +420,7 @@
|
||||
<Compile Include="Models\UmbracoDomain.cs" />
|
||||
<Compile Include="Models\DoNotCloneAttribute.cs" />
|
||||
<Compile Include="Models\IDomain.cs" />
|
||||
<Compile Include="SafeCallContext.cs" />
|
||||
<Compile Include="Persistence\DatabaseNodeLockExtensions.cs" />
|
||||
<Compile Include="Persistence\Factories\ExternalLoginFactory.cs" />
|
||||
<Compile Include="Persistence\Factories\MigrationEntryFactory.cs" />
|
||||
|
||||
44
src/Umbraco.Tests/CallContextTests.cs
Normal file
44
src/Umbraco.Tests/CallContextTests.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Umbraco.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class CallContextTests
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
ClearCallContext();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
ClearCallContext();
|
||||
}
|
||||
|
||||
private static void ClearCallContext()
|
||||
{
|
||||
// logical call context leaks between tests
|
||||
// cleanup things before/after we've run else one of
|
||||
// the two tests will fail
|
||||
CallContext.FreeNamedDataSlot("test1");
|
||||
CallContext.FreeNamedDataSlot("test2");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test1()
|
||||
{
|
||||
CallContext.LogicalSetData("test1", "test1");
|
||||
Assert.IsNull(CallContext.LogicalGetData("test2"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test2()
|
||||
{
|
||||
CallContext.LogicalSetData("test2", "test2");
|
||||
Assert.IsNull(CallContext.LogicalGetData("test1"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,9 @@ namespace Umbraco.Tests.Persistence
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
// bah
|
||||
SafeCallContext.ClearCallContext();
|
||||
|
||||
_dbContext = new DatabaseContext(
|
||||
new DefaultDatabaseFactory(Core.Configuration.GlobalSettings.UmbracoConnectionName, Mock.Of<ILogger>()),
|
||||
Mock.Of<ILogger>(), new SqlCeSyntaxProvider(), Constants.DatabaseProviders.SqlCe);
|
||||
@@ -98,7 +101,7 @@ namespace Umbraco.Tests.Persistence
|
||||
var schemaHelper = new DatabaseSchemaHelper(_dbContext.Database, Mock.Of<ILogger>(), new SqlCeSyntaxProvider());
|
||||
|
||||
var appCtx = new ApplicationContext(
|
||||
new DatabaseContext(Mock.Of<IDatabaseFactory>(), Mock.Of<ILogger>(), Mock.Of<ISqlSyntaxProvider>(), "test"),
|
||||
_dbContext,
|
||||
new ServiceContext(migrationEntryService: Mock.Of<IMigrationEntryService>()),
|
||||
CacheHelper.CreateDisabledCacheHelper(),
|
||||
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
|
||||
|
||||
@@ -61,6 +61,11 @@ namespace Umbraco.Tests.TestHelpers
|
||||
var path = TestHelper.CurrentAssemblyDirectory;
|
||||
AppDomain.CurrentDomain.SetData("DataDirectory", path);
|
||||
|
||||
// fixme - in theory we should not need this
|
||||
// it's done in base.Initialize() and no idea why we do things
|
||||
// in this order here... not going to change it now though...
|
||||
SafeCallContext.ClearCallContext();
|
||||
|
||||
_dbFactory = new DefaultDatabaseFactory(
|
||||
GetDbConnectionString(),
|
||||
GetDbProviderName(),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
|
||||
namespace Umbraco.Tests.TestHelpers
|
||||
{
|
||||
@@ -10,21 +9,25 @@ namespace Umbraco.Tests.TestHelpers
|
||||
[TestFixture]
|
||||
public abstract class BaseUmbracoConfigurationTest
|
||||
{
|
||||
|
||||
|
||||
[SetUp]
|
||||
public virtual void Initialize()
|
||||
{
|
||||
SettingsForTests.Reset();
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public virtual void TearDown()
|
||||
{
|
||||
//reset settings
|
||||
SettingsForTests.Reset();
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
private static void Reset()
|
||||
{
|
||||
// reset settings
|
||||
SettingsForTests.Reset();
|
||||
|
||||
// clear the logical call context
|
||||
SafeCallContext.ClearCallContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,6 +175,7 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CallContextTests.cs" />
|
||||
<Compile Include="Migrations\MigrationIssuesTests.cs" />
|
||||
<Compile Include="Persistence\Migrations\MigrationStartupHandlerTests.cs" />
|
||||
<Compile Include="Persistence\PetaPocoExpressionsTests.cs" />
|
||||
|
||||
Reference in New Issue
Block a user