Not to long ago I needed a way for a method to retry itself if the return value was not an expected value. My exact scenario was a requirement to upload a report to a vendor via FTP every 5 minutes. On the other end of the FTP was a service that watched for the report file and then processed it.

The problem is that the vendor didn’t always pick up the file in a timely manner and since the file name had to remain static (couldn’t change) there was a possibility of overwriting the previously uploaded file.

This may sound like poor design (no doubt it could have been better thought out) but it’s a problem I had to deal with. My original solution was to create this method

protected static bool WaitForEvent(Func<bool> func, bool result, int waitInterval, int cycles)
        {
            int waitCount = 0;
            while (func() != result)
            {
                if (waitCount++ < cycles)
                {
                    Thread.Sleep(waitInterval);
                }
                else
                {
                    return false;
                }
            }

            return true;
        }

and I would call this method using

 if (!WaitForEvent(() => { return FileHelper.FileExists(_dataFileName); }, false, 1000, 5))
{
     //Fail
}

This was an acceptable solution because it worked and I was in a hurry. It took in a function to execute and a few parameters then did it’s thing. But, what problems do you see here? I see two

1. It’s not so easy to read. What is going on here?

2. I have to make this call in multiple places which means if I need to change how long to wait, I have to then update it in multiple places. Of course, I had these values in the config so technically I could just change the config but even still if I had 10 methods that all required wait/retry functionality then I would have a bloated config. The reality is these values didn’t change often enough (if at all) to warrant config entries and all the plumbing that goes with it.

I later realized I could really clean this up and make it better by creating an aspect using PostSharp. I created a new aspect which inherits from MethodInterceptionAspect and takes parameters on the constructor to specify number of retries, the expected value and the time to wait between retries. I also added in a default value option for future uses.

[Serializable]
    public class RetryAspect : MethodInterceptionAspect
    {
        private int _sleep;
        private int _retries;
        private object _expectedResult;
        private object _defaultReturnValue;

        public RetryAspect(object expectedResult, int waitBetweenCycles, int numberOfRetries) : this(expectedResult, waitBetweenCycles, numberOfRetries, null) { }

        public RetryAspect(object expectedResult, int waitBetweenCycles, int numberOfRetries, object defaultReturnValue)
        {
            _expectedResult = expectedResult;
            _sleep = waitBetweenCycles;
            _retries = numberOfRetries;
            _defaultReturnValue = defaultReturnValue;
        }

        public override void OnInvoke(MethodInterceptionArgs args)
        {
            int waitCount = 0;

            while (!args.ReturnValue.Equals(_expectedResult))
            {
                args.Proceed();

                if (waitCount++ < _retries)
                {
                    Thread.Sleep(_sleep);
                }
                else
                {
                    if (_defaultReturnValue != null)
                    {
                        args.ReturnValue = _defaultReturnValue;
                    }

                    break;
                }
            }
        }
    }

When a method that is decorated with this aspect is being invoked, the advice I provided will take over. The code is very similar to the original method except that now we’re checking the return value of the method using the MethodInterceptionArgs.ReturnValue and if it doesn’t meet our expectations then we make another call to the target method using the MethodInterceptionArgs.Proceed().

Just a note: There is no transactional code here so if the method performs any actions that require rollback on failure then you will have to implement that manually.

Slap this aspect on a method and you’re done. Instant wait/retry functionality.

class Program
    {
        static int cnt = 0;

        static void Main(string[] args)
        {
            Console.WriteLine(Test());
            Console.ReadKey();
        }

        [RetryAspect(true, 1000, 5)]
        public static bool Test()
        {
                Console.WriteLine("Test {0}", cnt);
                if (cnt == 4)
                {
                    return true;
                }
                else
                {
                    cnt++;
                    return false;
                }
        }
    }

The output is

Test 0
Test 1
Test 2
Test 3
Test 4
True

The results show that the Test() method executed 5 times and only after the 5th time when the expected value was returned did it write the result of Test() which was ‘True’.

Now I don’t have to worry about where this method is being called from, I know that it will always have wait/retry functionality and if I do need to adjust the values I can do it in one place – the method declaration.

Advertisements