Skip to content

Commit

Permalink
fixed: #536
Browse files Browse the repository at this point in the history
  • Loading branch information
dadhi committed Nov 1, 2022
1 parent 97e5861 commit 4d5130f
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 20 deletions.
8 changes: 4 additions & 4 deletions src/DryIoc/Container.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ partial class Container
{
partial void GetLastGeneratedFactoryID(ref int lastFactoryID)
{
lastFactoryID = 597; // generated: equals to the last used Factory.FactoryID
lastFactoryID = 674; // generated: equals to the last used Factory.FactoryID
}

partial void ResolveGenerated(ref object service, Type serviceType)
Expand All @@ -62,7 +62,7 @@ partial void ResolveGenerated(ref object service,
requiredServiceType == null &&
Equals(preRequestParent, Request.Empty.Push(
typeof(IService),
592,
669,
typeof(MyService),
Reuse.Transient,
RequestFlags.IsResolutionCall)))
Expand Down Expand Up @@ -92,7 +92,7 @@ internal static object Get_IService_0(IResolverContext r) =>
null,
Request.Empty.Push(
typeof(IService),
592,
669,
typeof(MyService),
Reuse.Transient,
RequestFlags.IsResolutionCall|RequestFlags.StopRecursiveDependencyCheck),
Expand All @@ -104,7 +104,7 @@ internal static object Get_IService_0(IResolverContext r) =>
null,
Request.Empty.Push(
typeof(IService),
592,
669,
typeof(MyService),
Reuse.Transient,
RequestFlags.IsResolutionCall|RequestFlags.StopRecursiveDependencyCheck),
Expand Down
54 changes: 44 additions & 10 deletions src/DryIoc/Container.cs
Original file line number Diff line number Diff line change
Expand Up @@ -431,13 +431,15 @@ private object ResolveAndCache(int serviceTypeHash, Type serviceType, IfUnresolv
return value;
}

// Important to cache expression first before trying to interpret, so that parallel resolutions may already use it.
if (factory.CanCache)
TryCacheDefaultFactory(serviceTypeHash, serviceType, expr);

// 1) First try to interpret
if (Interpreter.TryInterpretAndUnwrapContainerException(this, expr, out var instance))
{
// Nope. Important to cache expression first before trying to interpret, so that parallel resolutions may already use it.
// todo: @wip ...but what if exception is thrown, isn't it better to avoid caching the bad expression?
if (factory.CanCache)
TryCacheDefaultFactory(serviceTypeHash, serviceType, expr);
return instance;
}

// 2) Fallback to expression compilation
factoryDelegate = expr.CompileToFactoryDelegate(rules.UseInterpretation);
Expand Down Expand Up @@ -3028,10 +3030,35 @@ public static bool TryInterpretAndUnwrapContainerException(IResolverContext r, E
// Or if unsuccessful we may get the wrong exception, but it is even more unlikely case.
// It is unlikely in the first place because the majority of cases the scope access is not concurrent.
// Comparing to the singletons where it is expected to be concurrent, but it does not addressed here.
TrySetScopedItemException(r, tex.InnerException);
throw tex.InnerException.TryRethrowWithPreservedStackTrace();
}
}

private static void TrySetScopedItemException(IResolverContext r, Exception ex)
{
ScopedItemException itemEx = null;
var s = r.CurrentScope as Scope;
while (s != null)
{
var itemMaps = s.CloneMaps();
foreach (var m in itemMaps)
{
if (!m.IsEmpty)
foreach (var it in m.Enumerate())
{
if (it.Value == Scope.NoItem)
{
itemEx ??= new ScopedItemException(ex);
if (Interlocked.CompareExchange(ref it.Value, itemEx, Scope.NoItem) == Scope.NoItem)
return; // done
}
}
}
s = s.Parent as Scope;
}
}

