UnRevert "Backport SafeCallContext, DefaultDatabaseFactory fixes from 7.6"

This reverts commit 8ba6cb3abf.
This commit is contained in:
Stephan
2016-11-23 11:26:30 +01:00
parent a77763ee89
commit 5784ea96e7
6 changed files with 258 additions and 78 deletions

View File

@@ -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);
}
}
}
@@ -78,7 +82,7 @@ namespace Umbraco.Core.Packaging
/// <summary>
/// Performs the assembly scanning
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="T"></typeparam>
/// <param name="assemblies"></param>
/// <param name="errorReport"></param>
/// <returns></returns>
@@ -107,7 +111,7 @@ namespace Umbraco.Core.Packaging
/// <summary>
/// Performs the assembly scanning
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="T"></typeparam>
/// <param name="dllPath"></param>
/// <param name="errorReport"></param>
/// <returns></returns>
@@ -154,7 +158,7 @@ namespace Umbraco.Core.Packaging
//get the list of assembly names to compare below
var loadedNames = loaded.Select(x => x.GetName().Name).ToArray();
//Then load each referenced assembly into the context
foreach (var a in loaded)
{
@@ -170,7 +174,7 @@ namespace Umbraco.Core.Packaging
}
catch (FileNotFoundException)
{
//if an exception occurs it means that a referenced assembly could not be found
//if an exception occurs it means that a referenced assembly could not be found
errors.Add(
string.Concat("This package references the assembly '",
assemblyName.Name,
@@ -179,7 +183,7 @@ namespace Umbraco.Core.Packaging
}
catch (Exception ex)
{
//if an exception occurs it means that a referenced assembly could not be found
//if an exception occurs it means that a referenced assembly could not be found
errors.Add(
string.Concat("This package could not be verified for compatibility. An error occurred while loading a referenced assembly '",
assemblyName.Name,
@@ -197,7 +201,7 @@ namespace Umbraco.Core.Packaging
{
//now we need to see if they contain any type 'T'
var reflectedAssembly = a;
try
{
var found = reflectedAssembly.GetExportedTypes()
@@ -210,8 +214,8 @@ namespace Umbraco.Core.Packaging
}
catch (Exception ex)
{
//This is a hack that nobody can seem to get around, I've read everything and it seems that
// this is quite a common thing when loading types into reflection only load context, so
//This is a hack that nobody can seem to get around, I've read everything and it seems that
// this is quite a common thing when loading types into reflection only load context, so
// we're just going to ignore this specific one for now
var typeLoadEx = ex as TypeLoadException;
if (typeLoadEx != null)
@@ -232,7 +236,7 @@ namespace Umbraco.Core.Packaging
LogHelper.Error<PackageBinaryInspector>("An error occurred scanning package assemblies", ex);
}
}
}
errorReport = errors.ToArray();
@@ -252,7 +256,7 @@ namespace Umbraco.Core.Packaging
var contractType = contractAssemblyLoadFrom.GetExportedTypes()
.FirstOrDefault(x => x.FullName == typeof(T).FullName && x.Assembly.FullName == typeof(T).Assembly.FullName);
if (contractType == null)
{
throw new InvalidOperationException("Could not find type " + typeof(T) + " in the LoadFrom assemblies");

View File

@@ -1,6 +1,6 @@
using System;
using System.Runtime.Remoting.Messaging;
using System.Web;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
namespace Umbraco.Core.Persistence
@@ -19,13 +19,24 @@ namespace Umbraco.Core.Persistence
private readonly ILogger _logger;
public string ConnectionString { get; private set; }
public string ProviderName { get; private set; }
//very important to have ThreadStatic:
// see: http://issues.umbraco.org/issue/U4-2172
[ThreadStatic]
private static volatile UmbracoDatabase _nonHttpInstance;
private static readonly object Locker = new object();
// NO! see notes in v8 HybridAccessorBase
//[ThreadStatic]
//private static volatile UmbracoDatabase _nonHttpInstance;
private const string ItemKey = "Umbraco.Core.Persistence.DefaultDatabaseFactory";
private static UmbracoDatabase NonContextValue
{
get { return (UmbracoDatabase) CallContext.LogicalGetData(ItemKey); }
set
{
if (value == null) CallContext.FreeNamedDataSlot(ItemKey);
else CallContext.LogicalSetData(ItemKey, value);
}
}
private static readonly object Locker = new object();
/// <summary>
/// Constructor accepting custom connection string
@@ -36,7 +47,10 @@ namespace Umbraco.Core.Persistence
{
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;
}
@@ -51,65 +65,133 @@ namespace Umbraco.Core.Persistence
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;
}
public UmbracoDatabase CreateDatabase()
{
//no http context, create the singleton global object
if (HttpContext.Current == null)
{
if (_nonHttpInstance == null)
{
lock (Locker)
{
//double check
if (_nonHttpInstance == null)
{
_nonHttpInstance = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false
? new UmbracoDatabase(ConnectionString, ProviderName, _logger)
: new UmbracoDatabase(_connectionStringName, _logger);
}
}
}
return _nonHttpInstance;
}
// no http context, create the call context object
// NOTHING is going to track the object and it is the responsibility of the caller to release it!
// using the ReleaseDatabase method.
if (HttpContext.Current == null)
{
LogHelper.Debug<DefaultDatabaseFactory>("Get NON http [T" + Environment.CurrentManagedThreadId + "]");
var value = NonContextValue;
if (value != null) return value;
lock (Locker)
{
value = NonContextValue;
if (value != null) return value;
//we have an http context, so only create one per request
if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)) == false)
{
HttpContext.Current.Items.Add(typeof (DefaultDatabaseFactory),
string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false
LogHelper.Debug<DefaultDatabaseFactory>("Create NON http [T" + Environment.CurrentManagedThreadId + "]");
NonContextValue = value = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false
? new UmbracoDatabase(ConnectionString, ProviderName, _logger)
: new UmbracoDatabase(_connectionStringName, _logger);
return value;
}
}
// we have an http context, so only create one per request.
// UmbracoDatabase is marked IDisposeOnRequestEnd and therefore will be disposed when
// UmbracoModule attempts to dispose the relevant HttpContext items. so we DO dispose
// connections at the end of each request. no need to call ReleaseDatabase.
LogHelper.Debug<DefaultDatabaseFactory>("Get http [T" + Environment.CurrentManagedThreadId + "]");
if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)) == false)
{
LogHelper.Debug<DefaultDatabaseFactory>("Create http [T" + Environment.CurrentManagedThreadId + "]");
HttpContext.Current.Items.Add(typeof(DefaultDatabaseFactory),
string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false
? new UmbracoDatabase(ConnectionString, ProviderName, _logger)
: new UmbracoDatabase(_connectionStringName, _logger));
}
return (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
}
}
return (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
}
protected override void DisposeResources()
// releases the "context" database
public void ReleaseDatabase()
{
if (HttpContext.Current == null)
{
var value = NonContextValue;
if (value != null) value.Dispose();
NonContextValue = null;
}
else
{
var db = (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
if (db != null) db.Dispose();
HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory));
}
}
protected override void DisposeResources()
{
if (HttpContext.Current == null)
{
_nonHttpInstance.Dispose();
}
else
{
if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)))
{
((UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]).Dispose();
}
}
ReleaseDatabase();
}
// during tests, the thread static var can leak between tests
// this method provides a way to force-reset the variable
internal void ResetForTests()
{
if (_nonHttpInstance == null) return;
_nonHttpInstance.Dispose();
_nonHttpInstance = null;
}
}
var value = NonContextValue;
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)
{
var database = o as UmbracoDatabase;
if (o != null && database == null) throw new ArgumentException("Not an UmbracoDatabase.", "o");
if (HttpContext.Current == null)
{
if (NonContextValue != null) throw new InvalidOperationException();
if (database != null) NonContextValue = database;
}
else
{
if (HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] != null) throw new InvalidOperationException();
if (database != null) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] = database;
}
}
#endregion
}
}

