Home
Manage Your Code
Snippet: Retry Policy (C#)
Title: Retry Policy Language: C#
Description: A fixed and exponential back-off retry policy. Views: 392
Author: Stephen Smith Date Added: 6/25/2012
Copy Code  
1// ReSharper disable CheckNamespace

2
3using System;
4using System.Diagnostics;
5using System.Text;
6using System.Threading;
7using Smithfield.WinRosta.eziTracker;
8
9namespace Smithfield
10{
11    public static class Retry
12    {
13        /// <summary> Retry a supplied <paramref name="action"/> with a fixed interlude between attempts 

14        /// </summary>

15        /// <param name="action">

16        /// The action to retry.

17        /// </param>

18        /// <param name="mumberOfRetries">

19        /// The maximum number of retry attempts (default 3).

20        /// </param>

21        /// <param name="delayInMillisecondsBetweenRetries">

22        /// The delay in milliseconds between each attempt (default 200).

23        /// </param>

24        /// <returns>

25        /// true if the <paramref name="action"/> completed successfully;

26        /// otherwise, false.

27        /// </returns>

28        /// <remarks>

29        /// The parameter action should return true (successful) if no more attempts

30        /// are needed, otherwise false.

31        /// </remarks>

32        public static bool Fixed(Func<RetryAttempt, bool> action, int mumberOfRetries = 3, int delayInMillisecondsBetweenRetries = 200)
33        {
34            return (new FixedRetryPolicy(mumberOfRetries, delayInMillisecondsBetweenRetries).Retry(action));
35        }
36
37        /// <summary> Retry a supplied <paramref name="action"/> with a fixed interlude between attempts 

38        /// </summary>

39        /// <param name="action">

40        /// The action to retry.

41        /// </param>

42        /// <param name="name">The retry name used for logging purposes</param>

43        /// <param name="mumberOfRetries">

44        /// The maximum number of retry attempts (default 3).

45        /// </param>

46        /// <param name="delayInMillisecondsBetweenRetries">

47        /// The delay in milliseconds between each attempt (default 200).

48        /// </param>

49        /// <returns>

50        /// true if the <paramref name="action"/> completed successfully;

51        /// otherwise, false.

52        /// </returns>

53        /// <remarks>

54        /// The parameter action should return true (successful) if no more attempts

55        /// are needed, otherwise false.

56        /// </remarks>

57        public static bool Fixed(Func<bool> action, string name = null, int mumberOfRetries = 3, int delayInMillisecondsBetweenRetries = 200)
58        {
59            var adpater = new RetryActionAdapter(action, name);
60            return (new FixedRetryPolicy(mumberOfRetries, delayInMillisecondsBetweenRetries).Retry(adpater.RetryAction));
61        }
62
63        /// <summary> Retry a supplied <paramref name="action"/> with a fixed interlude between attempts 

64        /// </summary>

65        /// <param name="action">

66        /// The action to retry.

67        /// </param>

68        /// <param name="name">A logging name</param>

69        /// <param name="mumberOfRetries">

70        /// The maximum number of retry attempts (default 3).

71        /// </param>

72        /// <param name="delayInMillisecondsBetweenRetries">

73        /// The delay in milliseconds between each attempt (default 200).

74        /// </param>

75        /// <returns>

76        /// true if the <paramref name="action"/> completed successfully;

77        /// otherwise, false.

78        /// </returns>

79        /// <remarks>

80        /// If no logging name is provided then <em>Fixed</em>

81        /// will not emit any trace information.

82        /// </remarks>

83        public static bool Fixed(Action action, string name = null, int mumberOfRetries = 3, int delayInMillisecondsBetweenRetries = 200)
84        {
85            var adpater = new RetryActionAdapter(action, name);
86            return (new FixedRetryPolicy(mumberOfRetries, delayInMillisecondsBetweenRetries).Retry(adpater.RetryAction));
87        }
88
89        /// <summary> Retry a <paramref name="action"/> based on an exponential back-off policy.

90        /// </summary>

91        /// <param name="action">The action to retry.</param>

92        /// <param name="maxAttempts">The maximum number of retry attempts (defaults to 10)</param>

93        /// <param name="power">The exponential factor (defaults to 3)</param>

94        /// <param name="initialDelayInMilliseconds">

95        /// The initial delay in milliseconds (defaults to 100 milliseconds)

96        /// </param>

97        /// <param name="maxDelayInSeconds">

98        /// The maximum permitted delay between retries in seconds (defaults to 10 seconds).

99        /// </param>

100        /// <returns>

101        /// true if the <paramref name="action"/> completed successfully;

102        /// otherwise, false.

103        /// </returns>

104        /// <remarks>

105        /// The parameter action should return true (successful) if no more attempts

106        /// are needed, otherwise false.

107        /// <para>

108        /// If the <paramref name="action"/> throws a <see cref="Exception"/>

109        /// then the <em>Retry</em> policy will be aborted. Throwing an 

110        /// <em>Exception</em> allows the <paramref name="action"/>

111        /// to propagate an error up the calling chain. 

112        /// </para>

113        /// The retry policy does not emit any logging it is expected

114        /// that the <paramref name="action"/> will handle all logging.

115        /// </remarks>

116        public static bool Backoff(Func<RetryAttempt, bool> action, int maxAttempts = 10, int power = 3, int initialDelayInMilliseconds = 100, int maxDelayInSeconds = 10 * 1000)
117        {
118            return (new RetryPolicyExpotentialBackoff(maxAttempts, power, initialDelayInMilliseconds, maxDelayInSeconds).Retry(action));
119        }
120
121        /// <summary> Retry a <paramref name="action"/> based on an exponential back-off policy.

122        /// </summary>

123        /// <param name="action">The action to retry.</param>

124        /// <param name="name">Name used for logging purposes</param>

125        /// <param name="maxAttempts">The maximum number of retry attempts (defaults to 10)</param>

126        /// <param name="power">The exponential factor (defaults to 3)</param>

127        /// <param name="initialDelayInMilliseconds">

128        /// The initial delay in milliseconds (defaults to 100 milliseconds)

129        /// </param>

130        /// <param name="maxDelayInSeconds">

131        /// The maximum permitted delay between retries in seconds (defaults to 10 seconds).

132        /// </param>

133        /// <returns>

134        /// true if the <paramref name="action"/> completed successfully;

135        /// otherwise, false.

136        /// </returns>

137        /// <remarks>

138        /// The parameter action should return true (successful) if no more attempts

139        /// are needed, otherwise false.

140        /// <para>

141        /// If the <paramref name="action"/> throws a <see cref="Exception"/>

142        /// then the <em>Retry</em> policy will be aborted. Throwing an 

143        /// <em>Exception</em> allows the <paramref name="action"/>

144        /// to propagate an error up the calling chain.

145        /// </para>

146        /// The retry policy does not emit any logging it is expected

147        /// that the <paramref name="action"/> will handle all logging.

148        /// </remarks>

149        public static bool Backoff(Func<bool> action, string name = null, int maxAttempts = 10, int power = 3, int initialDelayInMilliseconds = 100, int maxDelayInSeconds = 10 * 1000)
150        {
151            var adpater = new RetryActionAdapter(action, name);
152            return (new RetryPolicyExpotentialBackoff(maxAttempts, power, initialDelayInMilliseconds, maxDelayInSeconds).Retry(adpater.RetryAction));
153        }
154
155        /// <summary>Retry a <paramref name="action"/> based on an exponential back-off policy.

156        /// </summary>

157        /// <param name="action">The action to retry.</param>

158        /// <param name="name">A logging name</param>

159        /// <param name="maxAttempts">The maximum number of retry attempts (defaults to 10)</param>

160        /// <param name="power">The exponential factor (defaults to 3)</param>

161        /// <param name="initialDelayInMilliseconds">

162        /// The initial delay in milliseconds (defaults to 100 milliseconds)

163        /// </param>

164        /// <param name="maxDelayInSeconds">

165        /// The maximum permitted delay between retries in seconds (defaults to 10 seconds).

166        /// </param>

167        /// <returns>

168        /// true if the <paramref name="action"/> completed successfully;

169        /// otherwise, false.

170        /// </returns>

171        /// <remarks>

172        /// If the <paramref name="action"/> should return true to indicate

173        /// success and that no more retry attempts should be made.

174        /// <para>

175        /// If no logging name is provided then <em>Back-off</em>

176        /// will not emit any trace information.

177        /// </para>

178        /// </remarks>

179        public static bool Backoff(Action action, string name = null, int maxAttempts = 10, int power = 3, int initialDelayInMilliseconds = 100, int maxDelayInSeconds = 10 * 1000)
180        {
181            var adpater = new RetryActionAdapter(action, name);
182            return (new RetryPolicyExpotentialBackoff(maxAttempts, power, initialDelayInMilliseconds, maxDelayInSeconds).Retry(adpater.RetryAction));
183        }
184        
185        /// <summary>

186        /// Invokes the <see cref="IRetryPolicy"/> <see cref="IRetryPolicy.Retry"/> using the supplied function.

187        /// </summary>

188        /// <param name="policy">The retry policy used to determine how and when a retry attempt is made.</param>

189        /// <param name="func">

190        /// The function to retry, takes a <see cref="RetryAttempt"/>, and returns a <see cref="bool"/>

191        /// indicating success with true; or false, to retry.

192        /// </param>

193        /// <param name="retryName">The name used for logging purposes.</param>

194        /// <returns>

195        /// True if the <paramref name="func"/> was successful; otherwise false.

196        /// </returns>

197        /// <remarks>

198        /// The <paramref name="func"/> must return true if the operation was successful; or false

199        /// to retry based on the supplied <paramref name="policy"/>.

200        /// <example>

201        /// Given a retry policy then you can use it to 'retry' the function:

202        /// <code>

203        /// 
204        /// var policy = new FixedRetryPolicy(5, 200);

205        /// if (Retry.Try(policy, MyFunc, "test retry")

206        ///     Console.WriteLine("Retry was successful!");

207        /// 

208        /// bool MyFunc(RetryAttempt attempt)

209        /// {

210        ///     const string retryName = "test retry";

211        ///      

212        ///     var success = false;  // add your own 'action' logic here! 

213        /// 

214        ///     if (success) return true;

215        ///     

216        ///     // log the last attempt

217        ///     if (attempt.LastAttempt && Tracing.Switch.TraceError)

218        ///         Trace.TraceError(

219        ///             "RetryPolicy: The action '{0}' failed! Total retry attempts made were {1}! You are out of retries!",

220        ///             retryName,

221        ///             attempt.Attempt);

222        ///     

223        ///     // log an error on every 3 attempts

224        ///     else if (Tracing.Switch.TraceWarning &&

225        ///              (attempt.Attempt == 0 || ((attempt.Attempt%3) == 0)))

226        ///         Trace.TraceWarning(

227        ///             "RetryPolicy: The action '{0}' failed (on attempt {1}), will retry again in {2} milliseconds!",

228        ///             retryName,

229        ///             attempt.Attempt,

230        ///             attempt.NextRetryIn.TotalMilliseconds);

231        ///     

232        ///     return false;

233        /// }

234        /// ]]>

235        /// </code>

236        /// </example>

237        /// </remarks>

238        public static bool Try(IRetryPolicy policy, Func<RetryAttempt, bool> func, string retryName)
239        {
240            return policy.Retry(attempt => (func(attempt)));
241        }
242
243        /// <summary>

244        /// Invokes the <see cref="IRetryPolicy"/> <see cref="IRetryPolicy.Retry"/> using the supplied action.

245        /// </summary>

246        /// <param name="policy">The retry policy used to determine how and when a retry attempt is made.</param>

247        /// <param name="action">

248        /// The action to retry. The action should throw an error if unsuccessful.

249        /// </param>

250        /// <param name="retryName">The name used for logging purposes.</param>

251        /// <returns>

252        /// True if the <paramref name="action"/> was successful; otherwise false.

253        /// </returns>

254        /// <remarks>

255        /// The <paramref name="action"/> should throw an <see cref="Exception"/> if it failed. At which

256        /// point a retry attempt will be made based on the retry <see cref="policy"/>. 

257        /// <example>

258        /// Given a retry policy then you can use it to 'retry' an action:

259        /// <code>

260        /// var policy = new FixedRetryPolicy(5, 200);

261        /// if (Retry.Try(policy, myAction, "test retry")

262        ///     Console.WriteLine("Retry was successful!");

263        /// </code>

264        /// </example>

265        /// </remarks>

266        public static bool Try(IRetryPolicy policy, Action action, string retryName)
267        {
268            Func<RetryAttempt, bool> doRetry = attempt =>
269                {
270                    try
271                    {
272                        action();
273                        return true; // no error so we can stop trying

274                    }
275                    catch (Exception ex)
276                    {
277                        // don't retry on system critical or operation cancelled

278                        if (ex.IsCritical() || ex.IsOperationCancelled()) throw;
279
280                        // actions can only let us know they have failed by throwing an error so handle as 'unsuccessful'

281                        LogRetryAttempt(attempt, retryName, ex);
282
283                        if (attempt.LastAttempt)
284                            throw; // propagate the error back up the chain

285
286                        return false; // keep trying

287                    }
288                };
289
290            return policy.Retry(doRetry);
291        }
292
293
294        internal static void LogRetryAttempt(RetryAttempt attempt, string name, int logOnModulo = 3)
295        {
296            // log the last attempt

297            if (attempt.LastAttempt && Tracing.Switch.TraceError && !String.IsNullOrWhiteSpace(name))
298                Trace.TraceError(
299                    "RetryPolicy: The action '{0}' failed! Total retry attempts {1}. You are out of retries!",
300                    name,
301                    attempt.Attempt+1);
302
303            // log an error on every n attempts

304            else if (Tracing.Switch.TraceWarning &&
305                     (attempt.Attempt == 0 || ((attempt.Attempt%logOnModulo) == 0)) &&
306                     !String.IsNullOrWhiteSpace(name))
307                Trace.TraceWarning(
308                    "RetryPolicy: The action '{0}' failed (on attempt {1}), will retry again in {2} milliseconds!",
309                    name,
310                    attempt.Attempt+1,
311                    attempt.NextRetryIn.TotalMilliseconds);
312        }
313
314        internal static void LogRetryAttempt(RetryAttempt attempt, string name, Exception ex, int logOnModulo = 3)
315        {
316            // log the last attempt

317            if (attempt.LastAttempt && Tracing.Switch.TraceError && !String.IsNullOrWhiteSpace(name))
318                Trace.TraceError(
319                    "RetryPolicy: The action '{0}' failed! Total retry attempts made were {1}. You are out of retries!",
320                    name,
321                    attempt.Attempt+1);
322
323            // log an error on every n attempts

324            else if (Tracing.Switch.TraceWarning &&
325                     (attempt.Attempt == 0 || ((attempt.Attempt%logOnModulo) == 0)) && !String.IsNullOrWhiteSpace(name))
326                Trace.TraceWarning(
327                    "RetryPolicy: The action '{0}' failed (on attempt {1}), will retry again in {2} milliseconds. The error message is {3}",
328                    name,
329                    attempt.Attempt+1,
330                    attempt.NextRetryIn.TotalMilliseconds,
331                    GetExtendedMessage(ex));
332        }
333    
334        private static string GetExtendedMessage(Exception exception)
335        {
336            var result = new StringBuilder(exception.Message.Length + 200);
337            GetExtendedMessage(exception, result);
338            return result.ToString().Trim();
339        }
340
341        private static void GetExtendedMessage(Exception exception, StringBuilder buffer)
342        {
343            buffer.AppendLine(exception.Message);
344            if (exception.InnerException != null)
345            {
346                GetExtendedMessage(exception.InnerException, buffer);
347            }
348        }
349
350        private static bool IsOperationCancelled(this Exception ex)
351        {
352            return (ex is OperationCanceledException
353                    || ex is ThreadAbortException
354                   );
355        }
356
357        private static bool IsCritical(this Exception ex)
358        {
359            if (ex is OutOfMemoryException) return true;
360            if (ex is AppDomainUnloadedException) return true;
361            if (ex is BadImageFormatException) return true;
362            if (ex is CannotUnloadAppDomainException) return true;
363            if (ex is InvalidProgramException) return true;
364            return ex is ThreadAbortException;
365        } 
366    }
367
368    /// <summary>

369    /// A single 'retry' instance for a retry policy

370    /// </summary>

371    public struct RetryAttempt
372    {
373        private readonly TimeSpan _timeSpan;
374
375        /// <summary>

376        /// Default constructor with injected <see cref="Attempt"/>

377        /// and how many milliseconds the next retry will be in.

378        /// </summary>

379        /// <param name="attempt">The number of attempts so far (including this attempt)</param>

380        /// <param name="lastAttempt">A flat indicating that this will the last attempt</param>

381        /// <param name="nextRetryInMilliseconds"></param>

382        public RetryAttempt(int attempt, bool lastAttempt, int nextRetryInMilliseconds)
383            : this()
384        {
385            _timeSpan = TimeSpan.FromMilliseconds(nextRetryInMilliseconds);
386            LastAttempt = lastAttempt;
387            Attempt = attempt;
388        }
389
390        /// <summary>

391        /// The attempt number (i.e. first, second, etc)

392        /// </summary>

393        public int Attempt { get; private set; }
394
395        /// <summary>

396        /// Flag to indicate if this will be the last attempt.

397        /// </summary>

398        public bool LastAttempt { get; private set; }
399
400        /// <summary>

401        /// When the next attempt will be tried.

402        /// </summary>

403        public TimeSpan NextRetryIn
404        {
405            get { return _timeSpan; }
406        }
407    }
408
409    /// <summary>

410    /// A interface describing a retry on a supplied action.

411    /// </summary>

412    /// <remarks>

413    /// Useful for database connecting to database or so other

414    /// transient connection. 

415    /// </remarks>

416    public interface IRetryPolicy
417    {
418        /// <summary>

419        /// Event indicating retry attempts have been exceeded the maximum allowed.

420        /// </summary>

421        event EventHandler RetryExceeded;
422
423        /// <summary>

424        /// Retry a supplied <paramref name="action"/>

425        /// </summary>

426        /// <param name="action">

427        /// The action to retry.

428        /// </param>

429        /// <returns>

430        /// true if the <paramref name="action"/> completed successfully;

431        /// otherwise, false.

432        /// </returns>

433        /// <remarks>

434        /// The parameter action should return true (successful) if no more attempts

435        /// are needed, otherwise false.

436        /// </remarks>

437        bool Retry(Func<RetryAttempt, bool> action);
438    }
439
440    /// <summary>

441    /// A retry policy with a maximum number of times retries, with a fixed interlude between attempts.

442    /// </summary>

443    public class FixedRetryPolicy : IRetryPolicy
444    {
445        private readonly int _mumberOfRetries;
446        private readonly int _delayInMillisecondsBetweenRetries;
447        private int _retryAttempt;
448
449        /// <summary>

450        /// Default constructor with injected number-of-retries and

451        /// and how many milliseconds delay there will be between each attempt.

452        /// </summary>

453        /// <param name="mumberOfRetries">

454        /// The maximum number of retry attempts (default 3).

455        /// </param>

456        /// <param name="delayInMillisecondsBetweenRetries">

457        /// The delay in milliseconds between each attempt (default 200).

458        /// </param>

459        public FixedRetryPolicy(int mumberOfRetries = 3, int delayInMillisecondsBetweenRetries = 200)
460        {
461            _mumberOfRetries = mumberOfRetries;
462            _delayInMillisecondsBetweenRetries = delayInMillisecondsBetweenRetries;
463            _retryAttempt = 0;
464        }
465
466        /// <summary>

467        /// Event indicating retry attempts have been exceeded the maximum allowed.

468        /// </summary>

469        public event EventHandler RetryExceeded;
470
471        /// <summary>

472        /// Retry an <paramref name="action"/> based on the policy.

473        /// </summary>

474        /// <param name="action">The action to retry.</param>

475        /// <returns>

476        /// true if the <paramref name="action"/> completed successfully;

477        /// otherwise, false.

478        /// </returns>

479        /// <remarks>

480        /// The parameter action should return true (successful) if no more attempts

481        /// are needed, otherwise false.

482        /// <para>

483        /// If the <paramref name="action"/> throws a <see cref="Exception"/>

484        /// then the <em>Retry</em> policy will be aborted. Throwing an 

485        /// <em>Exception</em> allows the <paramref name="action"/>

486        /// to propogate an error up the calling chain. In this situation the 

487        /// <see cref="RetryExceeded"/> event will not be invoked.

488        /// </para> <para>

489        /// The retry policy does not emit any logging it is expected

490        /// that the <paramref name="action"/> will handle all logging.

491        /// </para>

492        /// </remarks>

493        public bool Retry(Func<RetryAttempt, bool> action)
494        {
495            while (_retryAttempt < _mumberOfRetries)
496            {
497                if (action(new RetryAttempt(_retryAttempt, _retryAttempt == _mumberOfRetries - 1, _delayInMillisecondsBetweenRetries))) return true;
498                _retryAttempt++;
499                if (_retryAttempt < _mumberOfRetries)
500                    Thread.Sleep(_delayInMillisecondsBetweenRetries);
501            }
502            try
503            {
504                if (RetryExceeded != null) RetryExceeded(this, EventArgs.Empty);
505            }
506            catch
507            { }
508            return false;
509        }
510    }
511
512    /// <summary>

513    /// A retry policy with an exponential back-off. 

514    /// </summary>

515    public class RetryPolicyExpotentialBackoff : IRetryPolicy
516    {
517        private readonly int _maxAttempts;
518        private readonly int _power;
519        private readonly int _delayInMilliseconds;
520        private readonly int _maxDelayInMilliseconds;
521
522        /// <summary>

523        /// A constructor specifying maximum number of retry attempts and the 

524        /// maximum delay allowed.

525        /// </summary>

526        /// <param name="maxAttempts">The maximum number of retry attempts (default 10)</param>

527        /// <param name="maxDelayInSeconds">

528        /// The maximum permitted delay between retries in seconds (default 10).

529        /// </param>

530        public RetryPolicyExpotentialBackoff(int maxAttempts = 10, int maxDelayInSeconds = 10)
531            : this(maxAttempts, 3, 500, maxDelayInSeconds)
532        { }
533
534        /// <summary>

535        /// A constructor specify maximum number of retry attempts, the exponential logOnModulo

536        /// and the maximum delay allowed.

537        /// </summary>

538        /// <param name="maxAttempts">The maximum number of retry attempts (default 10)</param>

539        /// <param name="power">The exponential factor (default = 3)</param>

540        /// <param name="initialDelayInMilliseconds">

541        /// The initial delay in milliseconds (default 100)

542        /// </param>

543        /// <param name="maxDelayInSeconds">

544        /// The maximum permitted delay between retries in seconds (default 10).

545        /// </param>

546        public RetryPolicyExpotentialBackoff(int maxAttempts = 10, int power = 3, int initialDelayInMilliseconds = 100, int maxDelayInSeconds = 10)
547        {
548            _maxAttempts = maxAttempts;
549            _power = power;
550            _delayInMilliseconds = initialDelayInMilliseconds;
551            _maxDelayInMilliseconds = maxDelayInSeconds * 1000;
552        }
553
554        /// <summary>

555        /// Event indicating retry attempts have been exceeded the maximum allowed.

556        /// </summary>

557        public event EventHandler RetryExceeded;
558
559        /// <summary>

560        /// Retry a <paramref name="action"/> based on the policy.

561        /// </summary>

562        /// <param name="action">The action to retry.</param>

563        /// <returns>

564        /// true if the <paramref name="action"/> completed successfully;

565        /// otherwise, false.

566        /// </returns>

567        /// <remarks>

568        /// The parameter action should return true (successful) if no more attempts

569        /// are needed, otherwise false.

570        /// <para>

571        /// If the <paramref name="action"/> throws a <see cref="Exception"/>

572        /// then the <em>Retry</em> policy will be aborted. Throwing an 

573        /// <em>Exception</em> allows the <paramref name="action"/>

574        /// to propagate an error up the calling chain. In this situation the 

575        /// <see cref="RetryExceeded"/> event will not be invoked.

576        /// </para>

577        /// The retry policy does not emit any logging it is expected

578        /// that the <paramref name="action"/> will handle all logging.

579        /// </remarks>

580        public bool Retry(Func<RetryAttempt, bool> action)
581        {
582            var attempt = 0;
583            while (attempt < _maxAttempts)
584            {
585                if (attempt > 0)
586                {
587                    var millisecondsTimeout = GetDelay(attempt);
588                    Thread.Sleep(millisecondsTimeout);
589                }
590                if (action(new RetryAttempt(attempt, attempt == _maxAttempts - 1, GetDelay(attempt + 1)))) return true;
591
592                attempt++;
593            }
594            try
595            {
596                if (RetryExceeded != null) RetryExceeded(this, EventArgs.Empty);
597            }
598            catch
599            { }
600            return false;
601        }
602
603        private int GetDelay(int attempts)
604        {
605            return Math.Min(_maxDelayInMilliseconds, (int)Math.Pow(_power, attempts) * _delayInMilliseconds);
606        }
607    }
608
609    /// <summary>

610    /// Wraps a simple action (<see cref="RetryAction"/> or <see cref="Func{boo}"/>)

611    /// allowing it to partake in a <see cref="IRetryPolicy"/>.

612    /// </summary>

613    /// <remarks>

614    /// <example>

615    /// Example:

616    /// <code>

617    /// var policy  = new SimpleRetryPolicy(3, 500);

618    /// policy.Retry(RetryActionAdapter.Action(beingGood, "Connect to Heaven"));

619    /// </code>

620    /// </example>

621    /// </remarks>

622    public class RetryActionAdapter
623    {
624        private readonly Action _action;
625        private readonly Func<bool> _func;
626        private readonly string _name;
627
628        /// <summary>

629        /// Allow a <see cref="RetryAction"/> method to partake in a <see cref="IRetryPolicy"/>

630        /// </summary>

631        /// <param name="action">The action to adapt.</param>

632        /// <param name="name">The action name used for logging purposes.</param>

633        /// <returns>A action that can be called by <see cref="IRetryPolicy.Retry"/></returns>

634        /// <remarks>

635        /// <example>

636        /// Example:

637        /// <code>

638        /// var policy  = new SimpleRetryPolicy(3, 500);

639        /// policy.Retry(RetryActionAdapter.Action(beingGood, "Connect to Heaven"));

640        /// </code>

641        /// </example>

642        /// </remarks>

643        public static Func<RetryAttempt, bool> Action(Action action, string name = null)
644        {
645            return (new RetryActionAdapter(action)).RetryAction;
646        }
647
648        /// <summary>

649        /// Allow a <see cref="Func{boo}"/> function to partake in a <see cref="IRetryPolicy"/>

650        /// </summary>

651        /// <param name="action">The action to adapt.</param>

652        /// <param name="name">The action name used for logging purposes (optional)</param>

653        /// <returns>A action that can be called by <see cref="IRetryPolicy.Retry"/></returns>

654        /// <remarks>

655        /// <example>

656        /// Example:

657        /// <code>

658        /// var policy = new SimpleRetryPolicy(3, 500);

659        /// policy.Retry(RetryActionAdapter.Action(beingGood, "Connect to Heaven"));

660        /// </code>

661        /// </example>

662        /// </remarks>

663        public static Func<RetryAttempt, bool> Action(Func<bool> action, string name = null)
664        {
665            return (new RetryActionAdapter(action)).RetryAction;
666        }
667
668        /// <summary>

669        /// Constructor with an injected <see cref="RetryAction"/> to retry.

670        /// </summary>

671        /// <param name="action">The action.</param>

672        /// <param name="name">A logging name</param>

673        /// <remarks>

674        /// If no logging name is provided then <em>RetryActionAdapter</em>

675        /// will not emit any trace information.

676        /// </remarks>

677        public RetryActionAdapter(Action action, string name = null)
678        {
679            if (action == null)
680                throw new ArgumentNullException("action", "The parameter 'action' cannot be null!");
681
682            _action = action;
683            _name = name;
684        }
685
686        /// <summary>

687        /// Constructor with an injected <see cref="Func{boo}"/> to retry.

688        /// </summary>

689        /// <param name="action">The action.</param>

690        /// <param name="name">A logging name</param>

691        /// <remarks>

692        /// If the <paramref name="action"/> should return true to indicate

693        /// success and that no more retry attempts should be made.

694        /// <para>

695        /// If no logging name is provided then <em>RetryActionAdapter</em>

696        /// will not emit any trace information.

697        /// </para>

698        /// </remarks>

699        public RetryActionAdapter(Func<bool> action, string name = null)
700        {
701            if (action == null)
702                throw new ArgumentNullException("action", "The parameter 'action' cannot be null!");
703
704            _func = action;
705            _name = name;
706        }
707
708        /// <summary>

709        /// The action passed to a <see cref="IRetryPolicy"/>

710        /// <see cref="IRetryPolicy.Retry"/> method.

711        /// </summary>

712        /// <remarks>

713        /// If retry atom is a <see cref="System.Action"/> then the action

714        /// should indicate failure by throwing an exception.

715        /// <para>

716        /// If the atom is a <see cref="System.Func{out bool}"/> then the action

717        /// should indicate success by return true; otherwise, false

718        /// </para>

719        /// <example>

720        /// Example:

721        /// <code>

722        /// var adapter = new RetryActionAdapter(beingGood, "Connect to Heaven");

723        /// var policy  = new SimpleRetryPolicy(3, 500);

724        /// policy.Retry(adapter.RetryAction);

725        /// </code>

726        /// </example>

727        /// </remarks>

728        public Func<RetryAttempt, bool> RetryAction
729        {
730            get { return InternalRetry; }
731        }
732
733        private bool InternalRetry(RetryAttempt attempt)
734        {
735            try
736            {
737                if (_func != null)
738                {
739                    var result = _func();
740                    if (!result)
741                        Retry.LogRetryAttempt(attempt, _name);
742                    return result;
743                }
744                _action();
745                return true; // no error so we can stop trying

746            }
747            catch (Exception ex)
748            {
749                // we allow func's to exit by throwing an error, which will cascade up the call chain!

750                // don't retry critical or abort exceptions.

751                if (_func != null || IsCritical(ex) || IsOperationCancelled(ex)) throw; 
752
753                // actions can only let us know they have failed by throwing an error so handle as 'unsuccessful'

754                Retry.LogRetryAttempt(attempt, _name, ex);
755                
756                if (attempt.LastAttempt)
757                    throw; // propagate the error back up the chain

758
759                return false; // keep trying

760            }
761        }
762
763
764        private static bool IsOperationCancelled(Exception ex)
765        {
766            return (ex is OperationCanceledException
767                    || ex is ThreadAbortException
768                   );
769        }
770
771        private static bool IsCritical(Exception ex)
772        {
773            if (ex is OutOfMemoryException) return true;
774            if (ex is AppDomainUnloadedException) return true;
775            if (ex is BadImageFormatException) return true;
776            if (ex is CannotUnloadAppDomainException) return true;
777            if (ex is InvalidProgramException) return true;
778            return ex is ThreadAbortException;
779        } 
780    }
781}