Fixed concurrency issues in the TypeCache class. (#758)

Co-authored-by: Vladimir Khvostov <vlkhvost@microsoft.com>
This commit is contained in:
Vladimir Khvostov 2021-01-21 04:55:35 -05:00 коммит произвёл GitHub
Родитель 4a0a2a14f4
Коммит 126ea1f169
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
1 изменённых файлов: 59 добавлений и 106 удалений

Просмотреть файл

@ -4,6 +4,7 @@
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
@ -41,15 +42,12 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
/// <summary>
/// Assembly info cache
/// </summary>
private readonly Dictionary<Assembly, TestAssemblyInfo> testAssemblyInfoCache;
private readonly ConcurrentDictionary<Assembly, TestAssemblyInfo> testAssemblyInfoCache = new ConcurrentDictionary<Assembly, TestAssemblyInfo>();
/// <summary>
/// ClassInfo cache
/// </summary>
private readonly Dictionary<string, TestClassInfo> classInfoCache;
private readonly object assemblyInfoSyncObject;
private readonly object classInfoSyncObject;
private readonly ConcurrentDictionary<string, TestClassInfo> classInfoCache = new ConcurrentDictionary<string, TestClassInfo>(StringComparer.Ordinal);
/// <summary>
/// Initializes a new instance of the <see cref="TypeCache"/> class.
@ -66,55 +64,29 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
internal TypeCache(ReflectHelper reflectionHelper)
{
this.reflectionHelper = reflectionHelper;
this.testAssemblyInfoCache = new Dictionary<Assembly, TestAssemblyInfo>();
this.classInfoCache = new Dictionary<string, TestClassInfo>(StringComparer.Ordinal);
this.assemblyInfoSyncObject = new object();
this.classInfoSyncObject = new object();
}
/// <summary>
/// Gets Class Info cache which has cleanup methods to execute
/// </summary>
public IEnumerable<TestClassInfo> ClassInfoListWithExecutableCleanupMethods
{
get
{
return this.classInfoCache.Values.Where(classInfo => classInfo.HasExecutableCleanupMethod).ToList();
}
}
public IEnumerable<TestClassInfo> ClassInfoListWithExecutableCleanupMethods =>
this.classInfoCache.Values.Where(classInfo => classInfo.HasExecutableCleanupMethod).ToList();
/// <summary>
/// Gets Assembly Info cache which has cleanup methods to execute
/// </summary>
public IEnumerable<TestAssemblyInfo> AssemblyInfoListWithExecutableCleanupMethods
{
get
{
return this.testAssemblyInfoCache.Values.Where(assemblyInfo => assemblyInfo.HasExecutableCleanupMethod).ToList();
}
}
public IEnumerable<TestAssemblyInfo> AssemblyInfoListWithExecutableCleanupMethods =>
this.testAssemblyInfoCache.Values.Where(assemblyInfo => assemblyInfo.HasExecutableCleanupMethod).ToList();
/// <summary>
/// Gets the set of cached assembly info values.
/// </summary>
public IEnumerable<TestAssemblyInfo> AssemblyInfoCache
{
get
{
return this.testAssemblyInfoCache.Values.ToList();
}
}
public IEnumerable<TestAssemblyInfo> AssemblyInfoCache => this.testAssemblyInfoCache.Values.ToList();
/// <summary>
/// Gets the set of cached class info values.
/// </summary>
public IEnumerable<TestClassInfo> ClassInfoCache
{
get
{
return this.classInfoCache.Values.ToList();
}
}
public IEnumerable<TestClassInfo> ClassInfoCache => this.classInfoCache.Values.ToList();
/// <summary>
/// Get the test method info corresponding to the parameter test Element
@ -176,30 +148,21 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
if (!this.classInfoCache.TryGetValue(typeName, out TestClassInfo classInfo))
{
// Acquiring a lock is usually a costly operation which does not need to be
// performed every time if the type is found in the cache.
lock (this.classInfoSyncObject)
// Load the class type
Type type = this.LoadType(typeName, testMethod.AssemblyName);
if (type == null)
{
// Perform a check again.
if (!this.classInfoCache.TryGetValue(typeName, out classInfo))
{
// Load the class type
Type type = this.LoadType(typeName, testMethod.AssemblyName);
if (type == null)
{
// This means the class containing the test method could not be found.
// Return null so we return a not found result.
return null;
}
// Get the classInfo
classInfo = this.CreateClassInfo(type, testMethod);
// Use the full type name for the cache.
this.classInfoCache.Add(typeName, classInfo);
}
// This means the class containing the test method could not be found.
// Return null so we return a not found result.
return null;
}
// Get the classInfo
classInfo = this.CreateClassInfo(type, testMethod);
// Use the full type name for the cache.
classInfo = this.classInfoCache.GetOrAdd(typeName, classInfo);
}
return classInfo;
@ -366,65 +329,55 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
if (!this.testAssemblyInfoCache.TryGetValue(assembly, out TestAssemblyInfo assemblyInfo))
{
// Aquiring a lock is usually a costly operation which does not need to be
// performed every time if the assembly is found in the cache.
lock (this.assemblyInfoSyncObject)
var assemblyInitializeType = typeof(AssemblyInitializeAttribute);
var assemblyCleanupType = typeof(AssemblyCleanupAttribute);
assemblyInfo = new TestAssemblyInfo();
var types = new AssemblyEnumerator().GetTypes(assembly, assembly.FullName, null);
foreach (var t in types)
{
if (this.testAssemblyInfoCache.TryGetValue(assembly, out assemblyInfo))
if (t == null)
{
return assemblyInfo;
continue;
}
var assemblyInitializeType = typeof(AssemblyInitializeAttribute);
var assemblyCleanupType = typeof(AssemblyCleanupAttribute);
assemblyInfo = new TestAssemblyInfo();
var types = new AssemblyEnumerator().GetTypes(assembly, assembly.FullName, null);
foreach (var t in types)
try
{
if (t == null)
// Only examine classes which are TestClass or derives from TestClass attribute
if (!this.reflectionHelper.IsAttributeDefined(t, typeof(TestClassAttribute), inherit: true) &&
!this.reflectionHelper.HasAttributeDerivedFrom(t, typeof(TestClassAttribute), true))
{
continue;
}
}
catch (Exception ex)
{
// If we fail to discover type from an assembly, then do not abort. Pick the next type.
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning(
"TypeCache: Exception occurred while checking whether type {0} is a test class or not. {1}",
t.FullName,
ex);
try
{
// Only examine classes which are TestClass or derives from TestClass attribute
if (!this.reflectionHelper.IsAttributeDefined(t, typeof(TestClassAttribute), inherit: true) &&
!this.reflectionHelper.HasAttributeDerivedFrom(t, typeof(TestClassAttribute), true))
{
continue;
}
}
catch (Exception ex)
{
// If we fail to discover type from an assembly, then do not abort. Pick the next type.
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning(
"TypeCache: Exception occurred while checking whether type {0} is a test class or not. {1}",
t.FullName,
ex);
continue;
}
// Enumerate through all methods and identify the Assembly Init and cleanup methods.
foreach (var methodInfo in t.GetTypeInfo().DeclaredMethods)
{
if (this.IsAssemblyOrClassInitializeMethod(methodInfo, assemblyInitializeType))
{
assemblyInfo.AssemblyInitializeMethod = methodInfo;
}
else if (this.IsAssemblyOrClassCleanupMethod(methodInfo, assemblyCleanupType))
{
assemblyInfo.AssemblyCleanupMethod = methodInfo;
}
}
continue;
}
this.testAssemblyInfoCache.Add(assembly, assemblyInfo);
// Enumerate through all methods and identify the Assembly Init and cleanup methods.
foreach (var methodInfo in t.GetTypeInfo().DeclaredMethods)
{
if (this.IsAssemblyOrClassInitializeMethod(methodInfo, assemblyInitializeType))
{
assemblyInfo.AssemblyInitializeMethod = methodInfo;
}
else if (this.IsAssemblyOrClassCleanupMethod(methodInfo, assemblyCleanupType))
{
assemblyInfo.AssemblyCleanupMethod = methodInfo;
}
}
}
assemblyInfo = this.testAssemblyInfoCache.GetOrAdd(assembly, assemblyInfo);
}
return assemblyInfo;