diff --git a/src/DryIoc/Container.cs b/src/DryIoc/Container.cs index 4470fd2ac..285bf254d 100644 --- a/src/DryIoc/Container.cs +++ b/src/DryIoc/Container.cs @@ -1966,8 +1966,9 @@ internal void TryCacheDefaultFactory(int serviceTypeHash, Type serviceType, T if (registry == null ? registryOrServices.IsEmpty : registry.Services.IsEmpty) return; - var withCache = registry as Registry.AndCache ?? - (Registry.AndCache)_registry.SwapAndGetNewValue(0, (r, _) => (r as Registry ?? new Registry(r)).WithDefaultFactoryCache()); // todo: @perf optimize + var withCache = (Registry.AndCache)( + (registry as Registry.AndCache)?.WithDefaultFactoryCache() ?? + (Registry.AndCache)_registry.SwapAndGetNewValue(0, (r, _) => (r as Registry ?? new Registry(r)).WithDefaultFactoryCache())); withCache.TryCacheDefaultFactory(serviceTypeHash, serviceType, factory); } @@ -1980,16 +1981,18 @@ internal void TryCacheKeyedFactory(int serviceTypeHash, Type serviceType, object if (registry == null ? registryOrServices.IsEmpty : registry.Services.IsEmpty) return; - var withCache = registry as Registry.AndCache ?? - (Registry.AndCache)_registry.SwapAndGetNewValue(0, (r, _) => (r as Registry ?? new Registry(r)).WithKeyedFactoryCache()); // todo: @perf optimize + var withCache = (Registry.AndCache)( + (registry as Registry.AndCache)?.WithKeyedFactoryCache() ?? + (Registry.AndCache)_registry.SwapAndGetNewValue(0, (r, _) => (r as Registry ?? new Registry(r)).WithKeyedFactoryCache())); withCache.TryCacheKeyedFactory(serviceTypeHash, serviceType, key, factory); } internal void CacheFactoryExpression(int factoryId, Expression expr, IReuse reuse, int dependencyCount, ImMapEntry entry = null) { - var withCache = _registry.Value as Registry.AndCache ?? - (Registry.AndCache)_registry.SwapAndGetNewValue(0, (r, _) => (r as Registry ?? new Registry(r)).WithFactoryExpressionCache()); // todo: @perf optimize + var withCache = (Registry.AndCache)( + (_registry.Value as Registry.AndCache)?.WithFactoryExpressionCache() ?? + (Registry.AndCache)_registry.SwapAndGetNewValue(0, (r, _) => (r as Registry ?? new Registry(r)).WithFactoryExpressionCache())); withCache.CacheFactoryExpression(factoryId, expr, reuse, dependencyCount, entry); } @@ -2206,6 +2209,27 @@ public override Registry WithIsChangePermitted(IsRegistryChangePermitted isChang isChangePermitted == _isChangePermitted ? this : new AndCache(Services, _defaultFactoryCache, _keyedFactoryCache, _factoryExpressionCache, isChangePermitted); + public override Registry WithDefaultFactoryCache() + { + if (_defaultFactoryCache != null) + Interlocked.CompareExchange(ref _defaultFactoryCache, new ImHashMap[CACHE_SLOT_COUNT], null); + return this; + } + + public override Registry WithKeyedFactoryCache() + { + if (_keyedFactoryCache != null) + Interlocked.CompareExchange(ref _keyedFactoryCache, new ImHashMap[CACHE_SLOT_COUNT], null); + return this; + } + + public override Registry WithFactoryExpressionCache() + { + if (_factoryExpressionCache != null) + Interlocked.CompareExchange(ref _factoryExpressionCache, new ImMap[CACHE_SLOT_COUNT], null); + return this; + } + public void TryCacheDefaultFactory(int serviceTypeHash, Type serviceType, T factory) { if (_defaultFactoryCache == null) @@ -2374,7 +2398,6 @@ public override Registry WithIsChangePermitted(IsRegistryChangePermitted isChang public virtual Registry WithoutCache() => this; - // todo: @perf optimize to avoid creating the new Registry instance when called in WithCache and descendants public virtual Registry WithDefaultFactoryCache() => new AndCache(Services, new ImHashMap[CACHE_SLOT_COUNT], null, null, default); diff --git a/test/DryIoc.IssuesTests/GHIssue116_ReOpened_DryIoc_Resolve_with_decorators_goes_wrong_for_parallel_execution.cs b/test/DryIoc.IssuesTests/GHIssue116_ReOpened_DryIoc_Resolve_with_decorators_goes_wrong_for_parallel_execution.cs index e69de29bb..4073b28f9 100644 --- a/test/DryIoc.IssuesTests/GHIssue116_ReOpened_DryIoc_Resolve_with_decorators_goes_wrong_for_parallel_execution.cs +++ b/test/DryIoc.IssuesTests/GHIssue116_ReOpened_DryIoc_Resolve_with_decorators_goes_wrong_for_parallel_execution.cs @@ -0,0 +1,142 @@ +using System; +using System.Text; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; + +using NUnit.Framework; + +namespace DryIoc.IssuesTests +{ + [TestFixture] + public class GHIssue116_ReOpened_DryIoc_Resolve_with_decorators_goes_wrong_for_parallel_execution : ITest + { + const int IterCount = 64; + const int TaskCount = 64; + + public int Run() + { + DryIoc_Resolve_parallel_execution_on_repeat(); + DryIoc_Resolve_parallel_execution_with_compile_service_expression_on_repeat(); + return 2; + } + + interface IQuery { } + class Query : IQuery { }; + class QueryDecorator : IQuery + { + public readonly IQuery Decoratee; + public QueryDecorator(IQuery decoratee) => Decoratee = decoratee; + } + + public void DryIoc_Resolve_parallel_execution_on_repeat() + { + for (var i = 0; i < IterCount; i++) + DryIoc_Resolve_parallel_execution(); + } + + public void DryIoc_Resolve_parallel_execution_with_compile_service_expression_on_repeat() + { + for (var i = 0; i < IterCount; i++) + DryIoc_Resolve_parallel_execution_with_compile_service_expression(); + } + + [Test] + public void DryIoc_Resolve_parallel_execution() + { + var container = new Container(); + + container.Register(typeof(IQuery), typeof(Query)); + container.Register(typeof(IQuery), typeof(QueryDecorator), setup: Setup.Decorator); + + var tasks = new Task>[TaskCount]; + for (var i = 0; i < tasks.Length; i++) + tasks[i] = Task.Run(() => container.Resolve>()); + + Task.WaitAll(tasks); + + var failed = false; + var sb = new StringBuilder(tasks.Length); + for (var i = 0; i < tasks.Length; i++) + { + var result = tasks[i].Result; + var decorator = result as QueryDecorator; + var success = decorator != null && decorator.Decoratee is QueryDecorator == false; + failed |= !success; + sb.Append(success ? '_' : decorator == null ? 'F' : 'f'); + } + + Assert.IsFalse(failed, $"Some of {tasks.Length} tasks are failed [{sb}]"); + } + + [Test] + public void DryIoc_Resolve_parallel_execution_with_compile_service_expression() + { + var container = new Container(Rules.Default.WithoutInterpretationForTheFirstResolution()); + + // check that open-generics work as well + container.Register(typeof(IQuery<>), typeof(Query<>)); + container.Register(typeof(IQuery<>), typeof(QueryDecorator<>), setup: Setup.Decorator); + + var tasks = new Task>[TaskCount]; + for (var i = 0; i < tasks.Length; i++) + tasks[i] = Task.Run(() => container.Resolve>()); + + Task.WaitAll(tasks); + + var failed = false; + var sb = new StringBuilder(tasks.Length); + for (var i = 0; i < tasks.Length; i++) + { + var result = tasks[i].Result; + var decorator = result as QueryDecorator; + var success = decorator != null && decorator.Decoratee is QueryDecorator == false; + failed |= !success; + sb.Append(success ? '_' : decorator == null ? 'F' : 'f'); + } + + Assert.IsFalse(failed, $"Some of {tasks.Length} tasks are failed [{sb}]"); + } + } + + class SingleThreadSynchronizationContext : SynchronizationContext, IDisposable + { + private readonly Thread _thread; + private readonly CancellationTokenSource _cancellationTokenSource; + private readonly BlockingCollection<(SendOrPostCallback, object)> _tasks; + + public SingleThreadSynchronizationContext() + { + _cancellationTokenSource = new(); + _tasks = new(); + _thread = new Thread(static state => + { + var ctx = (SingleThreadSynchronizationContext)state; + SynchronizationContext.SetSynchronizationContext(ctx); + try + { + while (!ctx._cancellationTokenSource.IsCancellationRequested) + { + var (post, a) = ctx._tasks.Take(ctx._cancellationTokenSource.Token); + post(a); + } + } + catch (OperationCanceledException) + { + // Ignore the cancellation exception + } + }); + _thread.Start(this); + } + + public override void Post(SendOrPostCallback d, object state) => _tasks.Add((d, state)); + + public void Dispose() + { + _cancellationTokenSource.Cancel(); + _thread.Join(); + _tasks.Dispose(); + _cancellationTokenSource.Dispose(); + } + } +} diff --git a/test/DryIoc.TestRunner/Program.cs b/test/DryIoc.TestRunner/Program.cs index e575d752b..7d189f5cf 100644 --- a/test/DryIoc.TestRunner/Program.cs +++ b/test/DryIoc.TestRunner/Program.cs @@ -10,6 +10,7 @@ public static void Main() { RunAllTests(); + // new GHIssue116_ReOpened_DryIoc_Resolve_with_decorators_goes_wrong_for_parallel_execution().Run(); // new RequiredPropertiesTests().Run(); // new PropertyResolutionTests().Run(); // new IssuesTests.Samples.DefaultReuseTest().Run(); @@ -61,6 +62,7 @@ void Run(Func run, string name = null) new RequiredPropertiesTests(), new SelectConstructorWithAllResolvableArgumentTests(), new Issue107_NamedScopesDependingOnResolvedTypes(), + new GHIssue116_ReOpened_DryIoc_Resolve_with_decorators_goes_wrong_for_parallel_execution(), new GHIssue378_InconsistentResolutionFailure(), new GHIssue380_ExportFactory_throws_Container_disposed_exception(), new GHIssue391_Deadlock_during_Resolve(),