adds notes, changes to use AsyncLocal<Guid> then will look at full refactor
This commit is contained in:
@@ -4,6 +4,11 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
// TODO: This may no longer be necessary but I'm unsure. This is based on the premise of using the old CallContext which
|
||||
// requires all objects to be serializable. Stephane wrote a blog post here https://www.zpqrtbnk.net/posts/putting-things-in-contexts/
|
||||
// about this. But now we are not using the CallContext since it doesn't exist and instead using AsyncLocal which probably
|
||||
// doesn't have this problem... but that would require some testing. For now we'll leave this class here until we
|
||||
// can acquire/discover more info.
|
||||
public class SafeCallContext : IDisposable
|
||||
{
|
||||
private static readonly List<Func<object>> EnterFuncs = new List<Func<object>>();
|
||||
|
||||
@@ -8,34 +8,30 @@ namespace Umbraco.Core.Scoping
|
||||
/// Provides a way to set contextual data that flows with the call and
|
||||
/// async context of a test or invocation.
|
||||
/// </summary>
|
||||
public static class CallContext
|
||||
public static class CallContext<T>
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, Guid?> _state = new ConcurrentDictionary<string, Guid?>();
|
||||
static ConcurrentDictionary<string, AsyncLocal<T>> _state = new ConcurrentDictionary<string, AsyncLocal<T>>();
|
||||
|
||||
/// <summary>
|
||||
/// Stores a given object and associates it with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name with which to associate the new item in the call context.</param>
|
||||
/// <param name="data">The object to store in the call context.</param>
|
||||
public static void SetData(string name, Guid? data)
|
||||
{
|
||||
_state[name + Thread.CurrentThread.ManagedThreadId] = data;
|
||||
}
|
||||
|
||||
public static void SetData(string name, T data) => _state.GetOrAdd(name, _ => new AsyncLocal<T>()).Value = data;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an object with the specified name from the <see cref="CallContext"/>.
|
||||
/// Retrieves an object with the specified name from the <see cref="CallContext{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the data being retrieved. Must match the type used when the <paramref name="name"/> was set via <see cref="SetData{T}(string, T)"/>.</typeparam>
|
||||
/// <param name="name">The name of the item in the call context.</param>
|
||||
/// <returns>The object in the call context associated with the specified name, or <see langword="null"/> if not found.</returns>
|
||||
public static Guid? GetData(string name)
|
||||
{
|
||||
return _state.TryGetValue(name + Thread.CurrentThread.ManagedThreadId, out var data) ? data : null;
|
||||
}
|
||||
/// <returns>The object in the call context associated with the specified name, or a default value for <typeparamref name="T"/> if none is found.</returns>
|
||||
public static T GetData(string name) => _state.TryGetValue(name, out var data) ? data.Value : default;
|
||||
|
||||
public static bool RemoveData(string name)
|
||||
{
|
||||
return _state.TryRemove(name+ Thread.CurrentThread.ManagedThreadId, out _);
|
||||
}
|
||||
/// <summary>
|
||||
/// Clears the state from <see cref="CallContext{T}"/> for the given name.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public static bool RemoveData(string name) => _state.TryRemove(name, out _);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,9 @@ namespace Umbraco.Core.Scoping
|
||||
|
||||
#region Context
|
||||
|
||||
// TODO: I don't think this whole thing is necessary anymore since we are using AsyncLocal which
|
||||
// I don't believe has the same odd requirements as the old CallContext! Also see SafeCallContext
|
||||
|
||||
// objects that go into the logical call context better be serializable else they'll eventually
|
||||
// cause issues whenever some cross-AppDomain code executes - could be due to ReSharper running
|
||||
// tests, any other things (see https://msdn.microsoft.com/en-us/library/dn458353(v=vs.110).aspx),
|
||||
@@ -88,6 +91,7 @@ namespace Umbraco.Core.Scoping
|
||||
// and we can retrieve the actual objects from the table.
|
||||
// only issue: how are we supposed to clear the table? we can't, really. objects should take
|
||||
// care of de-registering themselves from context.
|
||||
// see https://www.zpqrtbnk.net/posts/putting-things-in-contexts/
|
||||
|
||||
private static readonly object StaticCallContextObjectsLock = new object();
|
||||
private static readonly Dictionary<Guid, object> StaticCallContextObjects
|
||||
@@ -110,12 +114,12 @@ namespace Umbraco.Core.Scoping
|
||||
private static T GetCallContextObject<T>(string key)
|
||||
where T : class
|
||||
{
|
||||
var objectKey = CallContext.GetData(key);
|
||||
if (objectKey is null) return null;
|
||||
var objectKey = CallContext<Guid>.GetData(key);
|
||||
if (objectKey == Guid.Empty) return null;
|
||||
|
||||
lock (StaticCallContextObjectsLock)
|
||||
{
|
||||
if (StaticCallContextObjects.TryGetValue(objectKey.Value, out object callContextObject))
|
||||
if (StaticCallContextObjects.TryGetValue(objectKey, out object callContextObject))
|
||||
{
|
||||
#if DEBUG_SCOPES
|
||||
Current.Logger.Debug<ScopeProvider>("Got " + typeof(T).Name + " Object " + objectKey.ToString("N").Substring(0, 8));
|
||||
@@ -125,7 +129,7 @@ namespace Umbraco.Core.Scoping
|
||||
}
|
||||
|
||||
// hard to inject into a static method :(
|
||||
Current.Logger.Warn<ScopeProvider>("Missed {TypeName} Object {ObjectKey}", typeof(T).Name, objectKey.Value.ToString("N").Substring(0, 8));
|
||||
Current.Logger.Warn<ScopeProvider>("Missed {TypeName} Object {ObjectKey}", typeof(T).Name, objectKey.ToString("N").Substring(0, 8));
|
||||
#if DEBUG_SCOPES
|
||||
//Current.Logger.Debug<ScopeProvider>("At:\r\n" + Head(Environment.StackTrace, 24));
|
||||
#endif
|
||||
@@ -157,16 +161,16 @@ namespace Umbraco.Core.Scoping
|
||||
#endif
|
||||
if (value == null)
|
||||
{
|
||||
var objectKey = CallContext.GetData(key);
|
||||
CallContext.RemoveData(key);
|
||||
if (objectKey is null) return;
|
||||
var objectKey = CallContext<Guid>.GetData(key);
|
||||
CallContext<Guid>.RemoveData(key);
|
||||
if (objectKey == Guid.Empty) return;
|
||||
lock (StaticCallContextObjectsLock)
|
||||
{
|
||||
#if DEBUG_SCOPES
|
||||
Current.Logger.Debug<ScopeProvider>("Remove Object " + objectKey.ToString("N").Substring(0, 8));
|
||||
//Current.Logger.Debug<ScopeProvider>("At:\r\n" + Head(Environment.StackTrace, 24));
|
||||
#endif
|
||||
StaticCallContextObjects.Remove(objectKey.Value);
|
||||
StaticCallContextObjects.Remove(objectKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -183,7 +187,7 @@ namespace Umbraco.Core.Scoping
|
||||
#endif
|
||||
StaticCallContextObjects.Add(objectKey, value);
|
||||
}
|
||||
CallContext.SetData(key, objectKey);
|
||||
CallContext<Guid>.SetData(key, objectKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,7 @@ using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.Testing;
|
||||
using CallContext = Umbraco.Core.Scoping.CallContext;
|
||||
|
||||
//using CallContext = Umbraco.Core.Scoping.CallContext;
|
||||
|
||||
using CallContext = Umbraco.Core.Scoping.CallContext<System.Guid>;
|
||||
|
||||
namespace Umbraco.Tests.Scoping
|
||||
{
|
||||
@@ -126,7 +123,7 @@ namespace Umbraco.Tests.Scoping
|
||||
Assert.AreSame(scope, ((Scope) nested).ParentScope);
|
||||
|
||||
// it's moved over to call context
|
||||
var callContextKey = CallContext.GetData(ScopeProvider.ScopeItemKey).AsGuid();
|
||||
var callContextKey = CallContext.GetData(ScopeProvider.ScopeItemKey);
|
||||
Assert.AreNotEqual(Guid.Empty, callContextKey);
|
||||
|
||||
// only if Core.DEBUG_SCOPES are defined
|
||||
|
||||
Reference in New Issue
Block a user