views:

295

answers:

8

I have a web service method I am calling which is 3rd party and outside of my domain. For some reason every now and again the web service fails with a gateway timeout. Its intermittent and a call to it directly after a failed attempt can succeed.

Now I am left with a coding dilemma, I have code that should do the trick, but the code looks like amateur hour, as you'll see below.

Is this really bad code, or acceptable given the usage? If its not acceptable, how can I improve it?

Please try hard to keep a straight face while looking at it.

try
{
    MDO = OperationsWebService.MessageDownload(MI);
}
catch
{
    try
    {
        MDO = OperationsWebService.MessageDownload(MI);
    }
    catch
    {
        try
        {
            MDO = OperationsWebService.MessageDownload(MI);
        }
        catch
        {
            try
            {
                MDO = OperationsWebService.MessageDownload(MI);
            }
            catch 
            {
                try
                {
                    MDO = OperationsWebService.MessageDownload(MI);
                }
                catch (Exception ex)
                {
                    // 5 retries, ok now log and deal with the error.
                }
            }
        }
    }
}
+9  A: 

Here's another way you might try:

// Easier to change if you decide that 5 retries isn't right for you
Exception exceptionKeeper = null;
for (int i = 0; i < MAX_RETRIES; ++i)
{
    try
    {
       MDO = OperationsWebService.MessageDownload(MI);
       break;  // correct point from Joe - thanks.
    }
    catch (Exception ex)
    {
        exceptionKeeper = ex;
        // 5 retries, ok now log and deal with the error.
    }  
}

I think it documents the intent better. It's less code as well; easier to maintain.

duffymo
That looks much neater indeed, I guess in the catch I check for the value of MAX_RETRIES?
JL
I don't think this works. It would go into the catch block on the first exception.
Plynx
probably want a 'break' at the end of the try. :)
Joe
+1 I was writing almost the exact same thing before I saw the orange banner.
J.Hendrix
No, you'll just exit the loop if MAX_RETRIES is 5.
James McLeod
@Joe - spot on. Thanks for the improvement.
duffymo
In the above code , how can I keep the original exception after MAX_RETRIES is met?
JL
@JL: at the start of the loop just declare a variable of type Exception, and set this equal to the very first exception that is caught. Then, once the retry count is hit, just re-throw it.
Seth Petry-Johnson
+8  A: 

You can do it in a loop.

Exception firstEx = null;
for(int i=0; i<5; i++) 
{
    try
    {
        MDO = OperationsWebService.MessageDownload(MI);
        firstEx = null;
        break; 
    }
    catch(Exception ex)
    {
        if (firstEx == null) 
        {
            firstEx = ex;
        }
        Thread.Sleep(100 * (i + 1));
    }
}
if (firstEx != null) 
{
    throw new Exception("WebService call failed after 5 retries.", firstEx);
}
Sam
+1 I like the sleep
J.Hendrix
+1 me too ^___^
Plynx
+1 for the sleep and accepted answer....
JL
+1  A: 

Try a loop, with some kind of limit:

int retryCount = 5;
var done = false;
Exception error = null;
while (!done && retryCount > 0)
{
    try
    {
        MDO = OperationsWebService.MessageDownload(MI);
        done = true;
    }
    catch (Exception ex)
    {
        error = ex;
    }
    if (done)
        break;

    retryCount--;
}
Bevan
A: 
int cnt=0;
bool cont = true;
while (cont)
{
     try
     {
         MDO = OperationsWebService.MessageDownload(MI);
         cont = false; 
     }
     catch (Exception ex) 
     { 
         ++cnt;
         if (cnt == 5)
         {
             // 5 retries, ok now log and deal with the error. 
            cont = false;
         }
     } 
}

UPDATED : Fixed code based on comments.

James Curran
+9  A: 

All of the answers so far assume that the reaction to any exception should be to retry the operation. This is a good assumption right up until it's a false assumption. You could easily be retrying an operation that is damaging your system, all because you didn't check the exception type.

You should almost never use a bare "catch", nor "catch (Exception ex). Catch a more-specific exception - one you know you can safely recover from.

John Saunders
Good point. But doesn't an exception get raised before it damages the system? Isn't that the point?
Plynx
@Plynx: I was exaggerating a little. The operation being retried might have initiated some expensive processing, then thrown an exception, indicating "disk full" or something else that means the operation will never succeed. Retrying it would only waste system resources and never result in a success.
John Saunders
+1 In the author's scenario he states the problem is a communications one, not dealing with the local system. However I agree with the general reasoning.
John K
@jdk: if he knows it's a communications problem, he should be catching `CommunicationsException` and `TimeoutException`, not `Exception`.
John Saunders
@John S.: Good additional points to supplement the answer. +1 for that comment too :)
John K
+1  A: 

You should use recursion (or a loop), and should only retry if you got the error you expected.

For example:

static void TryExecute<TException>(Action method, Func<TException, bool> retryFilter, int maxRetries) where TException : Exception {
    try {
        method();
    } catch(TException ex) {
        if (maxRetries > 0 && retryFilter(ex))
            TryExecute(method, retryFilter, maxRetries - 1);
        else
            throw;
    }
}

EDIT: With a loop:

static void TryExecute<TException>(Action method, Func<TException, bool> retryFilter, int maxRetries) where TException : Exception {
    while (true) {
        try {
            method();
            return;
        } catch(TException ex) {
            if (maxRetries > 0 && retryFilter(ex))
                maxRetries--;
            else
                throw;
        }
    }
}

You can try to prevent future errors in retryFilter, perhaps by Thread.Sleep.

If the last retry fails, this will throw the last exception.

SLaks
As almost everyone else mentioned, you can, and probably should, use a loop.
SLaks
@SLaks: but recursion is sometimes amusing...
John Saunders
A: 

As everyone else has pointed out the correct approach is to wrap your try/catch inside some loop with a MAX_RETRY of some sort.

You might also consider adding a timeout between each loop iteration. Otherwise you're likely to burn through your retry counter before the transient issue has had a chance to resolve itself.

Seth Petry-Johnson
A: 

It seems you have the answers you need, but I thought I'd post this link, What is an Action Policy?, that I found to provide a much more elegant solution. Lokad has some rather labyrinthine implementations, but the guy's logic is pretty solid, and the end code you'd end up writing is pretty and simple.

HackedByChinese