tags:

views:

275

answers:

2

Hi,

For one of my DAL modules I have lots of duplicated plumbing in the shape of:

while (retry)
{
...
try
{
   ...do something
   retry = false;
}
catch (SqlException sqlEx)
{
   // Retry only if -2 = Connection Time Out or 1205 = Deadlock
  if (sqlEx.Number == -2 || sqlEx.Number == 1205)
  {
      ..retry if attempt < max
  }
      ..log and rethrow exception
}
}

and having discovered PostSharp recently I'm attempting to replace these plumbing code with an attribute.

My original plan was to: - extend OnMethodInvocationAspect and remember the method invocation event args during method invocation - implement IOnExceptionAspect and implement OnException to check exception type and if retry is required use the method invocation event args object from the original call, i.e.:

[Serializable]
public sealed class RetryAttribute : OnMethodInvocationAspect, IOnExceptionAspect
{
    [NonSerialized]
    private MethodInvocationEventArgs m_initialInvocationEventArgs = null;

    public override void OnInvocation(MethodInvocationEventArgs eventArgs)
    {
        if (m_initialInvocationEventArgs == null)
            m_initialInvocationEventArgs = eventArgs;

        base.OnInvocation(eventArgs);
    }

    public void OnException(MethodExecutionEventArgs eventArgs)
    {
        // check if retry is necessary
        m_initialInvocationEventArgs.Proceed();
    }
}

but the OnInvocation method is not fired anymore once I've added IOnExceptionAspect..

Does anyone know what I need to do here? Or perhaps there's more suitable aspect I should use?

Thanks,

+2  A: 

You cannot have an aspect that implement two aspect interfaces (IOnMethodInvocation and IOnExceptionAspect in your case). The weaver will take one arbitrary interface and implement the aspect.

I think all you need to achieve your objective is OnMethodInvocationAspect. Why don't you put the for loop and the try-catch in the OnInvocation handler?

-gael

Gael Fraiteur
Thanks Gael, good idea, tried it and works a treat :)
theburningmonk
A: 

Here's a rather simple solution that doesn't involve PostSharp. Create the following utility method.

public static void Try(Func<bool> task, int retryCount)
{
    int attemptNum = 1;
    while (attemptNum++ <= retryCount && task()) ;
}

Then create the task you'd like to be retried. The return value should indicate if a retry should be attempted.

public bool UnreliableTask()
{
    try
    {
        // Do something
    }
    catch (SqlException ex)
    {
        return (ex.Number == -2 || ex.Number == 1205);
    }

    return false;
}

Then just call the task like so to retry it five times:

Try(UnreliableTask, 5);
Allon Guralnek
The problem with solution is that you have to change the code of any unreliable task. There can be many. If you use PostSharp, you can use widlcards or Linq over Reflection to select the methods to which it applies.That said, if there would be only a pair of unreliable methods, I would prefer your approach -- functional programming.
Gael Fraiteur
Good point. Then the Try method could be slightly modified so that instead of requiring tasks to report success/failure via a boolean return value, it would just interpret any unhandled exceptions as a failure, otherwise a success. This would remove the need to modify the unreliable tasks. But of course you would still need to modify any calls to that method to use the Try method.
Allon Guralnek