Tuesday, October 20, 2015

Retry algorithm

Retry with exponential back-off

I think this is an useful class so I'm just going to leave it here. (I'm annoyed by the duplication between Retry and RetryAsync but I haven't been able to remove it.)

  public interface RetryPolicy
  {
    T Retry<T>(Func<T> func);
    void Retry(Action action);

    Task<T> RetryAsync<T>(Func<Task<T>> func);
    Task RetryAsync(Func<Task> action);
  }

  public class RetryPolicyWithExponentialDelay : RetryPolicy
  {
    // ReSharper disable once InconsistentNaming
    public Func<double> GetRandom = () => RND.NextDouble();

    // ReSharper disable once InconsistentNaming
    public Action<int> Sleep = timeout => Thread.Sleep(timeout);

    public RetryPolicyWithExponentialDelay(int maxCount, TimeSpan initialDelay, TimeSpan maxDelay)
    {
      this.maxCount = maxCount;
      this.initialDelay = initialDelay;
      this.maxDelay = maxDelay;
    }

    public T Retry<T>(Func<T> func)
    {
      var count = 0;
      var delay = initialDelay;

      while (true)
      {
        try
        {
          return func();
        }
        catch
        {
          count++;
          if (count >= maxCount)
            throw;

          SleepUpTo(delay);
          delay = IncreaseDelay(delay);
        }
      }
    }

    public void Retry(Action action)
    {
      Retry(() =>
      {
        action();
        return 0;
      });
    }

    public async Task<T> RetryAsync<T>(Func<Task<T>> func)
    {
      var count = 0;
      var delay = initialDelay;

      while (true)
      {
        try
        {
          return await func();
        }
        catch
        {
          count++;
          if (count >= maxCount)
            throw;

          SleepUpTo(delay);
          delay = IncreaseDelay(delay);
        }
      }
    }

    public async Task RetryAsync(Func<Task> action)
    {
      await RetryAsync(async () =>
      {
        await action();
        return 0;
      });
    }

    //

    private static readonly Random RND = new Random();

    private readonly int maxCount;
    private readonly TimeSpan initialDelay;
    private readonly TimeSpan maxDelay;

    private void SleepUpTo(TimeSpan delay)
    {
      var actualDelay = (int) Math.Truncate(GetRandom() * delay.TotalMilliseconds);
      Sleep(actualDelay);
    }

    private TimeSpan IncreaseDelay(TimeSpan delay)
    {
      delay = delay.Add(delay);
      if (delay > maxDelay)
        delay = maxDelay;

      return delay;
    }
  }

