Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Random null-ref exceptions in cache implementation #212

Closed
replaysMike opened this issue Mar 31, 2021 · 10 comments
Closed

Random null-ref exceptions in cache implementation #212

replaysMike opened this issue Mar 31, 2021 · 10 comments
Assignees
Labels

Comments

@replaysMike
Copy link

replaysMike commented Mar 31, 2021

We are seeing random failures in our build pipeline which are caused by a bug within AgileMapper. We aren't using the static mapper, but rather Mapper.CreateNew() which seems to be causing the problem. We don't appear to have the issue when using the static mapper instance. It seems to be a thread safety issue but I'm not familiar enough with your implementation to narrow it down further.

System.NullReferenceException: Object reference not set to an instance of an object.
at Type AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingData<TSource, TTarget>.GetSourceMemberRuntimeType(IQualifiedMember childSourceMember)  
   at bool AgileObjects.AgileMapper.Members.SourceMemberMatcher+<EnumerateSourceMembers>d__9.MoveNext()  
   at bool AgileObjects.AgileMapper.Members.SourceMemberMatcher+<EnumerateSourceMembers>d__9.MoveNext()  
   at bool AgileObjects.AgileMapper.Extensions.PublicEnumerableExtensions+<Filter>d__4<TItem, TArg>.MoveNext()  
   at bool AgileObjects.AgileMapper.Members.SourceMemberMatcher.TryFindSourceMemberMatch(SourceMemberMatchContext context)  
   at bool AgileObjects.AgileMapper.Members.SourceMemberMatcher.TryFindParentContextSourceMemberMatch(SourceMemberMatchContext context)  
   at SourceMemberMatch AgileObjects.AgileMapper.Members.SourceMemberMatcher.GetMatchFor(SourceMemberMatchContext context)  
   at SourceMemberMatch AgileObjects.AgileMapper.DataSources.Factories.DataSourceFindContext.get_BestSourceMemberMatch()  
   at bool AgileObjects.AgileMapper.DataSources.Factories.DataSourceFindContext.UseSourceMemberDataSource()  
   at bool AgileObjects.AgileMapper.DataSources.Factories.SourceMemberDataSourcesFactory+<Create>d__0.MoveNext()  
   at bool AgileObjects.AgileMapper.DataSources.Factories.MemberDataSourceSetFactory+<EnumerateDataSources>d__2.MoveNext()  
   at new System.Linq.Buffer<TElement>(IEnumerable<TElement> source)  
   at TSource[] System.Linq.Enumerable.ToArray<TSource>(IEnumerable<TSource> source)  
   at IDataSourceSet AgileObjects.AgileMapper.DataSources.Factories.MemberDataSourceSetFactory.CreateFor(DataSourceFindContext findContext)  
   at IMemberPopulator AgileObjects.AgileMapper.Members.Population.MemberPopulatorFactory.Create(MemberPopulationContext context)  
   at bool AgileObjects.AgileMapper.Extensions.PublicEnumerableExtensions+<Project>d__1<TItem, TArg, TResult>.MoveNext()  
   at bool AgileObjects.AgileMapper.Extensions.PublicEnumerableExtensions+<Filter>d__4<TItem, TArg>.MoveNext()  
   at bool AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes.PopulationExpressionFactoryBase+<GetPopulationsAndCallbacks>d__3.MoveNext()  
   at new System.Collections.Generic.List<T>(IEnumerable<T> collection)  
   at List<TSource> System.Linq.Enumerable.ToList<TSource>(IEnumerable<TSource> source)  
   at void AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes.PopulationExpressionFactoryBase.AddPopulation(MappingCreationContext context)  
   at void AgileObjects.AgileMapper.ObjectPopulation.MappingExpressionFactoryBase.AddPopulationsAndCallbacks(MappingCreationContext context)+(MappingExpressionFactoryBase factory, MappingCreationContext ctx) => { }  
   at void AgileObjects.AgileMapper.ObjectPopulation.MappingExpressionFactoryBase.AddPopulationsAndCallbacks<TArg>(TArg argument, MappingCreationContext context, Action<TArg undefined, MappingCreationContext> mappingBodyPopulator)  
   at Expression AgileObjects.AgileMapper.ObjectPopulation.MappingExpressionFactoryBase.Create(IObjectMappingData mappingData)  
   at IDataSource AgileObjects.AgileMapper.DataSources.ComplexTypeDataSource.Create(IObjectMappingData mappingData)  
   at IDataSourceSet AgileObjects.AgileMapper.DataSources.Factories.Mapping.MappingDataSourceSetFactory.CreateFor(IObjectMappingData mappingData)  
   at ObjectMapper<TSource, TTarget> AgileObjects.AgileMapper.ObjectPopulation.ObjectMapperFactory.Create<TSource, TTarget>(ObjectMappingData<TSource undefined, TTarget> mappingData)  
   at IObjectMapper AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingData<TSource, TTarget>.GetOrCreateMapper()  
   at Expression AgileObjects.AgileMapper.ObjectPopulation.MappingFactory.GetInlineMappingBlock(IObjectMappingData mappingData, MappingValues mappingValues, CreateMappingDataCallFactory createMappingDataCallFactory)  
   at Expression AgileObjects.AgileMapper.DataSources.Factories.DerivedComplexTypeDataSourcesFactory.GetReturnMappingResultExpression(IObjectMappingData declaredTypeMappingData, Expression sourceValue, Type targetType, out IObjectMappingData)  
   at IDataSource AgileObjects.AgileMapper.DataSources.Factories.DerivedComplexTypeDataSourcesFactory.GetReturnMappingResultDataSource(IObjectMappingData declaredTypeMappingData, Expression condition, DerivedSourceTypeCheck derivedSourceCheck, Type targetType)  
   at void AgileObjects.AgileMapper.DataSources.Factories.DerivedComplexTypeDataSourcesFactory.AddDerivedSourceTypeDataSources(IEnumerable<Type> derivedSourceTypes, IObjectMappingData declaredTypeMappingData, IList<IDataSource> derivedTypeDataSources)  
   at IList<IDataSource> AgileObjects.AgileMapper.DataSources.Factories.DerivedComplexTypeDataSourcesFactory.CreateFor(IObjectMappingData declaredTypeMappingData)  
   at Expression AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes.ShortCircuits.DerivedComplexTypeMappingFactory.GetMappingOrNull(MappingCreationContext context, out bool)  
   at bool AgileObjects.AgileMapper.ObjectPopulation.MappingExpressionFactoryBase.ShortCircuitMapping(MappingCreationContext context)  
   at Expression AgileObjects.AgileMapper.ObjectPopulation.MappingExpressionFactoryBase.Create(IObjectMappingData mappingData)  
   at IDataSource AgileObjects.AgileMapper.DataSources.ComplexTypeDataSource.Create(IObjectMappingData mappingData)  
   at IDataSourceSet AgileObjects.AgileMapper.DataSources.Factories.Mapping.MappingDataSourceSetFactory.CreateFor(IObjectMappingData mappingData)  
   at ObjectMapper<TSource, TTarget> AgileObjects.AgileMapper.ObjectPopulation.ObjectMapperFactory.Create<TSource, TTarget>(ObjectMappingData<TSource undefined, TTarget> mappingData)  
   at IObjectMapper AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingData<TSource, TTarget>.GetOrCreateMapper()  
   at Expression AgileObjects.AgileMapper.ObjectPopulation.MappingFactory.GetInlineMappingBlock(IObjectMappingData mappingData, MappingValues mappingValues, CreateMappingDataCallFactory createMappingDataCallFactory)  
   at IDataSource AgileObjects.AgileMapper.DataSources.ComplexTypeDataSource.Create(IDataSource wrappedDataSource, int dataSourceIndex, IChildMemberMappingData complexTypeMappingData)  
   at IDataSource AgileObjects.AgileMapper.DataSources.Factories.DataSourceFindContext.get_MatchingSourceMemberDataSource()  
   at bool AgileObjects.AgileMapper.DataSources.Factories.DataSourceFindContext.UseSourceMemberDataSource()  
   at bool AgileObjects.AgileMapper.DataSources.Factories.SourceMemberDataSourcesFactory+<Create>d__0.MoveNext()  
   at bool AgileObjects.AgileMapper.DataSources.Factories.MemberDataSourceSetFactory+<EnumerateDataSources>d__2.MoveNext()  
   at new System.Linq.Buffer<TElement>(IEnumerable<TElement> source)  
   at TSource[] System.Linq.Enumerable.ToArray<TSource>(IEnumerable<TSource> source)  
   at IDataSourceSet AgileObjects.AgileMapper.DataSources.Factories.MemberDataSourceSetFactory.CreateFor(DataSourceFindContext findContext)  
   at IMemberPopulator AgileObjects.AgileMapper.Members.Population.MemberPopulatorFactory.Create(MemberPopulationContext context)  
   at bool AgileObjects.AgileMapper.Extensions.PublicEnumerableExtensions+<Project>d__1<TItem, TArg, TResult>.MoveNext()  
   at bool AgileObjects.AgileMapper.Extensions.PublicEnumerableExtensions+<Filter>d__4<TItem, TArg>.MoveNext()  
   at bool AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes.PopulationExpressionFactoryBase+<GetPopulationsAndCallbacks>d__3.MoveNext()  
   at new System.Collections.Generic.List<T>(IEnumerable<T> collection)  
   at List<TSource> System.Linq.Enumerable.ToList<TSource>(IEnumerable<TSource> source)
   at void AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes.PopulationExpressionFactoryBase.AddPopulation(MappingCreationContext context)  
   at void AgileObjects.AgileMapper.ObjectPopulation.MappingExpressionFactoryBase.AddPopulationsAndCallbacks(MappingCreationContext context)+(MappingExpressionFactoryBase factory, MappingCreationContext ctx) => { } 
   at void AgileObjects.AgileMapper.ObjectPopulation.MappingExpressionFactoryBase.AddPopulationsAndCallbacks<TArg>(TArg argument, MappingCreationContext context, Action<TArg undefined, MappingCreationContext> mappingBodyPopulator)  
   at Expression AgileObjects.AgileMapper.ObjectPopulation.MappingExpressionFactoryBase.Create(IObjectMappingData mappingData)  
   at IDataSource AgileObjects.AgileMapper.DataSources.ComplexTypeDataSource.Create(IObjectMappingData mappingData)  
   at IDataSourceSet AgileObjects.AgileMapper.DataSources.Factories.Mapping.MappingDataSourceSetFactory.CreateFor(IObjectMappingData mappingData)  
   at ObjectMapper<TSource, TTarget> AgileObjects.AgileMapper.ObjectPopulation.ObjectMapperFactory.Create<TSource, TTarget>(ObjectMappingData<TSource undefined, TTarget> mappingData)  
   at IObjectMapper AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingData<TSource, TTarget>.GetOrCreateMapper()  
   at TValue AgileObjects.AgileMapper.Caching.ArrayCache<TKey, TValue>.GetOrAdd(TKey key, Func<TKey undefined, TValue> valueFactory)  
   at ObjectMapper<TSource, TTarget> AgileObjects.AgileMapper.ObjectPopulation.ObjectMapperFactory.GetOrCreateRoot<TSource, TTarget>(ObjectMappingData<TSource undefined, TTarget> mappingData)  
   at IObjectMappingData lambda_method.IObjectMappingData lambda_method(Closure undefined, object undefined, SspInventory undefined, int? undefined, object undefined, MappingTypes undefined, IMappingContext undefined, IObjectMappingData undefined)  
   at IObjectMappingData AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingDataFactory.Create<TDeclaredSource, TDeclaredTarget>(TDeclaredSource source, TDeclaredTarget target, int? elementIndex, object elementKey, MappingTypes mappingTypes, IMappingContext mappingContext, IObjectMappingData parent)  
   at IObjectMappingData AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingDataFactory.ForRoot<TSource, TTarget>(TSource source, TTarget target, IMappingContext mappingContext) 
   at TTarget AgileObjects.AgileMapper.MappingExecutor<TSource>.PerformMapping<TTarget>(TTarget target)  
   at TResult AgileObjects.AgileMapper.MappingExecutor<TSource>.ToANew<TResult>() 
   at Task<IHttpActionResult<SimpleObjectResponse>> 
