RetryableServiceClient.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. /*
  2. * Copyright (C) Alibaba Cloud Computing
  3. * All rights reserved.
  4. *
  5. * 版权所有 (C)阿里云计算有限公司
  6. */
  7. using System;
  8. using System.Net;
  9. using System.Threading;
  10. using System.IO;
  11. using Aliyun.OSS.Util;
  12. namespace Aliyun.OSS.Common.Communication
  13. {
  14. /// <summary>
  15. /// Implementation of <see cref="IServiceClient"/> that will auto-retry HTTP requests
  16. /// when encountering some specific exceptions or failures.
  17. /// </summary>
  18. internal class RetryableServiceClient : IServiceClient
  19. {
  20. #region Fields & Properties
  21. private const int DefaultMaxRetryTimes = 3;
  22. private const int DefaultRetryPauseScale = 300; // milliseconds.
  23. private readonly IServiceClient _innerClient;
  24. /// <summary>
  25. /// 为了兼容.NET2.0,定义了OssFunc,等价于.NET4.0中的 System.Func.
  26. /// </summary>
  27. public delegate TResult OssFunc<T, TResult>(T t);
  28. public OssFunc<Exception, bool> ShouldRetryCallback { get; set; }
  29. public int MaxRetryTimes { get; set; }
  30. #endregion
  31. #region Constructors
  32. public RetryableServiceClient(IServiceClient innerClient)
  33. {
  34. _innerClient = innerClient;
  35. MaxRetryTimes = DefaultMaxRetryTimes;
  36. }
  37. #endregion
  38. #region IServiceClient Members
  39. internal IServiceClient InnerServiceClient()
  40. {
  41. return _innerClient;
  42. }
  43. public ServiceResponse Send(ServiceRequest request, ExecutionContext context)
  44. {
  45. return SendImpl(request, context, 0);
  46. }
  47. private ServiceResponse SendImpl(ServiceRequest request, ExecutionContext context, int retryTimes)
  48. {
  49. long originalContentPosition = -1;
  50. try
  51. {
  52. if (request.Content != null && request.Content.CanSeek)
  53. originalContentPosition = request.Content.Position;
  54. return _innerClient.Send(request, context);
  55. }
  56. catch (Exception ex)
  57. {
  58. if (ShouldRetry(request, ex, retryTimes))
  59. {
  60. if (request.Content != null && (originalContentPosition >= 0 && request.Content.CanSeek))
  61. request.Content.Seek(originalContentPosition, SeekOrigin.Begin);
  62. Pause(retryTimes);
  63. return SendImpl(request, context, ++retryTimes);
  64. }
  65. // Rethrow
  66. throw;
  67. }
  68. }
  69. public IAsyncResult BeginSend(ServiceRequest request, ExecutionContext context,
  70. AsyncCallback callback, object state)
  71. {
  72. var asyncResult = new RetryableAsyncResult(callback, state, request, context);
  73. BeginSendImpl(request, context, asyncResult);
  74. return asyncResult;
  75. }
  76. private void BeginSendImpl(ServiceRequest request, ExecutionContext context,
  77. RetryableAsyncResult asyncResult)
  78. {
  79. if (asyncResult.InnerAsyncResult != null)
  80. asyncResult.InnerAsyncResult.Dispose();
  81. asyncResult.InnerAsyncResult =
  82. _innerClient.BeginSend(request, context, asyncResult.Callback, asyncResult) as AsyncResult;
  83. }
  84. public ServiceResponse EndSend(IAsyncResult ar)
  85. {
  86. if (ar == null)
  87. throw new ArgumentNullException("ar");
  88. var asyncResult = ar as AsyncResult<ServiceResponse>;
  89. RetryableAsyncResult retryableAsyncResult = ar.AsyncState as RetryableAsyncResult;
  90. if (asyncResult == null || retryableAsyncResult == null)
  91. throw new InvalidOperationException("Invalid asynchronous invocation status.");
  92. try
  93. {
  94. var response = asyncResult.GetResult();
  95. return response;
  96. }
  97. catch (Exception ex)
  98. {
  99. if (retryableAsyncResult.OriginalContentPosition >= 0)
  100. {
  101. retryableAsyncResult.Request.Content.Seek(retryableAsyncResult.OriginalContentPosition,
  102. SeekOrigin.Begin);
  103. }
  104. if (ShouldRetry(retryableAsyncResult.Request, ex, retryableAsyncResult.Retries))
  105. {
  106. Pause(retryableAsyncResult.Retries++);
  107. BeginSendImpl(retryableAsyncResult.Request, retryableAsyncResult.Context, retryableAsyncResult);
  108. }
  109. // Rethrow
  110. throw;
  111. }
  112. finally
  113. {
  114. asyncResult.Dispose();
  115. }
  116. }
  117. private bool ShouldRetry(ServiceRequest request, Exception ex, int retryTimes)
  118. {
  119. if (retryTimes > MaxRetryTimes || !request.IsRepeatable)
  120. return false;
  121. var webException = ex as WebException;
  122. if (webException != null)
  123. {
  124. var httpWebResponse = webException.Response as HttpWebResponse;
  125. if (httpWebResponse != null &&
  126. (httpWebResponse.StatusCode == HttpStatusCode.ServiceUnavailable ||
  127. httpWebResponse.StatusCode == HttpStatusCode.InternalServerError))
  128. {
  129. return true;
  130. }
  131. }
  132. if (ShouldRetryCallback != null && ShouldRetryCallback(ex))
  133. return true;
  134. return false;
  135. }
  136. private static void Pause(int retryTimes)
  137. {
  138. // make the pause time increase exponentially based on an assumption
  139. // that the more times it retries, the less probability it succeeds.
  140. var delay = (int)Math.Pow(2, retryTimes) * DefaultRetryPauseScale;
  141. Thread.Sleep(delay);
  142. }
  143. #endregion
  144. }
  145. internal class RetryableAsyncResult : AsyncResult<ServiceResponse>
  146. {
  147. public ServiceRequest Request { get; private set; }
  148. public ExecutionContext Context { get; private set; }
  149. public AsyncResult InnerAsyncResult { get; set; }
  150. public int Retries { get; set; }
  151. public long OriginalContentPosition { get; private set; }
  152. public RetryableAsyncResult(AsyncCallback callback, object state,
  153. ServiceRequest request, ExecutionContext context)
  154. : base(callback, state)
  155. {
  156. Request = request;
  157. Context = context;
  158. OriginalContentPosition = (request.Content != null && request.Content.CanSeek)
  159. ? request.Content.Position : -1;
  160. }
  161. protected override void Dispose(bool disposing)
  162. {
  163. base.Dispose(disposing);
  164. if (disposing && InnerAsyncResult != null)
  165. {
  166. InnerAsyncResult.Dispose();
  167. InnerAsyncResult = null;
  168. }
  169. }
  170. }
  171. }