Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 91 additions & 3 deletions src/Mapster.Tests/WhenRegisteringAndMappingRace.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;

namespace Mapster.Tests
{
Expand Down Expand Up @@ -39,6 +40,12 @@ public void Race_Condition_Produces_Error()

var simplePoco = new WhenAddingCustomMappings.SimplePoco {Id = Guid.NewGuid(), Name = "TestName"};

//first state (i = 0) Must be configured
TypeAdapterConfig<WhenAddingCustomMappings.SimplePoco, WeirdPoco>.NewConfig()
.Map(dest => dest.IHaveADifferentId, src => src.Id)
.Map(dest => dest.MyNamePropertyIsDifferent, src => src.Name)
.Ignore(dest => dest.Children);

var exception = Should.Throw<AggregateException>(() =>
{
for (int i = 0; i < 100; i++)
Expand Down Expand Up @@ -66,6 +73,12 @@ public void Explicit_Mapping_Requirement_Throws_Before_Mapping_Attempted()
TypeAdapterConfig.GlobalSettings.RequireExplicitMapping = true;
TypeAdapterConfig.GlobalSettings.RequireDestinationMemberSource = true;

//first state (i = 0) Must be configured
TypeAdapterConfig<WhenAddingCustomMappings.SimplePoco, WeirdPoco>.NewConfig()
.Map(dest => dest.IHaveADifferentId, src => src.Id)
.Map(dest => dest.MyNamePropertyIsDifferent, src => src.Name)
.Ignore(dest => dest.Children);

var simplePoco = new WhenAddingCustomMappings.SimplePoco { Id = Guid.NewGuid(), Name = "TestName" };

Should.Throw<AggregateException>(() =>
Expand All @@ -89,12 +102,87 @@ public void Explicit_Mapping_Requirement_Throws_Before_Mapping_Attempted()
TypeAdapter.Adapt<WhenAddingCustomMappings.SimplePoco, WeirdPoco>(simplePoco);
}

[TestMethod]
public void Race_Condition_Working()
{
TypeAdapterConfig.GlobalSettings.RequireExplicitMapping = true;
TypeAdapterConfig.GlobalSettings.RequireDestinationMemberSource = true;

var simplePoco = new WhenAddingCustomMappings.SimplePoco { Id = Guid.NewGuid(), Name = "TestName" };

//first state (i = 0) Must be configured
TypeAdapterConfigConcurrency<WhenAddingCustomMappings.SimplePoco, WeirdPoco>
.NewConfig(cfg =>
{
cfg
.Map(dest => dest.IHaveADifferentId, src => src.Id)
.Map(dest => dest.IHaveADifferentId, src => src.Id)
.Map(dest => dest.MyNamePropertyIsDifferent, src => src.Name)
.Ignore(dest => dest.Children);
});

for (int i = 0; i < 1000; i++)
{
Parallel.Invoke(
() =>
{
TypeAdapterConfigConcurrency<WhenAddingCustomMappings.SimplePoco, WeirdPoco>
.NewConfig(cfg =>
{
cfg
.Map(dest => dest.IHaveADifferentId, src => src.Id)
.Map(dest => dest.IHaveADifferentId, src => src.Id)
.Map(dest => dest.MyNamePropertyIsDifferent, src => src.Name)
.Ignore(dest => dest.Children);
});
},
() => { TypeAdapter.Adapt<WeirdPoco>(simplePoco); }
);
}

}

[TestMethod]
public void Scan_Race_Condition_Working()
{
TypeAdapterConfig.GlobalSettings.RequireExplicitMapping = true;
TypeAdapterConfig.GlobalSettings.RequireDestinationMemberSource = true;

var simplePoco = new WhenAddingCustomMappings.SimplePoco { Id = Guid.NewGuid(), Name = "TestName" };

//first state (i = 0) Must be configured
TypeAdapterConfig.GlobalSettings.Scan(Assembly.GetExecutingAssembly());


for (int i = 0; i < 1000; i++)
{
Parallel.Invoke(
() =>
{
TypeAdapterConfig.GlobalSettings.ScanConcurrency(Assembly.GetExecutingAssembly());
},
() => { TypeAdapter.Adapt<WeirdPoco>(simplePoco); }
);
}

}
}


#region TestClasses

public class RegData : IRegister
{
public void Register(TypeAdapterConfig config)
{
config.NewConfig<WhenAddingCustomMappings.SimplePoco, WeirdPoco>()
.Map(dest => dest.IHaveADifferentId, src => src.Id)
.Map(dest => dest.MyNamePropertyIsDifferent, src => src.Name)
.Ignore(dest => dest.Children);

}
}

public class SimplePoco
{
public Guid Id { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion src/Mapster.Tests/WhenScanningForRegisters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public void Registers_Are_Found()
{
var config = new TypeAdapterConfig();
IList<IRegister> registers = config.Scan(Assembly.GetExecutingAssembly());
registers.Count.ShouldBe(2);
registers.Count.ShouldBe(3);

var typeTuples = config.RuleMap.Keys.ToList();

Expand Down
130 changes: 105 additions & 25 deletions src/Mapster/TypeAdapterConfig.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
using System;
using Mapster.Adapters;
using Mapster.Models;
using Mapster.Utils;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using Mapster.Adapters;
using Mapster.Models;
using Mapster.Utils;
using System.Threading;

namespace Mapster
{
public class TypeAdapterConfig
{
#region ConcurrencyMod

[AdaptIgnore]
internal AutoResetEvent ConfigureSync { get; set; }

#endregion ConcurrencyMod

public static List<TypeAdapterRule> RulesTemplate { get; } = CreateRuleTemplate();
public static TypeAdapterConfig GlobalSettings { get; } = new TypeAdapterConfig();

Expand Down Expand Up @@ -95,6 +103,8 @@ private static List<TypeAdapterRule> CreateRuleTemplate()

public TypeAdapterConfig()
{
ConfigureSync = new(true);

Rules = RulesTemplate.ToList();
var settings = new TypeAdapterSettings();
Default = new TypeAdapterSetter(settings, this);
Expand Down Expand Up @@ -322,10 +332,19 @@ public Func<TSource, TDestination> GetMapFunction<TSource, TDestination>()
}
internal Delegate GetMapFunction(Type sourceType, Type destinationType)
{
var key = new TypeTuple(sourceType, destinationType);
if (!_mapDict.TryGetValue(key, out var del))
del = AddToHash(_mapDict, key, tuple => Compiler(CreateMapExpression(tuple, MapType.Map)));
return del;
ConfigureSync.WaitOne();

try
{
var key = new TypeTuple(sourceType, destinationType);
if (!_mapDict.TryGetValue(key, out var del))
del = AddToHash(_mapDict, key, tuple => Compiler(CreateMapExpression(tuple, MapType.Map)));
return del;
}
finally
{
ConfigureSync.Set();
}
}

private readonly ConcurrentDictionary<TypeTuple, Delegate> _mapToTargetDict = new ConcurrentDictionary<TypeTuple, Delegate>();
Expand All @@ -335,10 +354,20 @@ public Func<TSource, TDestination, TDestination> GetMapToTargetFunction<TSource,
}
internal Delegate GetMapToTargetFunction(Type sourceType, Type destinationType)
{
var key = new TypeTuple(sourceType, destinationType);
if (!_mapToTargetDict.TryGetValue(key, out var del))
del = AddToHash(_mapToTargetDict, key, tuple => Compiler(CreateMapExpression(tuple, MapType.MapToTarget)));
return del;
ConfigureSync.WaitOne();

try
{
var key = new TypeTuple(sourceType, destinationType);
if (!_mapToTargetDict.TryGetValue(key, out var del))
del = AddToHash(_mapToTargetDict, key, tuple => Compiler(CreateMapExpression(tuple, MapType.MapToTarget)));
return del;
}
finally
{
ConfigureSync.Set();
}

}

private readonly ConcurrentDictionary<TypeTuple, MethodCallExpression> _projectionDict = new ConcurrentDictionary<TypeTuple, MethodCallExpression>();
Expand All @@ -350,19 +379,37 @@ internal Expression<Func<TSource, TDestination>> GetProjectionExpression<TSource
}
internal MethodCallExpression GetProjectionCallExpression(Type sourceType, Type destinationType)
{
var key = new TypeTuple(sourceType, destinationType);
if (!_projectionDict.TryGetValue(key, out var del))
del = AddToHash(_projectionDict, key, CreateProjectionCallExpression);
return del;
ConfigureSync.WaitOne();

try
{
var key = new TypeTuple(sourceType, destinationType);
if (!_projectionDict.TryGetValue(key, out var del))
del = AddToHash(_projectionDict, key, CreateProjectionCallExpression);
return del;
}
finally
{
ConfigureSync.Set();
}
}

private readonly ConcurrentDictionary<TypeTuple, Delegate> _dynamicMapDict = new ConcurrentDictionary<TypeTuple, Delegate>();
public Func<object, TDestination> GetDynamicMapFunction<TDestination>(Type sourceType)
{
var key = new TypeTuple(sourceType, typeof(TDestination));
if (!_dynamicMapDict.TryGetValue(key, out var del))
del = AddToHash(_dynamicMapDict, key, tuple => Compiler(CreateDynamicMapExpression(tuple)));
return (Func<object, TDestination>)del;
ConfigureSync.WaitOne();

try
{
var key = new TypeTuple(sourceType, typeof(TDestination));
if (!_dynamicMapDict.TryGetValue(key, out var del))
del = AddToHash(_dynamicMapDict, key, tuple => Compiler(CreateDynamicMapExpression(tuple)));
return (Func<object, TDestination>)del;
}
finally
{
ConfigureSync.Set();
}
}

private Expression CreateSelfExpression()
Expand Down Expand Up @@ -403,6 +450,7 @@ public LambdaExpression CreateMapExpression(TypeTuple tuple, MapType mapType)
}
finally
{

if (fork != null)
context.Configs.Pop();
context.Running.Remove(tuple);
Expand Down Expand Up @@ -738,12 +786,25 @@ public IList<IRegister> Scan(params Assembly[] assemblies)
return registers;
}

public IList<IRegister> ScanConcurrency(params Assembly[] assemblies)
{
ConfigureSync.WaitOne();

try
{
return Scan(assemblies);
}
finally
{
ConfigureSync.Set();
}
}

/// <summary>
/// Applies type mappings.
/// </summary>
/// <param name="registers">collection of IRegister interface to apply mapping.</param>
public void Apply(IEnumerable<Lazy<IRegister>> registers)
/// <summary>
/// Applies type mappings.
/// </summary>
/// <param name="registers">collection of IRegister interface to apply mapping.</param>
public void Apply(IEnumerable<Lazy<IRegister>> registers)
{
Apply(registers.Select(register => register.Value));
}
Expand Down Expand Up @@ -882,4 +943,23 @@ public static void Clear()
TypeAdapterConfig.GlobalSettings.Remove(typeof(TSource), typeof(TDestination));
}
}

public static class TypeAdapterConfigConcurrency<TSource, TDestination>
{
public static void NewConfig(Action<TypeAdapterSetter<TSource, TDestination>> cfg)
{
var config = TypeAdapterConfig.GlobalSettings;

config.ConfigureSync.WaitOne();

try
{
cfg.Invoke(TypeAdapterConfig<TSource, TDestination>.NewConfig());
}
finally
{
config.ConfigureSync.Set();
}
}
}
}
Loading