Skip to content

Commit 72ff130

Browse files
committed
Fixes and improvements of caching registration
- added alphabetical index for namespaces to not search position from start all the time - fixed issue of namespaces with one type - added extra measure to not allow forever-loops
1 parent edfc564 commit 72ff130

1 file changed

Lines changed: 39 additions & 9 deletions

File tree

Orm/Xtensive.Orm.Tests.Framework/AssemblyExtensions.cs

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ namespace Xtensive.Orm.Tests
1717
public static class AssemblyExtensions
1818
{
1919
private static readonly Type ObjectType = typeof(object);
20+
private static readonly string MainTestAsseblyNsPrefix = "Xtensive.Orm.Tests."; // keep the dot at the end
21+
private static readonly byte[] ThisAssemblyPkt = typeof(AssemblyExtensions).Assembly.GetName().GetPublicKeyToken();
22+
2023
private static readonly ConcurrentDictionary<Assembly, Type[]> TypesPerAssembly = new();
24+
private static readonly ConcurrentDictionary<char, int> XtensiveOrmTestsNsAlphabeticIndex = new();
2125

2226
public static System.Configuration.Configuration GetAssemblyConfiguration(this Assembly assembly)
2327
{
@@ -26,41 +30,64 @@ public static System.Configuration.Configuration GetAssemblyConfiguration(this A
2630

2731
public static IReadOnlyList<Type> GetTypesFromNamespaceCaching(this Assembly assembly, string @namespace)
2832
{
33+
if (string.IsNullOrWhiteSpace(@namespace))
34+
throw new ArgumentException("Namespace cannot be null, empty or contains only white spaces");
35+
2936
// these two dummy mentions to not forget to sync filtration algorithm here and in the classes,
3037
// in particular BaseType property, if the property changed then this algorighm should be changed as well
3138
_ = nameof(Xtensive.IoC.ServiceTypeRegistrationProcessor.BaseType);
3239
_ = nameof(Xtensive.Orm.Configuration.DomainTypeRegistrationHandler.BaseType);
3340

34-
var assemblyTypes = TypesPerAssembly.GetOrAdd(assembly, static (a) => {
41+
var assemblyNameInfo = assembly.GetName();
42+
var isMainTestAssembly = assemblyNameInfo.Name == "Xtensive.Orm.Tests" && !ThisAssemblyPkt.Except(assemblyNameInfo.GetPublicKeyToken()).Any();
43+
44+
var assemblyTypes = TypesPerAssembly.GetOrAdd(assembly, static (a, isMain) => {
3545
var allTypes = a.GetTypes();
3646
var list = new List<Type>(allTypes.Length);
47+
var currentIndex = 0;
3748
foreach (var t in allTypes) {
3849
// we ignore compiler generated types because usuallty they are
3950
// at the end of sorted types
40-
if (t.IsSubclassOf(ObjectType) && t.GetCustomAttribute<CompilerGeneratedAttribute>()==null)
51+
if (t.IsSubclassOf(ObjectType) && t.GetCustomAttribute<CompilerGeneratedAttribute>() == null) {
4152
list.Add(t);
53+
if (isMain) {
54+
if (t.Namespace != null && t.Namespace.StartsWith(MainTestAsseblyNsPrefix, StringComparison.Ordinal)) {
55+
var firstLetter = t.Namespace[MainTestAsseblyNsPrefix.Length];
56+
// main test library has 5000+ types, to not enumerate them every type from the beginning
57+
// we try to have parts by first letter
58+
_ = XtensiveOrmTestsNsAlphabeticIndex.TryAdd(firstLetter, currentIndex);
59+
}
60+
}
61+
currentIndex++;
62+
}
4263
}
4364
return list.ToArray();
44-
});
65+
}, isMainTestAssembly);
4566

46-
var range = FindRange(assemblyTypes, @namespace);
67+
var range = FindRange(assemblyTypes, @namespace, isMainTestAssembly);
4768
return new ArraySegment<Type>(assemblyTypes, range.first, range.last - range.first + 1);
4869

4970

5071
//type.IsSubclassOf(BaseType) && (ns.IsNullOrEmpty() || (type.FullName.IndexOf(ns + ".", StringComparison.InvariantCulture) >= 0));
5172
}
5273

53-
private static (int first, int last) FindRange(Type[] types, string ns)
74+
private static (int first, int last) FindRange(Type[] types, string ns, bool isMainAssembly)
5475
{
5576
const int windowSize = 10;
5677

78+
var searchStart = (isMainAssembly)
79+
? (ns.StartsWith(MainTestAsseblyNsPrefix))
80+
? XtensiveOrmTestsNsAlphabeticIndex[ns[MainTestAsseblyNsPrefix.Length]]
81+
: 0 //types from root namespace
82+
: 0;
83+
5784
// we rely on the fact that types are sorted by full name, that means types of same namespace are go one by one
5885
// which gives us to optimize search - we find first type that has desired namespace, then we try to find last one
5986
// and then we return the part of original array as result
6087
var firstHit = -1;
6188
var lastHit = -1;
6289

63-
for (int headIndex = 0, count = types.Length ; headIndex < count; headIndex++) {
90+
for (int headIndex = searchStart, count = types.Length ; headIndex < count; headIndex++) {
6491
var head = types[headIndex];
6592
if (head.FullName.IndexOf(ns + ".", StringComparison.InvariantCulture) >= 0) {
6693
firstHit = headIndex;
@@ -69,21 +96,24 @@ private static (int first, int last) FindRange(Type[] types, string ns)
6996
}
7097

7198
var isOutOfRange = false;
99+
lastHit = firstHit;
72100
do {
73-
lastHit = firstHit + windowSize;
101+
lastHit = lastHit + windowSize;
74102
if (lastHit > types.Length - 1) {
75103
lastHit = types.Length - 1;
76104
}
77105
var tail = types[lastHit];
78106
if (tail.FullName.IndexOf(ns + ".", StringComparison.InvariantCulture) < 0)
79107
isOutOfRange = true;
108+
if (lastHit < firstHit)
109+
throw new Exception("There is something strage in the neighborhood! :-)");
80110
}
81111
while (!isOutOfRange);
82112

83-
for (int tailIndex = lastHit; tailIndex > firstHit; tailIndex--) {
113+
for (int tailIndex = lastHit; tailIndex >= firstHit; tailIndex--) {
84114
var tail = types[tailIndex];
115+
lastHit = tailIndex;
85116
if (tail.FullName.IndexOf(ns + ".", StringComparison.InvariantCulture) >= 0) {
86-
lastHit = tailIndex;
87117
break;
88118
}
89119
}

0 commit comments

Comments
 (0)