|
|
|
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}
|
|
|