I'm also adding the tests here:

  [TestClass]
  public class RetryPolicyWithExponentialDelayTests
  {
    private const int RESULT = 100;

    private RetryPolicyWithExponentialDelay sut;

    private int called;
    private int sleepTime;

    [TestInitialize]
    public void SetUp()
    {
      sut = new RetryPolicyWithExponentialDelay(5, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5))
      {
        GetRandom = () => 1.0,
        Sleep = timeout => sleepTime += timeout,
      };

      called = 0;
      sleepTime = 0;
    }

    [TestClass]
    public class Sync : RetryPolicyWithExponentialDelayTests
    {
      private Action action;
      private Func<int> func;

      [TestInitialize]
      public void InnerSetup()
      {
        action = null;
        func = () =>
        {
          action();
          return RESULT;
        };
      }

      [TestClass]
      public class SyncFunc : Sync
      {
        [TestMethod]
        public void NoErrors()
        {
          action = GetActionWithErrors(0);

          var result = sut.Retry(func);

          Assert.AreEqual(1, called, "Function was not called");
          Assert.AreEqual(RESULT, result, "Invalid result");
          Assert.AreEqual(0, sleepTime, "Should not have slept");
        }

        [TestMethod]
        public void OneError()
        {
          action = GetActionWithErrors(1);

          var result = sut.Retry(func);

          Assert.AreEqual(2, called, "The call was not retried");
          Assert.AreEqual(RESULT, result, "Invalid result");
          Assert.AreEqual(TimeSpan.FromSeconds(1).TotalMilliseconds, sleepTime, 1, "Did not sleep the correct amount");
        }

        [TestMethod]
        public void TwoErrors()
        {
          action = GetActionWithErrors(2);

          var result = sut.Retry(func);

          Assert.AreEqual(3, called, "The call was not retried twice");
          Assert.AreEqual(RESULT, result, "Invalid result");
          Assert.AreEqual(TimeSpan.FromSeconds(1 + 2).TotalMilliseconds, sleepTime, 1, "Did not sleep the correct amount");
        }

        [TestMethod]
        public void FourErrors()
        {
          action = GetActionWithErrors(4);

          var result = sut.Retry(func);

          Assert.AreEqual(5, called, "The call was not retried four times");
          Assert.AreEqual(RESULT, result, "Invalid result");
          Assert.AreEqual(TimeSpan.FromSeconds(1 + 2 + 4 + 5).TotalMilliseconds, sleepTime, 1, "Did not sleep the correct amount (limited by max delay)");
        }

        [TestMethod]
        public void TooManyErrors()
        {
          action = GetActionWithErrors(10);

          try
          {
            sut.Retry(func);
            Assert.Fail("The call did not throw");
          }
          catch
          {
            Assert.AreEqual(5, called, "The call was not tried five times");
            Assert.AreEqual(TimeSpan.FromSeconds(1 + 2 + 4 + 5).TotalMilliseconds, sleepTime, 1, "Did not sleep the correct amount (limited by max delay)");
          }
        }
      }

      [TestClass]
      public class SyncAction : Sync
      {
        [TestMethod]
        public void NoErrors()
        {
          action = GetActionWithErrors(0);

          sut.Retry(action);

          Assert.AreEqual(1, called, "Function was not called");
          Assert.AreEqual(0, sleepTime, "Should not have slept");
        }

        [TestMethod]
        public void OneError()
        {
          action = GetActionWithErrors(1);

          sut.Retry(action);

          Assert.AreEqual(2, called, "The call was not retried");
          Assert.AreEqual(TimeSpan.FromSeconds(1).TotalMilliseconds, sleepTime, 1, "Did not sleep the correct amount");
        }

        [TestMethod]
        public void TwoErrors()
        {
          action = GetActionWithErrors(2);

          sut.Retry(action);

          Assert.AreEqual(3, called, "The call was not retried twice");
          Assert.AreEqual(TimeSpan.FromSeconds(1 + 2).TotalMilliseconds, sleepTime, 1, "Did not sleep the correct amount");
        }

        [TestMethod]
        public void FourErrors()
        {
          action = GetActionWithErrors(4);

          sut.Retry(action);

          Assert.AreEqual(5, called, "The call was not retried four times");
          Assert.AreEqual(TimeSpan.FromSeconds(1 + 2 + 4 + 5).TotalMilliseconds, sleepTime, 1, "Did not sleep the correct amount (limited by max delay)");
        }

        [TestMethod]
        public void TooManyErrors()
        {
          action = GetActionWithErrors(10);

          try
          {
            sut.Retry(action);
            Assert.Fail("The call did not throw");
          }
          catch
          {
            Assert.AreEqual(5, called, "The call was not tried five times");
            Assert.AreEqual(TimeSpan.FromSeconds(1 + 2 + 4 + 5).TotalMilliseconds, sleepTime, 1, "Did not sleep the correct amount (limited by max delay)");
          }
        }
      }
    }

    [TestClass]
    public class Async : RetryPolicyWithExponentialDelayTests
    {
      private Action action;
      private Func<Task<int>> func;

      [TestInitialize]
      public void InnerSetup()
      {
        action = null;
        func = async () =>
        {
          await Task.Run(action);
          return RESULT;
        };
      }

      [TestClass]
      public class AsyncFunc : Async
      {
        [TestMethod]
        public async Task NoErrorsAsync()
        {
          action = GetActionWithErrors(0);

          var result = await sut.RetryAsync(func);

          Assert.AreEqual(1, called, "Function was not called");
          Assert.AreEqual(RESULT, result, "Invalid result");
          Assert.AreEqual(0, sleepTime, "Should not have slept");
        }

        [TestMethod]
        public async Task OneErrorAsync()
        {
          action = GetActionWithErrors(1);

          var result = await sut.RetryAsync(func);

          Assert.AreEqual(2, called, "The call was not retried");
          Assert.AreEqual(RESULT, result, "Invalid result");
          Assert.AreEqual(TimeSpan.FromSeconds(1).TotalMilliseconds, sleepTime, 1, "Did not sleep the correct amount");
        }

        [TestMethod]
        public async Task TwoErrorsAsync()
        {
          action = GetActionWithErrors(2);

          var result = await sut.RetryAsync(func);

          Assert.AreEqual(3, called, "The call was not retried twice");
          Assert.AreEqual(RESULT, result, "Invalid result");
          Assert.AreEqual(TimeSpan.FromSeconds(1 + 2).TotalMilliseconds, sleepTime, 1, "Did not sleep the correct amount");
        }

        [TestMethod]
        public async Task FourErrorsAsync()
        {
          action = GetActionWithErrors(4);

          var result = await sut.RetryAsync(func);

          Assert.AreEqual(5, called, "The call was not retried four times");
          Assert.AreEqual(RESULT, result, "Invalid result");
          Assert.AreEqual(TimeSpan.FromSeconds(1 + 2 + 4 + 5).TotalMilliseconds, sleepTime, 1, "Did not sleep the correct amount (limited by max delay)");
        }

        [TestMethod]
        public async Task TooManyErrorsAsync()
        {
          action = GetActionWithErrors(10);

          try
          {
            await sut.RetryAsync(func);
            Assert.Fail("The call did not throw");
          }
          catch
          {
            Assert.AreEqual(5, called, "The call was not tried five times");
            Assert.AreEqual(TimeSpan.FromSeconds(1 + 2 + 4 + 5).TotalMilliseconds, sleepTime, 1, "Did not sleep the correct amount (limited by max delay)");
          }
        }
      }

      [TestClass]
      public class AsyncAction : Async
      {
        [TestMethod]
        public async Task NoErrorsAsync()
        {
          action = GetActionWithErrors(0);

          await sut.RetryAsync(() => Task.Run(action));

          Assert.AreEqual(1, called, "Function was not called");
          Assert.AreEqual(0, sleepTime, "Should not have slept");
        }

        [TestMethod]
        public async Task OneErrorAsync()
        {
          action = GetActionWithErrors(1);

          await sut.RetryAsync(() => Task.Run(action));

          Assert.AreEqual(2, called, "The call was not retried");
          Assert.AreEqual(TimeSpan.FromSeconds(1).TotalMilliseconds, sleepTime, 1, "Did not sleep the correct amount");
        }

        [TestMethod]
        public async Task TwoErrorsAsync()
        {
          action = GetActionWithErrors(2);

          await sut.RetryAsync(() => Task.Run(action));

          Assert.AreEqual(3, called, "The call was not retried twice");
          Assert.AreEqual(TimeSpan.FromSeconds(1 + 2).TotalMilliseconds, sleepTime, 1, "Did not sleep the correct amount");
        }

        [TestMethod]
        public async Task FourErrorsAsync()
        {
          action = GetActionWithErrors(4);

          await sut.RetryAsync(() => Task.Run(action));

          Assert.AreEqual(5, called, "The call was not retried four times");
          Assert.AreEqual(TimeSpan.FromSeconds(1 + 2 + 4 + 5).TotalMilliseconds, sleepTime, 1, "Did not sleep the correct amount (limited by max delay)");
        }

        [TestMethod]
        public async Task TooManyErrorsAsync()
        {
          action = GetActionWithErrors(10);

          try
          {
            await sut.RetryAsync(() => Task.Run(action));
            Assert.Fail("The call did not throw");
          }
          catch
          {
            Assert.AreEqual(5, called, "The call was not tried five times");
            Assert.AreEqual(TimeSpan.FromSeconds(1 + 2 + 4 + 5).TotalMilliseconds, sleepTime, 1, "Did not sleep the correct amount (limited by max delay)");
          }
        }
      }
    }

    //

    private Action GetActionWithErrors(int errorCount)
    {
      return () =>
      {
        called++;
        if (called <= errorCount)
          throw new Exception();
      };
    }
  }