View File

@@ -0,0 +1,94 @@
using System;
using System.Linq;
using System.Collections.Generic;
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 InvalidOperationException("Cannot register while some SafeCallContext instances exist.");
EnterFuncs.Add(enterFunc);
ExitActions.Add(exitAction);
}
}
// 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 lets anything that uses the logical call context register itself,
// providing two methods:
// - an enter func that removes and returns whatever is in the logical call context
// - an exit action that restores the value into the logical call context
// whenever a SafeCallContext instance is created, it uses these methods to capture
// and clear the logical call context, and restore it when disposed.
//
// in addition, a static Clear method is provided - which uses the enter funcs to
// remove everything from logical call context - not to be used when the app runs,
// but can be useful during tests
//
// note
// see System.Transactions
// they are using a conditional weak table to store the data, and what they store in
// LLC is the key - which is just an empty MarshalByRefObject that is created with
// the transaction scope - that way, they can "clear current data" provided that
// they have the key - but they need to hold onto a ref to the scope... not ok for us
public static void Clear()
{
lock (EnterFuncs)
{
foreach (var enter in EnterFuncs)
enter();
}
}
public SafeCallContext()
{
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--;
}
}
// for unit tests ONLY
internal static void Reset()
{
lock (EnterFuncs)
{
if (_count > 0) throw new InvalidOperationException("Cannot reset while some SafeCallContext instances exist.");
EnterFuncs.Clear();
ExitActions.Clear();
}
}
}
}

View File

@@ -523,6 +523,7 @@
<Compile Include="PropertyEditors\ValueConverters\ImageCropperValueConverter.cs" />
<Compile Include="Publishing\UnPublishedStatusType.cs" />
<Compile Include="Publishing\UnPublishStatus.cs" />
<Compile Include="SafeCallContext.cs" />
<Compile Include="Security\ActiveDirectoryBackOfficeUserPasswordChecker.cs" />
<Compile Include="Security\BackOfficeClaimsIdentityFactory.cs" />
<Compile Include="Security\BackOfficeCookieAuthenticationProvider.cs" />

View File

@@ -37,7 +37,7 @@ namespace Umbraco.Tests.Persistence
{
DatabaseContext = _dbContext,
IsReady = true
};
};
}
[TearDown]
@@ -102,7 +102,7 @@ namespace Umbraco.Tests.Persistence
var appCtx = new ApplicationContext(
_dbContext,
new ServiceContext(migrationEntryService: Mock.Of<IMigrationEntryService>()),
new ServiceContext(migrationEntryService: Mock.Of<IMigrationEntryService>()),
CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));

View File

@@ -102,7 +102,6 @@ namespace UmbracoExamine.DataServices
{
try
{
var result = _applicationContext.DatabaseContext.Database.Fetch<string>("select distinct alias from cmsPropertyType order by alias");
return result;
}