LinuxCommunicator/RetryFramework.cs (230 lines of code) (raw):
//------------------------------------------------------------------------------
// <copyright file="ExceptionHelper.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <owner current="true" primary="true">nzeng</owner>
// Security review: nzeng 01-11-06
//------------------------------------------------------------------------------
#region Using directives
using System;
using System.Text;
using System.Diagnostics;
using System.Threading;
#endregion
namespace Microsoft.Hpc
{
internal class RetryManager
{
public const int InfiniteRetries = -1;
RetryWaitTimer _waitTimer;
int _maxRetries;
int _totalTimeLimit = Timeout.Infinite;
int _retryCount = 0;
int _totalWaitTime = 0;
int _currentWaitTime = 0;
public RetryManager(RetryWaitTimer waitTimer) : this(waitTimer, InfiniteRetries) { }
public RetryManager(RetryWaitTimer waitTimer, int maxRetries) : this(waitTimer, maxRetries, Timeout.Infinite) { }
public RetryManager(RetryWaitTimer waitTimer, int maxRetries, int totalTimeLimit)
{
if (waitTimer == null)
{
throw new ArgumentNullException("wait");
}
_waitTimer = waitTimer;
SetMaxRetries(maxRetries);
SetTotalTimeLimit(totalTimeLimit);
}
/// <summary>
/// Gets the number of retries attempted thus far
/// </summary>
public int RetryCount { get { return _retryCount; } }
/// <summary>
/// Get the total spent waiting between retries
/// </summary>
public int ElaspsedWaitTime { get { return _totalWaitTime; } }
/// <summary>
/// Gets or sets the maximum number of retries
/// </summary>
public int MaxRetryCount
{
get { return _maxRetries; }
set { SetMaxRetries(value); }
}
/// <summary>
/// Gets or sets the total amount of time that may be spend waiting for retries.
/// </summary>
public int TotalTimeLimit
{
get { return _totalTimeLimit; }
set { SetTotalTimeLimit(value); }
}
void SetMaxRetries(int n)
{
if (n <= 0 && n != RetryManager.InfiniteRetries)
{
throw new ArgumentException("The maximum number of retries must be greater than zero, or RetryOperator.InfiniteRetries");
}
_maxRetries = n;
}
void SetTotalTimeLimit(int t)
{
if (t <= 0 && t != Timeout.Infinite)
{
throw new ArgumentException("The specified time must be greater than zero, or Timeout.Infinite");
}
_totalTimeLimit = t;
}
/// <summary>
/// Returns true if there are more retries left
/// </summary>
public bool HasAttemptsLeft
{
get
{
return ((_maxRetries == RetryManager.InfiniteRetries || _retryCount < _maxRetries)
&& (_totalTimeLimit == Timeout.Infinite || _totalWaitTime < _totalTimeLimit));
}
}
/// <summary>
/// Get the next wait time
/// </summary>
public int NextWaitTime
{
get
{
int waitTime = _waitTimer.GetNextWaitTime(_retryCount, _currentWaitTime);
if (_totalTimeLimit != Timeout.Infinite && (_totalWaitTime + waitTime > _totalTimeLimit))
{
waitTime = _totalTimeLimit - _totalWaitTime;
}
return waitTime;
}
}
/// <summary>
/// Increment the retry count and advance the total wait time without actually waiting
/// </summary>
public void SimulateNextAttempt()
{
WaitForNextAttempt(false);
}
/// <summary>
/// Wait until the next retry by making the current thread sleep for the appropriate amount of time.
/// May return immediately if the wait is zero.
/// </summary>
public void WaitForNextAttempt()
{
WaitForNextAttempt(true);
}
void WaitForNextAttempt(bool doSleep)
{
if (!HasAttemptsLeft)
{
throw new InvalidOperationException("There are no more retry attempts remaining");
}
_currentWaitTime = NextWaitTime;
_retryCount++;
Debug.Assert(_currentWaitTime >= 0);
if (_currentWaitTime > 0)
{
if (doSleep)
{
Thread.Sleep(_currentWaitTime);
}
_totalWaitTime += _currentWaitTime;
}
}
/// <summary>
/// Resets the retry manager's retry count
/// </summary>
public void Reset()
{
_retryCount = 0;
_totalWaitTime = 0;
_currentWaitTime = 0;
}
}
/// <summary>
/// Defines how long a retry manager will wait between sub-sequent retries
/// </summary>
internal abstract class RetryWaitTimer
{
internal abstract int GetNextWaitTime(int retryCount, int currentWaitTime);
}
/// <summary>
/// Instantly returns without waiting
/// </summary>
internal class InstantRetryTimer : RetryWaitTimer
{
internal override int GetNextWaitTime(int retryCount, int currentWaitTime)
{
return 0;
}
// This class should be a singleton
private InstantRetryTimer() { }
static InstantRetryTimer _instance = new InstantRetryTimer();
public static InstantRetryTimer Instance
{
get { return _instance; }
}
}
/// <summary>
/// Waits a constant time between subsequent retries
/// </summary>
internal class PeriodicRetryTimer : RetryWaitTimer
{
int _period;
public PeriodicRetryTimer(int period)
{
if (period < 0)
{
throw new ArgumentOutOfRangeException("period", "The period must be a non-negative integer (in milliseconds)");
}
_period = period;
}
internal override int GetNextWaitTime(int retryCount, int currentWaitTime)
{
return _period;
}
}
/// <summary>
/// A retry timer where wait time at retry n depends on the wait at retry n-1.
/// </summary>
internal abstract class BoundedBackoffRetryTimer : RetryWaitTimer
{
int _initialWait;
int _waitUpperBound;
protected BoundedBackoffRetryTimer(int initialWait, int waitUpperBound)
{
if (initialWait <= 0)
{
throw new ArgumentOutOfRangeException("initialWait", "Initial value must be a positive integer (in milliseconds)");
}
if (waitUpperBound <= 0 && waitUpperBound != Timeout.Infinite)
{
throw new ArgumentOutOfRangeException("waitCap", "The wait cap must be greater than zero, or Timeout.Infinite");
}
_initialWait = initialWait;
_waitUpperBound = waitUpperBound;
}
internal override int GetNextWaitTime(int retryCount, int currentWaitTime)
{
if (retryCount == 0)
{
return _initialWait;
}
int nextWaitTime = GetBackOffValue(currentWaitTime);
if (nextWaitTime < 0)
{
return 0;
}
if (_waitUpperBound != Timeout.Infinite && nextWaitTime > _waitUpperBound)
{
return _waitUpperBound;
}
return nextWaitTime;
}
protected abstract int GetBackOffValue(int currentValue);
}
/// <summary>
/// Wait times will increase exponentially
/// </summary>
internal class ExponentialBackoffRetryTimer : BoundedBackoffRetryTimer
{
double _growthFactor;
public ExponentialBackoffRetryTimer(int initialWait) : this(initialWait, Timeout.Infinite, 2) { }
public ExponentialBackoffRetryTimer(int initialWait, int waitUpperBound) : this(initialWait, waitUpperBound, 2) { }
public ExponentialBackoffRetryTimer(int initialWait, int waitUpperBound, double growthFactor)
: base(initialWait, waitUpperBound)
{
if (growthFactor <= 0)
{
throw new ArgumentOutOfRangeException("growthFactor", "The growth factor must be a positive value");
}
_growthFactor = growthFactor;
}
protected override int GetBackOffValue(int currentValue)
{
return (int)Math.Round(currentValue * _growthFactor);
}
}
/// <summary>
/// Wait times will increase exponentially and also vary a bit randomly
/// </summary>
internal class ExponentialRandomBackoffRetryTimer : ExponentialBackoffRetryTimer
{
Random _rand = null;
public ExponentialRandomBackoffRetryTimer(int initialWait) : this(initialWait, Timeout.Infinite, 2) { }
public ExponentialRandomBackoffRetryTimer(int initialWait, int waitUpperBound) : this(initialWait, waitUpperBound, 2) { }
public ExponentialRandomBackoffRetryTimer(int initialWait, int waitUpperBound, double growthFactor)
: base(initialWait, waitUpperBound,growthFactor)
{
_rand = new Random();
}
protected override int GetBackOffValue(int currentValue)
{
return ((int)base.GetBackOffValue(currentValue)) + _rand.Next(0, currentValue);
}
}
/// <summary>
/// Wait times will increase linearly
/// </summary>
internal class LinearBackoffRetryTimer : BoundedBackoffRetryTimer
{
int _increment;
public LinearBackoffRetryTimer(int initialWait) : this(initialWait, Timeout.Infinite, initialWait) { }
public LinearBackoffRetryTimer(int initialWait, int waitUpperBound) : this(initialWait, waitUpperBound, initialWait) { }
public LinearBackoffRetryTimer(int initialWait, int waitUpperBound, int increment)
: base(initialWait, waitUpperBound)
{
_increment = increment;
}
protected override int GetBackOffValue(int currentValue)
{
return currentValue + _increment;
}
}
}