adds notes, changes to use AsyncLocal<Guid> then will look at full refactor

This commit is contained in:
Shannon
2019-12-20 12:50:33 +11:00
parent dbf0581cb1
commit bcc0fa391a
4 changed files with 33 additions and 31 deletions

View File

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

View File

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

View File

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

View File

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