Skip to content

Commit

Permalink
Fix concurrent registration of type members (sebastienros#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastienros authored Mar 21, 2019
1 parent f2aa794 commit 675498a
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 46 deletions.
2 changes: 1 addition & 1 deletion Fluid.Benchmarks/Fluid.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<PackageReference Include="BenchmarkDotNet" Version="0.11.4" />
<PackageReference Include="DotLiquid" Version="2.0.298" />
<PackageReference Include="Liquid.NET" Version="0.10.0" />
<PackageReference Include="Scriban" Version="2.0.0-alpha-005" />
<PackageReference Include="Scriban" Version="2.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
32 changes: 32 additions & 0 deletions Fluid.Tests/TemplateContextTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace Fluid.Tests
{
public class TemplateContextTests
{
private class TestClass
{
public string Name { get; set; }
}

[Fact]
public async Task ShouldNotThrowException()
{
var exception = await Record.ExceptionAsync(() => Task.WhenAll(Enumerable.Range(0, 10).Select(x => Register())));

Assert.Null(exception);
}

private static async Task Register()
{
await Task.Delay(10);
var templateContext = new TemplateContext();
templateContext.MemberAccessStrategy.Register(typeof(TestClass));
}
}
}
5 changes: 0 additions & 5 deletions Fluid/Accessors/DelegateAccessorOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ public DelegateAccessor(Func<T, string, TemplateContext, TResult> getter)
_getter = getter;
}

public TResult Get(T obj, string name, TemplateContext ctx)
{
return _getter(obj, name, ctx);
}

object IMemberAccessor.Get(object obj, string name, TemplateContext ctx)
{
return _getter((T)obj, name, ctx);
Expand Down
44 changes: 44 additions & 0 deletions Fluid/Accessors/PropertyInfoAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Reflection;

namespace Fluid.Accessors
{
public class PropertyInfoAccessor : IMemberAccessor
{
private readonly IInvoker _invoker;

public PropertyInfoAccessor(PropertyInfo propertyInfo)
{
var delegateType = typeof(Func<,>).MakeGenericType(propertyInfo.DeclaringType, propertyInfo.PropertyType);
var d = propertyInfo.GetGetMethod().CreateDelegate(delegateType);

var invokerType = typeof(Invoker<,>).MakeGenericType(propertyInfo.DeclaringType, propertyInfo.PropertyType);
_invoker = Activator.CreateInstance(invokerType, new object[] { d }) as IInvoker;
}

public object Get(object obj, string name, TemplateContext ctx)
{
return _invoker.Invoke(obj);
}

private interface IInvoker
{
object Invoke(object target);
}

private sealed class Invoker<T, TResult> : IInvoker
{
private readonly Func<T, TResult> _d;

public Invoker(Delegate d)
{
_d = (Func<T, TResult>)d;
}

public object Invoke(object target)
{
return _d((T)target);
}
}
}
}
1 change: 0 additions & 1 deletion Fluid/MemberAccessStrategy.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;

namespace Fluid
{
Expand Down
58 changes: 19 additions & 39 deletions Fluid/MemberAccessStrategyExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Fluid.Accessors;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
Expand All @@ -10,60 +11,39 @@ namespace Fluid
{
public static class MemberAccessStrategyExtensions
{
internal static Dictionary<string, IMemberAccessor> _namedAccessors = new Dictionary<string, IMemberAccessor>();
private static Dictionary<Type, List<string>> _typeMembers = new Dictionary<Type, List<string>>();
// A cache of accessors so we don't rebuild them once they are added to global or contextual access strategies
internal static ConcurrentDictionary<Type, Dictionary<string, IMemberAccessor>> _typeMembers = new ConcurrentDictionary<Type, Dictionary<string, IMemberAccessor>>();

private static List<string> GetAllMembers(Type type)
internal static Dictionary<string, IMemberAccessor> GetTypeMembers(Type type)
{
if (!_typeMembers.TryGetValue(type, out var list))
return _typeMembers.GetOrAdd(type, t =>
{
list = new List<string>();
var list = new Dictionary<string, IMemberAccessor>();

foreach (var propertyInfo in type.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance))
foreach (var propertyInfo in t.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
list.Add(propertyInfo.Name);
var key = String.Concat("(", type.FullName, ")", propertyInfo.Name);
_namedAccessors[key] = new MethodInfoAccessor(propertyInfo.GetGetMethod());
list[propertyInfo.Name] = new PropertyInfoAccessor(propertyInfo);
}

foreach (var fieldInfo in type.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance))
foreach (var fieldInfo in t.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance))
{
list.Add(fieldInfo.Name);
var key = String.Concat("(", type.FullName, ")", fieldInfo.Name);
_namedAccessors[key] = new DelegateAccessor((o, n) => fieldInfo.GetValue(o));
list[fieldInfo.Name] = new DelegateAccessor((o, n) => fieldInfo.GetValue(o));
}

_typeMembers[type] = list;
}

return list;
return list;
});
}

public static IMemberAccessor GetNamedAccessor(Type type, string name)
internal static IMemberAccessor GetNamedAccessor(Type type, string name)
{
var key = String.Concat("(", type.FullName, ")", name);
var typeMembers = GetTypeMembers(type);

if (!_namedAccessors.TryGetValue(key, out var result))
if (typeMembers.TryGetValue(name, out var result))
{
var propertyInfo = type.GetTypeInfo().GetProperty(name, BindingFlags.Public | BindingFlags.Instance);

if (propertyInfo != null)
{
result = new MethodInfoAccessor(propertyInfo.GetGetMethod());
}

if (result == null)
{
var fieldInfo = type.GetTypeInfo().GetField(name, BindingFlags.Public | BindingFlags.Instance);

if (fieldInfo != null)
{
result = new DelegateAccessor((o, n) => fieldInfo.GetValue(o));
}
}
return result;
}

return result;
return null;
}


Expand All @@ -82,9 +62,9 @@ public static void Register<T>(this IMemberAccessStrategy strategy)
/// <param name="type">The type to register.</param>
public static void Register(this IMemberAccessStrategy strategy, Type type)
{
foreach (var name in GetAllMembers(type))
foreach (var entry in GetTypeMembers(type))
{
strategy.Register(type, name, GetNamedAccessor(type, name));
strategy.Register(type, entry.Key, entry.Value);
}
}

Expand Down

0 comments on commit 675498a

Please sign in to comment.