Module lookup overload (#485)

* ModuleLookup, overload on MethodBuilder
This commit is contained in:
Colin Higgins 2019-08-22 15:23:31 -04:00 committed by GitHub
parent 55a2f25057
commit 205d469c5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 204 additions and 29 deletions

View File

@ -18,29 +18,34 @@ namespace Datadog.Trace.ClrProfiler.Emit
private static readonly ConcurrentDictionary<Key, TDelegate> Cache = new ConcurrentDictionary<Key, TDelegate>(new KeyComparer());
private static readonly ILog Log = LogProvider.GetLogger(typeof(MethodBuilder<TDelegate>));
private readonly Assembly _resolutionAssembly;
private readonly Module _resolutionModule;
private readonly int _mdToken;
private readonly int _originalOpCodeValue;
private readonly OpCodeValue _opCode;
// Legacy fallback mechanisms
private readonly string _methodName;
private Type _returnType;
private readonly Guid? _moduleVersionId;
private Type _returnType;
private MethodBase _methodBase;
private Type _concreteType;
private string _concreteTypeName;
private object[] _parameters = new object[0];
private Type[] _explicitParameterTypes = null;
private string[] _namespaceAndNameFilter = null;
private Type[] _declaringTypeGenerics;
private Type[] _methodGenerics;
private bool _forceMethodDefResolve;
private MethodBuilder(Assembly resolutionAssembly, int mdToken, int opCode, string methodName)
private MethodBuilder(Guid moduleVersionId, int mdToken, int opCode, string methodName)
: this(ModuleLookup.Get(moduleVersionId), mdToken, opCode, methodName)
{
_resolutionAssembly = resolutionAssembly;
// Save the Guid for logging purposes
_moduleVersionId = moduleVersionId;
}
private MethodBuilder(Module resolutionModule, int mdToken, int opCode, string methodName)
{
_resolutionModule = resolutionModule;
_mdToken = mdToken;
_opCode = (OpCodeValue)opCode;
_originalOpCodeValue = opCode;
@ -50,19 +55,25 @@ namespace Datadog.Trace.ClrProfiler.Emit
public static MethodBuilder<TDelegate> Start(Assembly resolutionAssembly, int mdToken, int opCode, string methodName)
{
return new MethodBuilder<TDelegate>(resolutionAssembly, mdToken, opCode, methodName);
// This method is deprecated in favor of the newer ModuleVersionId lookup
return new MethodBuilder<TDelegate>(resolutionAssembly.ManifestModule, mdToken, opCode, methodName);
}
public static MethodBuilder<TDelegate> Start(Guid moduleVersionId, int mdToken, int opCode, string methodName)
{
return new MethodBuilder<TDelegate>(moduleVersionId, mdToken, opCode, methodName);
}
public MethodBuilder<TDelegate> WithConcreteType(Type type)
{
_concreteType = type;
_concreteTypeName = type.FullName;
_concreteTypeName = type?.FullName;
return this;
}
public MethodBuilder<TDelegate> WithConcreteTypeName(string typeName)
{
var concreteType = _resolutionAssembly.GetType(typeName);
var concreteType = _resolutionModule?.GetType(typeName);
return this.WithConcreteType(concreteType);
}
@ -123,7 +134,7 @@ namespace Datadog.Trace.ClrProfiler.Emit
}
var cacheKey = new Key(
callingModule: _resolutionAssembly.ManifestModule,
callingModule: _resolutionModule,
mdToken: _mdToken,
callOpCode: _opCode,
concreteType: _concreteType,
@ -144,29 +155,36 @@ namespace Datadog.Trace.ClrProfiler.Emit
{
var requiresBestEffortMatching = false;
try
if (_resolutionModule != null)
{
// Don't resolve until we build, as it may be an unnecessary lookup because of the cache
// We also may need the generics which were specified
if (_forceMethodDefResolve || (_declaringTypeGenerics == null && _methodGenerics == null))
try
{
_methodBase =
_resolutionAssembly.ManifestModule.ResolveMethod(metadataToken: _mdToken);
// Don't resolve until we build, as it may be an unnecessary lookup because of the cache
// We also may need the generics which were specified
if (_forceMethodDefResolve || (_declaringTypeGenerics == null && _methodGenerics == null))
{
_methodBase =
_resolutionModule.ResolveMethod(metadataToken: _mdToken);
}
else
{
_methodBase =
_resolutionModule.ResolveMethod(
metadataToken: _mdToken,
genericTypeArguments: _declaringTypeGenerics,
genericMethodArguments: _methodGenerics);
}
}
else
catch (Exception ex)
{
_methodBase =
_resolutionAssembly.ManifestModule.ResolveMethod(
metadataToken: _mdToken,
genericTypeArguments: _declaringTypeGenerics,
genericMethodArguments: _methodGenerics);
string message = $"Unable to resolve method {_concreteTypeName}.{_methodName} by metadata token: {_mdToken}";
Log.Error(message, ex);
requiresBestEffortMatching = true;
}
}
catch (Exception ex)
else
{
string message = $"Unable to resolve method {_concreteTypeName}.{_methodName} by metadata token: {_mdToken}";
Log.Error(message, ex);
requiresBestEffortMatching = true;
Log.Warn($"Unable to resolve module version id {_moduleVersionId}. Using method builder fallback.");
}
MethodInfo methodInfo = null;
@ -296,7 +314,7 @@ namespace Datadog.Trace.ClrProfiler.Emit
private MethodInfo VerifyMethodFromToken(MethodInfo methodInfo)
{
// Verify baselines to ensure this isn't the wrong method somehow
var detailMessage = $"Unexpected method: {_concreteTypeName}.{_methodName} received for mdToken: {_mdToken} in assembly: {_resolutionAssembly.FullName}";
var detailMessage = $"Unexpected method: {_concreteTypeName}.{_methodName} received for mdToken: {_mdToken} in module: {_resolutionModule?.FullyQualifiedName ?? "NULL"}, {_resolutionModule?.ModuleVersionId ?? _moduleVersionId}";
if (!string.Equals(_methodName, methodInfo.Name))
{
@ -364,7 +382,7 @@ namespace Datadog.Trace.ClrProfiler.Emit
private MethodInfo TryFindMethod()
{
var logDetail = $"mdToken {_mdToken} on {_concreteTypeName}.{_methodName} in {_resolutionAssembly.FullName}";
var logDetail = $"mdToken {_mdToken} on {_concreteTypeName}.{_methodName} in {_resolutionModule?.FullyQualifiedName ?? "NULL"}, {_resolutionModule?.ModuleVersionId ?? _moduleVersionId}";
Log.Warn($"Using fallback method matching ({logDetail})");
var methods =

View File

@ -0,0 +1,87 @@
using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Threading;
using Datadog.Trace.Logging;
namespace Datadog.Trace.ClrProfiler.Emit
{
internal static class ModuleLookup
{
/// <summary>
/// Some naive upper limit to resolving assemblies that we can use to stop making expensive calls.
/// </summary>
private const int MaxFailures = 50;
private static readonly ILog Log = LogProvider.GetLogger(typeof(ModuleLookup));
private static ManualResetEventSlim _populationResetEvent = new ManualResetEventSlim(initialState: true);
private static ConcurrentDictionary<Guid, Module> _modules = new ConcurrentDictionary<Guid, Module>();
private static int _failures = 0;
private static bool _shortCircuitLogicHasLogged = false;
public static Module Get(Guid moduleVersionId)
{
// First attempt at cached values with no blocking
if (_modules.TryGetValue(moduleVersionId, out Module value))
{
return value;
}
// Block if a population event is happening
_populationResetEvent.Wait();
// See if the previous population event populated what we need
if (_modules.TryGetValue(moduleVersionId, out value))
{
return value;
}
if (_failures >= MaxFailures)
{
// For some unforeseeable reason we have failed on a lot of AppDomain lookups
if (!_shortCircuitLogicHasLogged)
{
Log.Warn("Datadog is unable to continue attempting module lookups for this AppDomain. Falling back to legacy method lookups.");
}
return null;
}
// Block threads on this event
_populationResetEvent.Reset();
try
{
PopulateModules();
}
catch (Exception ex)
{
_failures++;
Log.Error("Error when populating modules.", ex);
}
finally
{
// Continue threads blocked on this event
_populationResetEvent.Set();
}
_modules.TryGetValue(moduleVersionId, out value);
return value;
}
private static void PopulateModules()
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
foreach (var module in assembly.Modules)
{
_modules.TryAdd(module.ModuleVersionId, module);
}
}
}
}
}

View File

@ -0,0 +1,70 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Datadog.Trace.ClrProfiler.Emit;
using Xunit;
namespace Datadog.Trace.ClrProfiler.Managed.Tests
{
public class ModuleLookupTests
{
[Fact]
public void Lookup_SystemData_Succeeds_WithTwentyConcurrentTries()
{
var tasks = new Task[20];
var resetEvent = new ManualResetEventSlim(initialState: false);
var bag = new ConcurrentBag<Module>();
var systemDataGuid = typeof(System.Data.DataTable).Assembly.ManifestModule.ModuleVersionId;
for (var i = 0; i < 20; i++)
{
tasks[i] = Task.Run(() =>
{
resetEvent.Wait();
bag.Add(ModuleLookup.Get(systemDataGuid));
});
}
resetEvent.Set();
Task.WaitAll(tasks);
Assert.True(bag.All(m => m.ModuleVersionId == systemDataGuid) && bag.Count() == tasks.Length);
}
[Fact]
public void Lookup_Self_Succeeds()
{
var expectedModule = typeof(ModuleLookupTests).Assembly.ManifestModule;
var lookup = ModuleLookup.Get(expectedModule.ModuleVersionId);
Assert.Equal(expectedModule, lookup);
}
[Fact]
public void Lookup_DatadogTraceClrProfilerManaged_Succeeds()
{
var expectedModule = typeof(MethodBuilder<>).Assembly.ManifestModule;
var lookup = ModuleLookup.Get(expectedModule.ModuleVersionId);
Assert.Equal(expectedModule, lookup);
}
[Fact]
public void Lookup_DatadogTrace_Succeeds()
{
var expectedModule = typeof(Span).Assembly.ManifestModule;
var lookup = ModuleLookup.Get(expectedModule.ModuleVersionId);
Assert.Equal(expectedModule, lookup);
}
[Fact]
public void Lookup_SystemData_Succeeds()
{
var expectedModule = typeof(System.Data.DataTable).Assembly.ManifestModule;
var lookup = ModuleLookup.Get(expectedModule.ModuleVersionId);
Assert.Equal(expectedModule, lookup);
}
}
}