我正在尝试使用 VirtualMethodInterceptor 通过 Unity Interception 将瞬态故障处理应用程序块集成到我的应用程序中。
我创建了一个调用处理程序来创建被拦截方法的操作或函数或任务,并将其传递给瞬态故障处理程序,但现在我得到了一个 stackoverflow 异常。这是有道理的,因为故障处理程序将调用统一将拦截并传递给故障处理程序的方法。
我需要的是能够在故障处理程序回调或直接调用基本方法时跳过拦截。一些统一如何能够做到这一点,因为它最终会调用我的类方法。
我的拦截代码
public class RetryHandler : ICallHandler
{
// store a cache of the delegates
private static readonly ConcurrentDictionary<MethodInfo, Func<object, object[], object>> CacheCall =
new ConcurrentDictionary<MethodInfo, Func<object, object[], object>>();
// List of methods we need to call
private static readonly Action<Action> RetryActionMethod = RetryPolicy.NoRetry.ExecuteAction;
private static readonly Func<Func<int>, int> RetryFuncMethod = RetryPolicy.NoRetry.ExecuteAction;
private static readonly Func<Func<Task<int>>, Task<int>> RetryAsyncFuncMethod = RetryPolicy.NoRetry.ExecuteAsync;
private static readonly Func<Func<Task>, Task> RetryAsyncActionMethod = RetryPolicy.NoRetry.ExecuteAsync;
private static readonly ConstructorInfo RetryPolicyConstructor;
static RetryHandler()
{
RetryPolicyConstructor =
typeof (RetryPolicy).GetConstructor(new[]
{typeof (ITransientErrorDetectionStrategy), typeof (RetryStrategy)});
}
public int Order { get; set; }
/// <summary>
/// Uses Expression Trees to wrap method call into retryhandler
/// </summary>
/// <param name="input"></param>
/// <param name="getNext"></param>
/// <returns></returns>
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
if (input.Arguments.Count == input.Inputs.Count)
{
var methodInfo = input.MethodBase as MethodInfo;
if (methodInfo != null)
{
var func = CacheCall.GetOrAdd(methodInfo, BuildLambda);
var results = func(input.Target, input.Inputs.OfType<object>().ToArray());
var ex = results as Exception;
if (ex != null)
{
return input.CreateExceptionMethodReturn(ex);
}
return input.CreateMethodReturn(results);
}
}
return getNext()(input, getNext);
}
private static Func<object, object[], object> BuildLambda(MethodInfo methodInfo)
{
var retryAttribute = methodInfo.GetCustomAttributes<RetryAttribute>(true).First();
// Convert parameters and source object to be able to call method
var target = Expression.Parameter(typeof (object), "target");
var parameters = Expression.Parameter(typeof (object[]), "parameters");
Expression source;
if (methodInfo.DeclaringType == null)
{
source = target;
}
else
{
source = Expression.Convert(target, methodInfo.DeclaringType);
}
var convertedParams =
methodInfo.GetParameters()
.Select(
(p, i) =>
Expression.Convert(Expression.ArrayIndex(parameters, Expression.Constant(i)),
p.ParameterType)).Cast<Expression>()
.ToArray();
//!!!!! **This line of code causes the stackoverflow as this will go back through interception**
var innerExpression = Expression.Call(source, methodInfo, convertedParams);
// get what type of lambda we need to build and what method we need to call on the retry handler
Type returnType;
MethodInfo retryMethod;
if (methodInfo.ReturnType == typeof (void))
{
returnType = typeof (Action);
retryMethod = RetryActionMethod.Method;
}
else if (methodInfo.ReturnType == typeof (Task))
{
returnType = typeof (Func<Task>);
retryMethod = RetryAsyncActionMethod.Method;
}
else if (methodInfo.ReturnType.IsGenericType &&
methodInfo.ReturnType.GetGenericTypeDefinition() == typeof (Task<>))
{
var genericType = methodInfo.ReturnType.GetGenericArguments()[0];
returnType =
typeof (Func<>).MakeGenericType(
typeof (Task<>).MakeGenericType(genericType));
retryMethod = RetryAsyncFuncMethod.Method.GetGenericMethodDefinition().MakeGenericMethod(genericType);
}
else
{
returnType = typeof (Func<>).MakeGenericType(methodInfo.ReturnType);
retryMethod =
RetryFuncMethod.Method.GetGenericMethodDefinition().MakeGenericMethod(methodInfo.ReturnType);
}
var innerLambda = Expression.Lambda(returnType, innerExpression);
var outerLambda = Expression.Lambda(
typeof (Func<,,>).MakeGenericType(typeof (object), typeof (object[]), returnType),
innerLambda,
target, parameters);
// create the retry handler
var retryPolicy = Expression.New(RetryPolicyConstructor,
Expression.Invoke(retryAttribute.TransientErrorDetectionStrategy),
Expression.Invoke(retryAttribute.RetryStrategy));
var passedInTarget = Expression.Parameter(typeof (object), "wrapperTarget");
var passedInParameters = Expression.Parameter(typeof (object[]), "wrapperParamters");
var retryCall = Expression.Call(retryPolicy, retryMethod,
Expression.Invoke(outerLambda, passedInTarget, passedInParameters));
Expression resultExpression;
if (methodInfo.ReturnType != typeof (void))
{
// convert to object so we can have a standard func<object, object[], object>
resultExpression = Expression.Convert(retryCall, typeof (object));
}
else
{
// if void we will set the return results as null - it's what unity wants and plus we keep our signature
var returnTarget = Expression.Label(typeof (object));
resultExpression = Expression.Block(retryCall, Expression.Label(returnTarget, Expression.Constant(null)));
}
var func =
Expression.Lambda<Func<object, object[], object>>(resultExpression, passedInTarget, passedInParameters)
.Compile();
return func;
}
}
我的属性
public abstract class RetryAttribute : HandlerAttribute
{
public RetryAttribute()
{
RetryStrategy = () =>
Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling.RetryStrategy.NoRetry;
TransientErrorDetectionStrategy = () => RetryPolicy.NoRetry.ErrorDetectionStrategy;
}
public Expression<Func<RetryStrategy>> RetryStrategy { get; protected set; }
public Expression<Func<ITransientErrorDetectionStrategy>> TransientErrorDetectionStrategy { get; protected set; }
public override ICallHandler CreateHandler(IUnityContainer container)
{
return new RetryHandler();
}
}
public class SqlDatabaseeRetryAttribute : RetryAttribute
{
public SqlDatabaseeRetryAttribute()
{
RetryStrategy =
() => Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling.RetryStrategy.DefaultExponential;
TransientErrorDetectionStrategy = () => new SqlDatabaseTransientErrorDetectionStrategy();
}
}