Skip to content

Commit

Permalink
fixed: #116
Browse files Browse the repository at this point in the history
  • Loading branch information
dadhi committed Apr 30, 2023
1 parent 9e92a04 commit ae42382
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 7 deletions.
37 changes: 30 additions & 7 deletions src/DryIoc/Container.cs
Expand Up @@ -1966,8 +1966,9 @@ internal void TryCacheDefaultFactory<T>(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);
}
Expand All @@ -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<object> 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);
}
Expand Down Expand Up @@ -2206,6 +2209,27 @@ internal class AndCache : Registry
isChangePermitted == _isChangePermitted ? this :
new AndCache(Services, _defaultFactoryCache, _keyedFactoryCache, _factoryExpressionCache, isChangePermitted);

public override Registry WithDefaultFactoryCache()
{
if (_defaultFactoryCache != null)
Interlocked.CompareExchange(ref _defaultFactoryCache, new ImHashMap<Type, object>[CACHE_SLOT_COUNT], null);
return this;
}

public override Registry WithKeyedFactoryCache()
{
if (_keyedFactoryCache != null)
Interlocked.CompareExchange(ref _keyedFactoryCache, new ImHashMap<Type, object>[CACHE_SLOT_COUNT], null);
return this;
}

public override Registry WithFactoryExpressionCache()
{
if (_factoryExpressionCache != null)
Interlocked.CompareExchange(ref _factoryExpressionCache, new ImMap<object>[CACHE_SLOT_COUNT], null);
return this;
}

public void TryCacheDefaultFactory<T>(int serviceTypeHash, Type serviceType, T factory)
{
if (_defaultFactoryCache == null)
Expand Down Expand Up @@ -2374,7 +2398,6 @@ internal sealed class CacheAndWrappersAndDecorators : AndCache

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<Type, object>[CACHE_SLOT_COUNT], null, null, default);

Expand Down
@@ -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<T> { }
class Query<T> : IQuery<T> { };
class QueryDecorator<T> : IQuery<T>
{
public readonly IQuery<T> Decoratee;
public QueryDecorator(IQuery<T> 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<string>), typeof(Query<string>));
container.Register(typeof(IQuery<string>), typeof(QueryDecorator<string>), setup: Setup.Decorator);

var tasks = new Task<IQuery<string>>[TaskCount];
for (var i = 0; i < tasks.Length; i++)
tasks[i] = Task.Run(() => container.Resolve<IQuery<string>>());

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<string>;
var success = decorator != null && decorator.Decoratee is QueryDecorator<string> == 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<IQuery<string>>[TaskCount];
for (var i = 0; i < tasks.Length; i++)
tasks[i] = Task.Run(() => container.Resolve<IQuery<string>>());

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<string>;
var success = decorator != null && decorator.Decoratee is QueryDecorator<string> == 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();
}
}
}
2 changes: 2 additions & 0 deletions test/DryIoc.TestRunner/Program.cs
Expand Up @@ -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();
Expand Down Expand Up @@ -61,6 +62,7 @@ void Run(Func<int> 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(),
Expand Down

0 comments on commit ae42382

Please sign in to comment.