internal static bool TryInterpretSingletonAndUnwrapContainerException(IResolverContext r, Expression expr, ImMapEntry<object> itemRef, out object result)
{
try
Expand Down Expand Up @@ -3107,7 +3134,10 @@ public static bool TryInterpret(IResolverContext r, Expression expr,
}
case ExprType.Call:
{
return TryInterpretMethodCall(r, (MethodCallExpression)expr, paramExprs, paramValues, parentArgs, ref result);
var ok = TryInterpretMethodCall(r, (MethodCallExpression)expr, paramExprs, paramValues, parentArgs, ref result);
if (ok && result is ScopedItemException ie)
ie.ReThrow();
return ok;
}
case ExprType.Convert:
{
Expand All @@ -3118,6 +3148,8 @@ public static bool TryInterpret(IResolverContext r, Expression expr,
{
if (!TryInterpretMethodCall(r, m, paramExprs, paramValues, parentArgs, ref instance))
return false;
if (instance is ScopedItemException ie)
ie.ReThrow();
}
else if (!TryInterpret(r, operandExpr, paramExprs, paramValues, parentArgs, out instance))
return false;
Expand Down Expand Up @@ -12835,17 +12867,19 @@ public class Scope : IScope

internal static readonly object NoItem = new object();

private static ImMap<object>[] _emptySlots = CreateEmptyMaps();
private static ImMap<object>[] _emptyMaps = CreateEmptyMaps();

private static ImMap<object>[] CreateEmptyMaps()
{
var slots = new ImMap<object>[MAP_COUNT];
var maps = new ImMap<object>[MAP_COUNT];
var empty = ImMap<object>.Empty;
for (var i = 0; i < MAP_COUNT; ++i)
slots[i] = empty;
return slots;
maps[i] = empty;
return maps;
}

internal ImMap<object>[] CloneMaps() => _maps.CopyNonEmpty();

///<summary>Creating</summary>
[MethodImpl((MethodImplOptions)256)]
public static IScope Of(IScope parent, object name) =>
Expand Down Expand Up @@ -13140,7 +13174,7 @@ public void Dispose()

_disposables = ImMap<ImList<IDisposable>>.Empty; // todo: @perf @mem combine used and _factories together
_used = ImHashMap<Type, object>.Empty;
_maps = _emptySlots;
_maps = _emptyMaps;
}

private static void SafelyDisposeOrderedDisposables(ImMap<ImList<IDisposable>> disposables)
Expand Down
3 changes: 2 additions & 1 deletion src/DryIoc/ImTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6212,6 +6212,7 @@ void IDisposable.Dispose() { }
/// So you may pass the empty `parents` into the first `Enumerate` and then keep passing the same `parents` into the subsequent `Enumerate` calls</summary>
public static ImMapEnumerable<V> Enumerate<V>(this ImMap<V> map) => new ImMapEnumerable<V>(map);

// todo: @feature I need to have ForEachUntil with the result of `Func<ImMapEntry<V>, int, S, bool> handler` saying when to stop
/// <summary>Depth-first in-order of hash traversal as described in http://en.wikipedia.org/wiki/Tree_traversal.
/// The `parents` parameter allows to reuse the stack memory used for the traversal between multiple calls.
/// So you may pass the empty `parents` into the first `Enumerate` and then keep passing the same `parents` into the subsequent calls</summary>
Expand Down Expand Up @@ -6863,7 +6864,7 @@ public static S ForEach<K, V, S>(this ImHashMap<K, V>[] parts, S state, Action<I
/// </summary>
public static class PartitionedMap
{
/// <summary>The default number of partions</summary>
/// <summary>The default number of partitions</summary>
public const int PARTITION_COUNT_POWER_OF_TWO = 16;

/// <summary>The default mask to partition the key</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ public class GHIssue536_DryIoc_Exception_in_a_Constructor_of_a_Dependency_does_t
{
public int Run()
{
// Test_root_singleton_should_rethrow_the_exception();
Test_root_singleton_should_rethrow_the_exception();
Test_dep_singleton_should_rethrow_the_exception();
return 2;
Test_dep_scoped_should_rethrow_the_exception();
return 3;
}

[Test]
Expand Down Expand Up @@ -42,6 +43,23 @@ public void Test_dep_singleton_should_rethrow_the_exception()
container.Resolve<B>());
}

[Test]
public void Test_dep_scoped_should_rethrow_the_exception()
{
var container = new Container();

container.Register<B>();
container.Register<IInterfaceA, ClassA>(Reuse.Scoped);

using var scope = container.OpenScope();

Assert.Throws<ArgumentException>(() =>
scope.Resolve<B>());

Assert.Throws<ArgumentException>(() =>
scope.Resolve<B>());
}

public class B
{
public readonly IInterfaceA IntA;
Expand Down
6 changes: 3 additions & 3 deletions test/DryIoc.TestRunner/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ public class Program
{
public static void Main()
{
// RunAllTests();

new GHIssue536_DryIoc_Exception_in_a_Constructor_of_a_Dependency_does_tunnel_through_Resolve_call().Run();
RunAllTests();

// new GHIssue536_DryIoc_Exception_in_a_Constructor_of_a_Dependency_does_tunnel_through_Resolve_call().Run();
// new GHIssue535_Property_injection_does_not_work_when_appending_implementation_for_multiple_registration().Run();
// new GHIssue532_WithUseInterpretation_still_use_DynamicMethod_and_ILEmit().Run();
// new GHIssue506_WithConcreteTypeDynamicRegistrations_hides_failed_dependency_resolution().Run();
Expand Down Expand Up @@ -65,6 +64,7 @@ void Run(Func<int> run, string name = null)
new GHIssue508_Throws_when_lazy_resolve_after_explicit_create_using_factory_func_from_within_scope(),
new GHIssue471_Regression_v5_using_Rules_SelectKeyedOverDefaultFactory(),
new GHIssue532_WithUseInterpretation_still_use_DynamicMethod_and_ILEmit(),
new GHIssue536_DryIoc_Exception_in_a_Constructor_of_a_Dependency_does_tunnel_through_Resolve_call()
};

// Parallel.ForEach(tests, x => Run(x.Run)); // todo: @perf enable and test when more tests are added
Expand Down

0 comments on commit 4d5130f

Please sign in to comment.