Skip to content

Commit 4d5130f

Browse files
committed
fixed: #536
1 parent 97e5861 commit 4d5130f

File tree

5 files changed

+73
-20
lines changed

5 files changed

+73
-20
lines changed

src/DryIoc/Container.Generated.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ partial class Container
4444
{
4545
partial void GetLastGeneratedFactoryID(ref int lastFactoryID)
4646
{
47-
lastFactoryID = 597; // generated: equals to the last used Factory.FactoryID
47+
lastFactoryID = 674; // generated: equals to the last used Factory.FactoryID
4848
}
4949

5050
partial void ResolveGenerated(ref object service, Type serviceType)
@@ -62,7 +62,7 @@ partial void ResolveGenerated(ref object service,
6262
requiredServiceType == null &&
6363
Equals(preRequestParent, Request.Empty.Push(
6464
typeof(IService),
65-
592,
65+
669,
6666
typeof(MyService),
6767
Reuse.Transient,
6868
RequestFlags.IsResolutionCall)))
@@ -92,7 +92,7 @@ internal static object Get_IService_0(IResolverContext r) =>
9292
null,
9393
Request.Empty.Push(
9494
typeof(IService),
95-
592,
95+
669,
9696
typeof(MyService),
9797
Reuse.Transient,
9898
RequestFlags.IsResolutionCall|RequestFlags.StopRecursiveDependencyCheck),
@@ -104,7 +104,7 @@ internal static object Get_IService_0(IResolverContext r) =>
104104
null,
105105
Request.Empty.Push(
106106
typeof(IService),
107-
592,
107+
669,
108108
typeof(MyService),
109109
Reuse.Transient,
110110
RequestFlags.IsResolutionCall|RequestFlags.StopRecursiveDependencyCheck),

src/DryIoc/Container.cs

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -431,13 +431,15 @@ private object ResolveAndCache(int serviceTypeHash, Type serviceType, IfUnresolv
431431
return value;
432432
}
433433

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

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

3038+
private static void TrySetScopedItemException(IResolverContext r, Exception ex)
3039+
{
3040+
ScopedItemException itemEx = null;
3041+
var s = r.CurrentScope as Scope;
3042+
while (s != null)
3043+
{
3044+
var itemMaps = s.CloneMaps();
3045+
foreach (var m in itemMaps)
3046+
{
3047+
if (!m.IsEmpty)
3048+
foreach (var it in m.Enumerate())
3049+
{
3050+
if (it.Value == Scope.NoItem)
3051+
{
3052+
itemEx ??= new ScopedItemException(ex);
3053+
if (Interlocked.CompareExchange(ref it.Value, itemEx, Scope.NoItem) == Scope.NoItem)
3054+
return; // done
3055+
}
3056+
}
3057+
}
3058+
s = s.Parent as Scope;
3059+
}
3060+
}
3061+
30353062
internal static bool TryInterpretSingletonAndUnwrapContainerException(IResolverContext r, Expression expr, ImMapEntry<object> itemRef, out object result)
30363063
{
30373064
try
@@ -3107,7 +3134,10 @@ public static bool TryInterpret(IResolverContext r, Expression expr,
31073134
}
31083135
case ExprType.Call:
31093136
{
3110-
return TryInterpretMethodCall(r, (MethodCallExpression)expr, paramExprs, paramValues, parentArgs, ref result);
3137+
var ok = TryInterpretMethodCall(r, (MethodCallExpression)expr, paramExprs, paramValues, parentArgs, ref result);
3138+
if (ok && result is ScopedItemException ie)
3139+
ie.ReThrow();
3140+
return ok;
31113141
}
31123142
case ExprType.Convert:
31133143
{
@@ -3118,6 +3148,8 @@ public static bool TryInterpret(IResolverContext r, Expression expr,
31183148
{
31193149
if (!TryInterpretMethodCall(r, m, paramExprs, paramValues, parentArgs, ref instance))
31203150
return false;
3151+
if (instance is ScopedItemException ie)
3152+
ie.ReThrow();
31213153
}
31223154
else if (!TryInterpret(r, operandExpr, paramExprs, paramValues, parentArgs, out instance))
31233155
return false;
@@ -12835,17 +12867,19 @@ public class Scope : IScope
1283512867

1283612868
internal static readonly object NoItem = new object();
1283712869

12838-
private static ImMap<object>[] _emptySlots = CreateEmptyMaps();
12870+
private static ImMap<object>[] _emptyMaps = CreateEmptyMaps();
1283912871

1284012872
private static ImMap<object>[] CreateEmptyMaps()
1284112873
{
12842-
var slots = new ImMap<object>[MAP_COUNT];
12874+
var maps = new ImMap<object>[MAP_COUNT];
1284312875
var empty = ImMap<object>.Empty;
1284412876
for (var i = 0; i < MAP_COUNT; ++i)
12845-
slots[i] = empty;
12846-
return slots;
12877+
maps[i] = empty;
12878+
return maps;
1284712879
}
1284812880

12881+
internal ImMap<object>[] CloneMaps() => _maps.CopyNonEmpty();
12882+
1284912883
///<summary>Creating</summary>
1285012884
[MethodImpl((MethodImplOptions)256)]
1285112885
public static IScope Of(IScope parent, object name) =>
@@ -13140,7 +13174,7 @@ public void Dispose()
1314013174

1314113175
_disposables = ImMap<ImList<IDisposable>>.Empty; // todo: @perf @mem combine used and _factories together
1314213176
_used = ImHashMap<Type, object>.Empty;
13143-
_maps = _emptySlots;
13177+
_maps = _emptyMaps;
1314413178
}
1314513179

1314613180
private static void SafelyDisposeOrderedDisposables(ImMap<ImList<IDisposable>> disposables)

src/DryIoc/ImTools.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6212,6 +6212,7 @@ void IDisposable.Dispose() { }
62126212
/// So you may pass the empty `parents` into the first `Enumerate` and then keep passing the same `parents` into the subsequent `Enumerate` calls</summary>
62136213
public static ImMapEnumerable<V> Enumerate<V>(this ImMap<V> map) => new ImMapEnumerable<V>(map);
62146214

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

68696870
/// <summary>The default mask to partition the key</summary>

test/DryIoc.IssuesTests/GHIssue536_DryIoc_Exception_in_a_Constructor_of_a_Dependency_does_tunnel_through_Resolve_call.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ public class GHIssue536_DryIoc_Exception_in_a_Constructor_of_a_Dependency_does_t
88
{
99
public int Run()
1010
{
11-
// Test_root_singleton_should_rethrow_the_exception();
11+
Test_root_singleton_should_rethrow_the_exception();
1212
Test_dep_singleton_should_rethrow_the_exception();
13-
return 2;
13+
Test_dep_scoped_should_rethrow_the_exception();
14+
return 3;
1415
}
1516

1617
[Test]
@@ -42,6 +43,23 @@ public void Test_dep_singleton_should_rethrow_the_exception()
4243
container.Resolve<B>());
4344
}
4445

46+
[Test]
47+
public void Test_dep_scoped_should_rethrow_the_exception()
48+
{
49+
var container = new Container();
50+
51+
container.Register<B>();
52+
container.Register<IInterfaceA, ClassA>(Reuse.Scoped);
53+
54+
using var scope = container.OpenScope();
55+
56+
Assert.Throws<ArgumentException>(() =>
57+
scope.Resolve<B>());
58+
59+
Assert.Throws<ArgumentException>(() =>
60+
scope.Resolve<B>());
61+
}
62+
4563
public class B
4664
{
4765
public readonly IInterfaceA IntA;

test/DryIoc.TestRunner/Program.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ public class Program
88
{
99
public static void Main()
1010
{
11-
// RunAllTests();
12-
13-
new GHIssue536_DryIoc_Exception_in_a_Constructor_of_a_Dependency_does_tunnel_through_Resolve_call().Run();
11+
RunAllTests();
1412

13+
// new GHIssue536_DryIoc_Exception_in_a_Constructor_of_a_Dependency_does_tunnel_through_Resolve_call().Run();
1514
// new GHIssue535_Property_injection_does_not_work_when_appending_implementation_for_multiple_registration().Run();
1615
// new GHIssue532_WithUseInterpretation_still_use_DynamicMethod_and_ILEmit().Run();
1716
// new GHIssue506_WithConcreteTypeDynamicRegistrations_hides_failed_dependency_resolution().Run();
@@ -65,6 +64,7 @@ void Run(Func<int> run, string name = null)
6564
new GHIssue508_Throws_when_lazy_resolve_after_explicit_create_using_factory_func_from_within_scope(),
6665
new GHIssue471_Regression_v5_using_Rules_SelectKeyedOverDefaultFactory(),
6766
new GHIssue532_WithUseInterpretation_still_use_DynamicMethod_and_ILEmit(),
67+
new GHIssue536_DryIoc_Exception_in_a_Constructor_of_a_Dependency_does_tunnel_through_Resolve_call()
6868
};
6969

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

0 commit comments

Comments
 (0)