in src/profiler/Elastic.Apm.Profiler.Managed/CallTarget/Handlers/IntegrationMapper.cs [515:661]
internal static CreateAsyncEndMethodResult CreateAsyncEndMethodDelegate(Type integrationType, Type targetType, Type returnType)
{
/*
* OnAsyncMethodEnd signatures with 3 or 4 parameters with 1 or 2 generics:
* - TReturn OnAsyncMethodEnd<TTarget, TReturn>(TTarget instance, TReturn returnValue, Exception exception, CallTargetState state);
* - TReturn OnAsyncMethodEnd<TTarget, TReturn>(TReturn returnValue, Exception exception, CallTargetState state);
* - [Type] OnAsyncMethodEnd<TTarget>([Type] returnValue, Exception exception, CallTargetState state);
*
* In case the continuation is for a Task/ValueTask, the returnValue type will be an object and the value null.
* In case the continuation is for a Task<T>/ValueTask<T>, the returnValue type will be T with the instance value after the task completes.
*
*/
Logger.Debug(
$"Creating AsyncEndMethod Dynamic Method for '{integrationType.FullName}' integration. [Target={targetType.FullName}, ReturnType={returnType.FullName}]");
var onAsyncMethodEndMethodInfo =
integrationType.GetMethod(EndAsyncMethodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
if (onAsyncMethodEndMethodInfo is null)
{
Logger.Debug($"'{EndAsyncMethodName}' method was not found in integration type: '{integrationType.FullName}'.");
return default;
}
if (!onAsyncMethodEndMethodInfo.ReturnType.IsGenericParameter && onAsyncMethodEndMethodInfo.ReturnType != returnType)
{
throw new ArgumentException(
$"The return type of the method: {EndAsyncMethodName} in type: {integrationType.FullName} is not {returnType}");
}
var genericArgumentsTypes = onAsyncMethodEndMethodInfo.GetGenericArguments();
if (genericArgumentsTypes.Length < 1 || genericArgumentsTypes.Length > 2)
{
throw new ArgumentException(
$"The method: {EndAsyncMethodName} in type: {integrationType.FullName} must have the generic type for the instance type.");
}
var onAsyncMethodEndParameters = onAsyncMethodEndMethodInfo.GetParameters();
if (onAsyncMethodEndParameters.Length < 3)
{
throw new ArgumentException(
$"The method: {EndAsyncMethodName} with {onAsyncMethodEndParameters.Length} parameters in type: {integrationType.FullName} has less parameters than required.");
}
else if (onAsyncMethodEndParameters.Length > 4)
{
throw new ArgumentException(
$"The method: {EndAsyncMethodName} with {onAsyncMethodEndParameters.Length} parameters in type: {integrationType.FullName} has more parameters than required.");
}
if (onAsyncMethodEndParameters[onAsyncMethodEndParameters.Length - 2].ParameterType != typeof(Exception))
{
throw new ArgumentException(
$"The Exception type parameter of the method: {EndAsyncMethodName} in type: {integrationType.FullName} is missing.");
}
if (onAsyncMethodEndParameters[onAsyncMethodEndParameters.Length - 1].ParameterType != typeof(CallTargetState))
{
throw new ArgumentException(
$"The CallTargetState type parameter of the method: {EndAsyncMethodName} in type: {integrationType.FullName} is missing.");
}
var preserveContext = onAsyncMethodEndMethodInfo.GetCustomAttribute<PreserveContextAttribute>() != null;
var callGenericTypes = new List<Type>();
var mustLoadInstance = onAsyncMethodEndParameters.Length == 4;
var instanceGenericType = genericArgumentsTypes[0];
var instanceGenericConstraint = instanceGenericType.GetGenericParameterConstraints().FirstOrDefault();
Type instanceProxyType = null;
if (instanceGenericConstraint != null)
{
var result = DuckType.GetOrCreateProxyType(instanceGenericConstraint, targetType);
instanceProxyType = result.ProxyType;
callGenericTypes.Add(instanceProxyType);
}
else
callGenericTypes.Add(targetType);
var returnParameterIndex = onAsyncMethodEndParameters.Length == 4 ? 1 : 0;
var isAGenericReturnValue = onAsyncMethodEndParameters[returnParameterIndex].ParameterType.IsGenericParameter;
Type returnValueGenericType = null;
Type returnValueGenericConstraint = null;
Type returnValueProxyType = null;
if (isAGenericReturnValue)
{
returnValueGenericType = genericArgumentsTypes[1];
returnValueGenericConstraint = returnValueGenericType.GetGenericParameterConstraints().FirstOrDefault();
if (returnValueGenericConstraint != null)
{
var result = DuckType.GetOrCreateProxyType(returnValueGenericConstraint, returnType);
returnValueProxyType = result.ProxyType;
callGenericTypes.Add(returnValueProxyType);
}
else
callGenericTypes.Add(returnType);
}
else if (onAsyncMethodEndParameters[returnParameterIndex].ParameterType != returnType)
{
throw new ArgumentException(
$"The ReturnValue type parameter of the method: {EndAsyncMethodName} in type: {integrationType.FullName} is invalid. [{onAsyncMethodEndParameters[returnParameterIndex].ParameterType} != {returnType}]");
}
var callMethod = new DynamicMethod(
$"{onAsyncMethodEndMethodInfo.DeclaringType.Name}.{onAsyncMethodEndMethodInfo.Name}.{targetType.Name}.{returnType.Name}",
returnType,
new Type[] { targetType, returnType, typeof(Exception), typeof(CallTargetState) },
onAsyncMethodEndMethodInfo.Module,
true);
var ilWriter = callMethod.GetILGenerator();
// Load the instance if is needed
if (mustLoadInstance)
{
ilWriter.Emit(OpCodes.Ldarg_0);
if (instanceGenericConstraint != null)
WriteCreateNewProxyInstance(ilWriter, instanceProxyType, targetType);
}
// Load the return value
ilWriter.Emit(OpCodes.Ldarg_1);
if (returnValueProxyType != null)
WriteCreateNewProxyInstance(ilWriter, returnValueProxyType, returnType);
// Load the exception
ilWriter.Emit(OpCodes.Ldarg_2);
// Load the state
ilWriter.Emit(OpCodes.Ldarg_3);
// Call Method
onAsyncMethodEndMethodInfo = onAsyncMethodEndMethodInfo.MakeGenericMethod(callGenericTypes.ToArray());
ilWriter.EmitCall(OpCodes.Call, onAsyncMethodEndMethodInfo, null);
// Unwrap return value proxy
if (returnValueProxyType != null)
{
var unwrapReturnValue = UnwrapReturnValueMethodInfo.MakeGenericMethod(returnValueProxyType, returnType);
ilWriter.EmitCall(OpCodes.Call, unwrapReturnValue, null);
}
ilWriter.Emit(OpCodes.Ret);
Logger.Debug(
$"Created AsyncEndMethod Dynamic Method for '{integrationType.FullName}' integration. [Target={targetType.FullName}, ReturnType={returnType.FullName}]");
return new CreateAsyncEndMethodResult(callMethod, preserveContext);
}