Friday, May 30, 2014

Stupid code fragments, part two

Probabilities are hard. As an example, there's a known puzzle: a family has two children; if one of them is a girl, what is the probability that the other one is also a girl?

The answer, un-intuitively, is not 1/2 but 1/3. There are various explanations but – as with the Monty Python puzzle years ago – I wanted to write code to check it out, so I wrote the following using LinqPad:

  
  void Main()  
  {  
    var rnd = new Random();  
      
    // Generate a random set of families with two children; true means girl, false means boy  
    var all = Enumerable.Range(1, 10000).Select(_ => new Pair(rnd.Next(2) == 0, rnd.Next(2) == 0)).ToList();  
      
    // Extract only the families with at least one girl  
    var oneGirl = all.Where(it => it.First || it.Second).ToList();  
      
    // Out of those families, how many have two girls? The result should be 1/3rd  
    var otherGirl = oneGirl.Where(it => it.First && it.Second).ToList();  
    Console.WriteLine((decimal) otherGirl.Count / oneGirl.Count);  
  }  
  
  // Define other methods and classes here  
  public class Pair  
  {  
    public bool First { get; private set; }  
    public bool Second { get; private set; }  
      
    public Pair(bool first, bool second)  
    {  
    First = first;  
    Second = second;  
    }  
  }  

Conditional probabilities (that is, probabilities where we have additional information – in this case, knowing that one child is a girl) are surprisingly tricky. A way to express the condition more clearly is: what is the probability that a family with two kids has two girls, given that they have at least one girl? Probabilities are, at base, expressions of uncertainty. If we ask "what is the probability that a family with two children has two girls?" we're in a situation of maximum uncertainty: all outcomes (BB, BG, GB and GG) are equally probable so the best we can do is 1/4 – that is the mathematical equivalent of "no clue". However, if we add some information – namely that one of the children is definitely a girl – then we removed some uncertainty from the problem: the BB case is no longer possible. This lowers the uncertainty of the GG case to 1/3.

Stupid code fragments, part one

I just discovered a surprisingly simple (and obvious in hindsight) algorithm for calculating the week index of a given date. For example, April 15th is in the 3rd week (or the 3rd Tuesday of the month).

I was going to do the usual thing and just Google for it but then I realized that the solution is extremely simple:

    private static int GetWeekPosition(DateTime date)
    {
      // the position of the given date is how many times I can subtract 7 days (go back one week) and still be in the same month
      // in other words, it's the integer part of (day / 7)
      return date.Day / 7;
    }

(I am returning a base-zero result, but you can of course add 1 if you need it.)

I realize this is not the answer to the Universe or anything but I thought it's interesting.