......more....
@SteveWilkes
Copy link
Member

Thanks for letting me know - It's a funny place for it to fall over! - I'll check it out soon.

@replaysMike
Copy link
Author

here's another one, different stack trace but also a null-ref:

System.NullReferenceException: Object reference not set to an instance of an object.
at AgileObjects.AgileMapper.Caching.DefaultComparer`1.Equals(T x, T y)  
   at AgileObjects.AgileMapper.Caching.ArrayCache`2.TryGetValue(TKey key, Int32 startIndex, TValue& value)  
   at AgileObjects.AgileMapper.Caching.ArrayCache`2.GetOrAdd(TKey key, Func`2 valueFactory)  
   at AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes.ComplexTypeConstructionFactory.GetTargetObjectCreation(IObjectMappingData mappingData)  
   at AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes.TargetObjectResolutionFactory.GetObjectResolution(Func`3 constructionFactory, IObjectMappingData mappingData, IList`1 memberPopulations, Boolean assignCreatedObject, Boolean assignTargetObject)  
   at AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes.PopulationExpressionFactoryBase.GetLocalVariableInstantiation(Boolean assignCreatedObject, IList`1 memberPopulations, IObjectMappingData mappingData)  
   at AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes.PopulationExpressionFactoryBase.AddPopulation(MappingCreationContext context)  
   at AgileObjects.AgileMapper.ObjectPopulation.MappingExpressionFactoryBase.<AddPopulationsAndCallbacks>b__11_0(MappingExpressionFactoryBase factory, MappingCreationContext ctx)  
   at AgileObjects.AgileMapper.ObjectPopulation.MappingExpressionFactoryBase.AddPopulationsAndCallbacks[TArg](TArg argument, MappingCreationContext context, Action`2 mappingBodyPopulator)  
   at AgileObjects.AgileMapper.ObjectPopulation.MappingExpressionFactoryBase.Create(IObjectMappingData mappingData)  
   at AgileObjects.AgileMapper.DataSources.ComplexTypeDataSource.Create(IObjectMappingData mappingData)  
   at AgileObjects.AgileMapper.DataSources.Factories.Mapping.MappingDataSourceSetFactory.CreateFor(IObjectMappingData mappingData)  
   at AgileObjects.AgileMapper.ObjectPopulation.ObjectMapperFactory.Create[TSource,TTarget](ObjectMappingData`2 mappingData)  
   at AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingData`2.GetOrCreateMapper()  
   at AgileObjects.AgileMapper.ObjectPopulation.MappingFactory.GetInlineMappingBlock(IObjectMappingData mappingData, MappingValues mappingValues, CreateMappingDataCallFactory createMappingDataCallFactory)  
   at AgileObjects.AgileMapper.ObjectPopulation.Enumerables.EnumerablePopulationBuilder.GetElementPopulation(IPopulationLoopData loopData, IObjectMappingData enumerableMappingData)  
   at AgileObjects.AgileMapper.ObjectPopulation.Enumerables.Looping.PopulationLoopDataExtensions.BuildPopulationLoop[TLoopData](TLoopData loopData, EnumerablePopulationBuilder builder, IObjectMappingData mappingData, Func`3 elementPopulationFactory)  
   at AgileObjects.AgileMapper.ObjectPopulation.Enumerables.EnumerablePopulationBuilder.BuildPopulationLoop(Func`3 elementPopulationFactory, IObjectMappingData mappingData)  
   at AgileObjects.AgileMapper.ObjectPopulation.Enumerables.CopySourceEnumerablePopulationStrategy.Create(EnumerablePopulationBuilder builder, IObjectMappingData enumerableMappingData)  
   at AgileObjects.AgileMapper.ObjectPopulation.Enumerables.EnumerableMappingExpressionFactory.AddObjectPopulation(MappingCreationContext context)  
   at AgileObjects.AgileMapper.ObjectPopulation.MappingExpressionFactoryBase.<AddPopulationsAndCallbacks>b__11_0(MappingExpressionFactoryBase factory, MappingCreationContext ctx)  
   at AgileObjects.AgileMapper.ObjectPopulation.MappingExpressionFactoryBase.AddPopulationsAndCallbacks[TArg](TArg argument, MappingCreationContext context, Action`2 mappingBodyPopulator)  
   at AgileObjects.AgileMapper.ObjectPopulation.MappingExpressionFactoryBase.Create(IObjectMappingData mappingData)  
   at AgileObjects.AgileMapper.DataSources.Factories.Mapping.MappingDataSourceFactoryBase.CreateFor(IObjectMappingData mappingData)  
   at AgileObjects.AgileMapper.DataSources.Factories.Mapping.MappingDataSourceSetFactory.CreateFor(IObjectMappingData mappingData)  
   at AgileObjects.AgileMapper.ObjectPopulation.ObjectMapperFactory.Create[TSource,TTarget](ObjectMappingData`2 mappingData)  
   at AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingData`2.GetOrCreateMapper()  
   at AgileObjects.AgileMapper.ObjectPopulation.MappingFactory.GetInlineMappingBlock(IObjectMappingData mappingData, MappingValues mappingValues, CreateMappingDataCallFactory createMappingDataCallFactory)  
   at AgileObjects.AgileMapper.DataSources.EnumerableDataSource..ctor(IDataSource sourceEnumerableDataSource, Int32 dataSourceIndex, IChildMemberMappingData enumerableMappingData)  
   at AgileObjects.AgileMapper.DataSources.Factories.DataSourceFindContext.GetFinalDataSource(IDataSource foundDataSource, IChildMemberMappingData mappingData)  
   at AgileObjects.AgileMapper.DataSources.Factories.DataSourceFindContext.get_MatchingSourceMemberDataSource()  
   at AgileObjects.AgileMapper.DataSources.Factories.DataSourceFindContext.UseSourceMemberDataSource()  
   at AgileObjects.AgileMapper.DataSources.Factories.SourceMemberDataSourcesFactory.<Create>d__0.MoveNext()  
   at AgileObjects.AgileMapper.DataSources.Factories.MemberDataSourceSetFactory.<EnumerateDataSources>d__2.MoveNext()  
   at System.Linq.Buffer`1..ctor(IEnumerable`1 source)  
   at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)  
   at AgileObjects.AgileMapper.DataSources.Factories.MemberDataSourceSetFactory.CreateFor(DataSourceFindContext findContext)  
   at AgileObjects.AgileMapper.Members.Population.MemberPopulatorFactory.Create(MemberPopulationContext context)  
   at AgileObjects.AgileMapper.Extensions.PublicEnumerableExtensions.<Project>d__1`3.MoveNext()  
   at AgileObjects.AgileMapper.Extensions.PublicEnumerableExtensions.<Filter>d__4`2.MoveNext()  
   at AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes.PopulationExpressionFactoryBase.<GetPopulationsAndCallbacks>d__3.MoveNext()  
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)  
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)  
   at AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes.PopulationExpressionFactoryBase.AddPopulation(MappingCreationContext context)  
   at AgileObjects.AgileMapper.ObjectPopulation.MappingExpressionFactoryBase.<AddPopulationsAndCallbacks>b__11_0(MappingExpressionFactoryBase factory, MappingCreationContext ctx)  
   at AgileObjects.AgileMapper.ObjectPopulation.MappingExpressionFactoryBase.AddPopulationsAndCallbacks[TArg](TArg argument, MappingCreationContext context, Action`2 mappingBodyPopulator)  
   at AgileObjects.AgileMapper.ObjectPopulation.MappingExpressionFactoryBase.Create(IObjectMappingData mappingData)  
   at AgileObjects.AgileMapper.DataSources.ComplexTypeDataSource.Create(IObjectMappingData mappingData)  
   at AgileObjects.AgileMapper.DataSources.Factories.Mapping.MappingDataSourceSetFactory.CreateFor(IObjectMappingData mappingData)  
   at AgileObjects.AgileMapper.ObjectPopulation.ObjectMapperFactory.Create[TSource,TTarget](ObjectMappingData`2 mappingData)  
   at AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingData`2.GetOrCreateMapper()  
   at AgileObjects.AgileMapper.Caching.ArrayCache`2.GetOrAdd(TKey key, Func`2 valueFactory)  
   at AgileObjects.AgileMapper.ObjectPopulation.ObjectMapperFactory.GetOrCreateRoot[TSource,TTarget](ObjectMappingData`2 mappingData)  
   at AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingData`2..ctor(TSource source, TTarget target, Nullable`1 elementIndex, Object elementKey, MappingTypes mappingTypes, IMappingContext mappingContext, IObjectMappingData declaredTypeMappingData, IObjectMappingData parent, Boolean createMapper)  
   at AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingData`2..ctor(TSource source, TTarget target, Nullable`1 elementIndex, Object elementKey, MappingTypes mappingTypes, IMappingContext mappingContext, IObjectMappingData parent, Boolean createMapper)  
   at lambda_method.lambda_method(Closure , Object , EstimatesResult , Nullable`1 , Object , MappingTypes , IMappingContext , IObjectMappingData )  
   at AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingDataFactory.ForRoot[TSource,TTarget](TSource source, TTarget target, IMappingContext mappingContext)  
   at AgileObjects.AgileMapper.MappingExecutor`1.PerformMapping[TTarget](TTarget target)  
   at AgileObjects.AgileMapper.MappingExecutor`1.ToANew[TResult]() 

@julealgon
Copy link

@SteveWilkes you might want to look into that ReSharper suppression in the key comparer implementation:

internal struct DefaultComparer<T> : IKeyComparer<T>
{
public bool UseHashCodes => false;
public bool Equals(T x, T y)
{
// ReSharper disable once PossibleNullReferenceException
return ReferenceEquals(x, y) || x.Equals(y);
}
}

I assume the suppression is there due to some sort of guarantee that x would never be null. However as per the trace above, it sounds like it is a possibility in some situations.

If null really is a valid input in some circumstance, using object.Equals(T1,T2) could be a simple way to workaround the warning.

@SteveWilkes
Copy link
Member

Thanks both! I'll look at getting a bug-fix release sorted out over the weekend.

@julealgon
Copy link

@SteveWilkes , over the weekend I realized exactly what is causing these problems.

The root of the issue lies here:

            var lazyAgileMapper = new Lazy<AgileObjects.AgileMapper.IMapper>(() => AgileObjects.AgileMapper.Mapper.CreateNew());
            container.Configure(c => c.For<AgileObjects.AgileMapper.IMapper>().Use(() => lazyAgileMapper.Value));

Notice how we lazily initialize the mapper, then register it in the container with a factory argument. This is seemingly ok, if it wasn't for the fact that registrations are transient by default.

What happens here is that the container (StructureMap in this case) takes "ownership" of the created instance and disposes it after each request. However, it is actually disposing a shared instance, that could be in use by a different thread serving a different request.

This clearly means the following:

  1. Our registration code was problematic by nature: you should never register a shared instance as a transient service like that
  2. Your cache implementation in combination with Dispose is not thread-safe (this is what is causing the actual exceptions, as the cache is purged by thread A while a mapping is occurring in thread B)

You should be able to repro the issue on your side by introducing some artificial delays in the cache access code while a mapping is taking place, and then, in parallel, dispose of the mapper instance.

While you can probably fix this problem by making your cache thread-safe, I also suggest another change: throw ObjectDisposedException when attempting to call any methods on a disposed mapper. This is a common design to signal to callers that they shouldn't try to keep using a disposed instance: it is in an invalid state now.

I believe that, if such exception was present, it wouldn't've taken this long for us to realize our mistake in the registration code and consequently the impact of the problem would've been reduced significantly.

@SteveWilkes
Copy link
Member

Hi @julealgon - that's a great find, thanks very much! I'm in the process of putting together a v1.8 release - I'll add the Disposed checks as you suggest.

@SteveWilkes
Copy link
Member

The v1.8 branch now handles null keys in the default cache key comparer, throws ObjectDisposedException on any attempt to use a disposed mapper, and has a fix for a threading issue found in the cache implementation. Release to follow.

Thanks again!

@SteveWilkes SteveWilkes self-assigned this Apr 6, 2021
@SteveWilkes SteveWilkes added bug in-branch A feature or bug fix exists in code, and a release will follow labels Apr 6, 2021
@SteveWilkes
Copy link
Member

Hi,

I've now uploaded a preview v1.8 release to NuGet with the caching and ObjectDisposed issues fixed - please could you try it out and let me know how you get on?

Thanks,

Steve

@SteveWilkes SteveWilkes added in-preview A feature or bug fix exists in a preview release, and a full release will follow and removed in-branch A feature or bug fix exists in code, and a release will follow labels Apr 6, 2021
@SteveWilkes
Copy link
Member

Have you had a chance to try the v1.8 preview, @replaysMike ?

SteveWilkes added a commit that referenced this issue Apr 28, 2021
* Updating AO packages

* Adding .NET Standard 2.0 target / Rationalising compiler directives / Removing non-latest-major-version .NET Core test projects

* Fixing caching thread safety issue / Handling null keys in default key comparer, re: #212

* Throwing ObjectDisposedException on attempt to use a disposed mapper

* Simplifying numeric constants setup, re: #213

* Tidying

* Improving target member selection, re: #209

* Adding v1.8 preview NuGet package

* Tidying documentation

* Organising classes

* Features/target member matcher data sources (#214)

* Adding data source -> target member matcher selection

* Member matcher data sources / Conflict testing / Documentation improvements

* Fixing query projection

* All fixed

* Adding documentation

* Updating release notes / Updating to v1.8

* Extra test coverage

* Adding v1.8 NuGet package
@SteveWilkes SteveWilkes removed the in-preview A feature or bug fix exists in a preview release, and a full release will follow label Apr 28, 2021
@SteveWilkes
Copy link
Member

The changes made for this issue are in the full v1.8 release, which is now available on NuGet.

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants