Scope - bugfixing

This commit is contained in:
Stephan
2017-02-14 09:09:05 +01:00
parent 12dd01dd29
commit b58d8677b3
8 changed files with 174 additions and 57 deletions

View File

@@ -0,0 +1,9 @@
using System;
namespace Umbraco.Core.Scoping
{
public interface IInstanceIdentifiable
{
Guid InstanceId { get; }
}
}

View File

@@ -8,7 +8,7 @@ namespace Umbraco.Core.Scoping
/// <summary>
/// Represents a scope.
/// </summary>
public interface IScope : IDisposable
public interface IScope : IDisposable, IInstanceIdentifiable
{
/// <summary>
/// Gets the scope database.
@@ -39,9 +39,5 @@ namespace Umbraco.Core.Scoping
/// Completes the scope.
/// </summary>
void Complete();
#if DEBUG_SCOPES
Guid InstanceId { get; }
#endif
}
}

View File

@@ -24,10 +24,8 @@ namespace Umbraco.Core.Scoping
#endif
}
#if DEBUG_SCOPES
private readonly Guid _instanceId = Guid.NewGuid();
public Guid InstanceId { get { return _instanceId; } }
#endif
/// <inheritdoc />
public bool CallContext { get { return false; } }

View File

@@ -133,10 +133,8 @@ namespace Umbraco.Core.Scoping
throw new Exception("NoScope instance is not free.");
}
#if DEBUG_SCOPES
private readonly Guid _instanceId = Guid.NewGuid();
public Guid InstanceId { get { return _instanceId; } }
#endif
// a value indicating whether to force call-context
public bool CallContext
@@ -189,6 +187,8 @@ namespace Umbraco.Core.Scoping
// the parent scope (in a nested scopes chain)
public IScopeInternal ParentScope { get; set; }
public bool Attached { get; set; }
// the original scope (when attaching a detachable scope)
public IScopeInternal OrigScope { get; set; }
@@ -329,7 +329,18 @@ namespace Umbraco.Core.Scoping
EnsureNotDisposed();
if (this != _scopeProvider.AmbientScope)
{
#if DEBUG_SCOPES
var ambient = _scopeProvider.AmbientScope;
Logging.LogHelper.Debug<Scope>("Dispose error (" + (ambient == null ? "no" : "other") + " ambient)");
if (ambient == null)
throw new InvalidOperationException("Not the ambient scope (no ambient scope).");
var infos = _scopeProvider.GetScopeInfo(ambient);
throw new InvalidOperationException("Not the ambient scope (see current ambient ctor stack trace).\r\n" + infos.CtorStack);
#else
throw new InvalidOperationException("Not the ambient scope.");
#endif
}
#if DEBUG_SCOPES
_scopeProvider.Disposed(this);
@@ -432,6 +443,9 @@ namespace Umbraco.Core.Scoping
{
// get out of the way, restore original
_scopeProvider.SetAmbient(OrigScope, OrigContext);
Attached = false;
OrigScope = null;
OrigContext = null;
}
});
}

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Umbraco.Core.Scoping
{
public class ScopeContext
public class ScopeContext : IInstanceIdentifiable
{
private Dictionary<string, IEnlistedObject> _enlisted;
@@ -27,6 +27,9 @@ namespace Umbraco.Core.Scoping
throw new AggregateException("Exceptions were thrown by listed actions.", exceptions);
}
private readonly Guid _instanceId = Guid.NewGuid();
public Guid InstanceId { get { return _instanceId; } }
private IDictionary<string, IEnlistedObject> Enlisted
{
get

View File

@@ -37,7 +37,7 @@ namespace Umbraco.Core.Scoping
{
// cannot re-attached over leaked scope/context
// except of course over NoScope (which leaks)
var ambientScope = AmbientScopeInternal;
var ambientScope = GetCallContextObject<IScope>(ScopeItemKey);
if (ambientScope != null)
{
var ambientNoScope = ambientScope as NoScope;
@@ -47,7 +47,8 @@ namespace Umbraco.Core.Scoping
// this should rollback any pending transaction
ambientNoScope.Dispose();
}
if (AmbientContextInternal != null) throw new Exception("Found leaked context when restoring call context.");
if (GetCallContextObject<ScopeContext>(ContextItemKey) != null)
throw new Exception("Found leaked context when restoring call context.");
var t = (Tuple<IScopeInternal, ScopeContext>) o;
SetCallContextObject(ScopeItemKey, t.Item1);
@@ -101,27 +102,41 @@ namespace Umbraco.Core.Scoping
lock (StaticCallContextObjectsLock)
{
object callContextObject;
return StaticCallContextObjects.TryGetValue(objectKey, out callContextObject) ? (T)callContextObject : null;
if (StaticCallContextObjects.TryGetValue(objectKey, out callContextObject))
{
#if DEBUG_SCOPES
//Logging.LogHelper.Debug<ScopeProvider>("GotObject " + objectKey.ToString("N").Substring(0, 8));
#endif
return (T) callContextObject;
}
#if DEBUG_SCOPES
//Logging.LogHelper.Debug<ScopeProvider>("MissedObject " + objectKey.ToString("N").Substring(0, 8));
#endif
return null;
}
}
private static void SetCallContextObject(string key, object value)
private static void SetCallContextObject(string key, IInstanceIdentifiable value)
{
#if DEBUG_SCOPES
// manage the 'context' that contains the scope (null, "http" or "call")
// first, null-register the existing value
var ambientKey = CallContext.LogicalGetData(ScopeItemKey).AsGuid();
object o = null;
lock (StaticCallContextObjectsLock)
// only for scopes of course!
if (key == ScopeItemKey)
{
if (ambientKey != default (Guid))
StaticCallContextObjects.TryGetValue(ambientKey, out o);
// first, null-register the existing value
var ambientKey = CallContext.LogicalGetData(ScopeItemKey).AsGuid();
object o = null;
lock (StaticCallContextObjectsLock)
{
if (ambientKey != default(Guid))
StaticCallContextObjects.TryGetValue(ambientKey, out o);
}
var ambientScope = o as IScope;
if (ambientScope != null) RegisterContext(ambientScope, null);
// then register the new value
var scope = value as IScope;
if (scope != null) RegisterContext(scope, "call");
}
var ambientScope = o as IScope;
if (ambientScope != null) RegisterContext(ambientScope, null);
// then register the new value
var scope = value as IScope;
if (scope != null) RegisterContext(scope, "call");
#endif
if (value == null)
{
@@ -130,6 +145,9 @@ namespace Umbraco.Core.Scoping
if (objectKey == default (Guid)) return;
lock (StaticCallContextObjectsLock)
{
#if DEBUG_SCOPES
//Logging.LogHelper.Debug<ScopeProvider>("RemoveObject " + objectKey.ToString("N").Substring(0, 8));
#endif
StaticCallContextObjects.Remove(objectKey);
}
}
@@ -138,9 +156,12 @@ namespace Umbraco.Core.Scoping
// note - we are *not* detecting an already-existing value
// because our code in this class *always* sets to null before
// setting to a real value
var objectKey = Guid.NewGuid();
var objectKey = value.InstanceId;
lock (StaticCallContextObjectsLock)
{
#if DEBUG_SCOPES
//Logging.LogHelper.Debug<ScopeProvider>("AddObject " + objectKey.ToString("N").Substring(0, 8));
#endif
StaticCallContextObjects.Add(objectKey, value);
}
CallContext.LogicalSetData(key, objectKey);
@@ -181,12 +202,16 @@ namespace Umbraco.Core.Scoping
}
#if DEBUG_SCOPES
// manage the 'context' that contains the scope (null, "http" or "call")
// first, null-register the existing value
var ambientScope = (IScope)httpContextItems[ScopeItemKey];
if (ambientScope != null) RegisterContext(ambientScope, null);
// then register the new value
var scope = value as IScope;
if (scope != null) RegisterContext(scope, "http");
// only for scopes of course!
if (key == ScopeItemKey)
{
// first, null-register the existing value
var ambientScope = (IScope)httpContextItems[ScopeItemKey];
if (ambientScope != null) RegisterContext(ambientScope, null);
// then register the new value
var scope = value as IScope;
if (scope != null) RegisterContext(scope, "http");
}
#endif
if (value == null)
httpContextItems.Remove(key);
@@ -195,7 +220,7 @@ namespace Umbraco.Core.Scoping
return true;
}
#endregion
#endregion
#region Ambient Context
@@ -292,9 +317,6 @@ namespace Umbraco.Core.Scoping
return;
}
if (context == null)
throw new ArgumentNullException("context");
if (scope.CallContext == false && SetHttpContextObject(ScopeItemKey, scope, false))
{
SetHttpContextObject(ScopeRefItemKey, StaticScopeReference);
@@ -327,6 +349,10 @@ namespace Umbraco.Core.Scoping
if (otherScope.Detachable == false)
throw new ArgumentException("Not a detachable scope.");
if (otherScope.Attached)
throw new InvalidOperationException("Already attached.");
otherScope.Attached = true;
otherScope.OrigScope = AmbientScope;
otherScope.OrigContext = AmbientContext;
@@ -355,6 +381,7 @@ namespace Umbraco.Core.Scoping
SetAmbient(scope.OrigScope, scope.OrigContext);
scope.OrigScope = null;
scope.OrigContext = null;
scope.Attached = false;
return scope;
}
@@ -446,7 +473,7 @@ namespace Umbraco.Core.Scoping
// all scope instances that are currently beeing tracked
private static readonly object StaticScopeInfosLock = new object();
private static readonly List<ScopeInfo> StaticScopeInfos = new List<ScopeInfo>();
private static readonly Dictionary<IScope, ScopeInfo> StaticScopeInfos = new Dictionary<IScope, ScopeInfo>();
public IEnumerable<ScopeInfo> ScopeInfos
{
@@ -454,7 +481,7 @@ namespace Umbraco.Core.Scoping
{
lock (StaticScopeInfosLock)
{
return StaticScopeInfos.ToArray(); // capture in an array
return StaticScopeInfos.Values.ToArray(); // capture in an array
}
}
}
@@ -463,7 +490,8 @@ namespace Umbraco.Core.Scoping
{
lock (StaticScopeInfosLock)
{
return StaticScopeInfos.FirstOrDefault(x => x.Scope == scope);
ScopeInfo scopeInfo;
return StaticScopeInfos.TryGetValue(scope, out scopeInfo) ? scopeInfo : null;
}
}
@@ -477,8 +505,9 @@ namespace Umbraco.Core.Scoping
{
lock (StaticScopeInfosLock)
{
if (StaticScopeInfos.Any(x => x.Scope == scope)) throw new Exception("oops: already registered.");
StaticScopeInfos.Add(new ScopeInfo(scope, Environment.StackTrace));
if (StaticScopeInfos.ContainsKey(scope)) throw new Exception("oops: already registered.");
//Logging.LogHelper.Debug<ScopeProvider>("Register " + scope.InstanceId.ToString("N").Substring(0, 8));
StaticScopeInfos[scope] = new ScopeInfo(scope, Environment.StackTrace);
}
}
@@ -488,13 +517,17 @@ namespace Umbraco.Core.Scoping
{
lock (StaticScopeInfosLock)
{
var info = StaticScopeInfos.FirstOrDefault(x => x.Scope == scope);
ScopeInfo info;
if (StaticScopeInfos.TryGetValue(scope, out info) == false) info = null;
if (info == null)
{
if (context == null) return;
throw new Exception("oops: unregistered scope.");
}
//Logging.LogHelper.Debug<ScopeProvider>("Register context " + (context ?? "null") + " for " + scope.InstanceId.ToString("N").Substring(0, 8));
if (context == null) info.NullStack = Environment.StackTrace;
//if (context == null)
// Logging.LogHelper.Debug<ScopeProvider>("STACK\r\n" + info.NullStack);
info.Context = context;
}
}
@@ -503,11 +536,12 @@ namespace Umbraco.Core.Scoping
{
lock (StaticScopeInfosLock)
{
var info = StaticScopeInfos.FirstOrDefault(x => x.Scope == scope);
if (info != null)
if (StaticScopeInfos.ContainsKey(scope))
{
// enable this by default
StaticScopeInfos.Remove(info);
//Console.WriteLine("unregister " + scope.InstanceId.ToString("N").Substring(0, 8));
StaticScopeInfos.Remove(scope);
//Logging.LogHelper.Debug<ScopeProvider>("Remove " + scope.InstanceId.ToString("N").Substring(0, 8));
// instead, enable this to keep *all* scopes
// beware, there can be a lot of scopes!