diff --git a/src/Mapster.Tests/WhenRegisteringAndMappingRace.cs b/src/Mapster.Tests/WhenRegisteringAndMappingRace.cs index e6d796dc..f32250c6 100644 --- a/src/Mapster.Tests/WhenRegisteringAndMappingRace.cs +++ b/src/Mapster.Tests/WhenRegisteringAndMappingRace.cs @@ -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 { @@ -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.NewConfig() + .Map(dest => dest.IHaveADifferentId, src => src.Id) + .Map(dest => dest.MyNamePropertyIsDifferent, src => src.Name) + .Ignore(dest => dest.Children); + var exception = Should.Throw(() => { for (int i = 0; i < 100; i++) @@ -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.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(() => @@ -89,12 +102,87 @@ public void Explicit_Mapping_Requirement_Throws_Before_Mapping_Attempted() TypeAdapter.Adapt(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 + .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 + .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(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(simplePoco); } + ); + } + + } } #region TestClasses + public class RegData : IRegister + { + public void Register(TypeAdapterConfig config) + { + config.NewConfig() + .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; } diff --git a/src/Mapster.Tests/WhenScanningForRegisters.cs b/src/Mapster.Tests/WhenScanningForRegisters.cs index 20b287b6..c86e1298 100644 --- a/src/Mapster.Tests/WhenScanningForRegisters.cs +++ b/src/Mapster.Tests/WhenScanningForRegisters.cs @@ -16,7 +16,7 @@ public void Registers_Are_Found() { var config = new TypeAdapterConfig(); IList registers = config.Scan(Assembly.GetExecutingAssembly()); - registers.Count.ShouldBe(2); + registers.Count.ShouldBe(3); var typeTuples = config.RuleMap.Keys.ToList(); diff --git a/src/Mapster/TypeAdapterConfig.cs b/src/Mapster/TypeAdapterConfig.cs index 1e6abade..f865c3d8 100644 --- a/src/Mapster/TypeAdapterConfig.cs +++ b/src/Mapster/TypeAdapterConfig.cs @@ -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 RulesTemplate { get; } = CreateRuleTemplate(); public static TypeAdapterConfig GlobalSettings { get; } = new TypeAdapterConfig(); @@ -95,6 +103,8 @@ private static List CreateRuleTemplate() public TypeAdapterConfig() { + ConfigureSync = new(true); + Rules = RulesTemplate.ToList(); var settings = new TypeAdapterSettings(); Default = new TypeAdapterSetter(settings, this); @@ -322,10 +332,19 @@ public Func GetMapFunction() } 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 _mapToTargetDict = new ConcurrentDictionary(); @@ -335,10 +354,20 @@ public Func GetMapToTargetFunction 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 _projectionDict = new ConcurrentDictionary(); @@ -350,19 +379,37 @@ internal Expression> GetProjectionExpression _dynamicMapDict = new ConcurrentDictionary(); public Func GetDynamicMapFunction(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)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)del; + } + finally + { + ConfigureSync.Set(); + } } private Expression CreateSelfExpression() @@ -403,6 +450,7 @@ public LambdaExpression CreateMapExpression(TypeTuple tuple, MapType mapType) } finally { + if (fork != null) context.Configs.Pop(); context.Running.Remove(tuple); @@ -738,12 +786,25 @@ public IList Scan(params Assembly[] assemblies) return registers; } + public IList ScanConcurrency(params Assembly[] assemblies) + { + ConfigureSync.WaitOne(); + + try + { + return Scan(assemblies); + } + finally + { + ConfigureSync.Set(); + } + } - /// - /// Applies type mappings. - /// - /// collection of IRegister interface to apply mapping. - public void Apply(IEnumerable> registers) + /// + /// Applies type mappings. + /// + /// collection of IRegister interface to apply mapping. + public void Apply(IEnumerable> registers) { Apply(registers.Select(register => register.Value)); } @@ -882,4 +943,23 @@ public static void Clear() TypeAdapterConfig.GlobalSettings.Remove(typeof(TSource), typeof(TDestination)); } } + + public static class TypeAdapterConfigConcurrency + { + public static void NewConfig(Action> cfg) + { + var config = TypeAdapterConfig.GlobalSettings; + + config.ConfigureSync.WaitOne(); + + try + { + cfg.Invoke(TypeAdapterConfig.NewConfig()); + } + finally + { + config.ConfigureSync.Set(); + } + } + } } \ No newline at end of file