2
0

Quick.FaultControl.pas 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. { ***************************************************************************
  2. Copyright (c) 2016-2019 Kike Pérez
  3. Unit : Quick.FaultControl
  4. Description : Thread workers retry and circuit break
  5. Author : Kike Pérez
  6. Version : 1.5
  7. Created : 20/06/2019
  8. Modified : 02/12/2019
  9. This file is part of QuickLib: https://github.com/exilon/QuickLib
  10. ***************************************************************************
  11. Licensed under the Apache License, Version 2.0 (the "License");
  12. you may not use this file except in compliance with the License.
  13. You may obtain a copy of the License at
  14. http://www.apache.org/licenses/LICENSE-2.0
  15. Unless required by applicable law or agreed to in writing, software
  16. distributed under the License is distributed on an "AS IS" BASIS,
  17. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. See the License for the specific language governing permissions and
  19. limitations under the License.
  20. *************************************************************************** }
  21. unit Quick.FaultControl;
  22. {$i QuickLib.inc}
  23. interface
  24. uses
  25. SysUtils,
  26. SyncObjs,
  27. Quick.Commons;
  28. type
  29. TRetryEvent = procedure(aRaisedException : Exception; var vStopRetries : Boolean) of object;
  30. TMaxRetriesEvent = procedure of object;
  31. TCircuitBreakEvent = procedure of object;
  32. TFaultPolicy = class
  33. private
  34. fMaxRetries : Integer;
  35. fWaitTimeBetweenRetries : Integer;
  36. fWaitTimeMultiplierFactor : Double;
  37. public
  38. constructor Create;
  39. property MaxRetries : Integer read fMaxRetries write fMaxRetries;
  40. property WaitTimeBetweenRetries : Integer read fWaitTimeBetweenRetries write fWaitTimeBetweenRetries;
  41. property WaitTimeMultiplierFactor : Double read fWaitTimeMultiplierFactor write fWaitTimeMultiplierFactor;
  42. end;
  43. TFaultControl = class
  44. private
  45. fMaxRetries : Integer;
  46. fWaitTimeBetweenRetries : Integer;
  47. fWaitTimeMultiplierFactor : Double;
  48. fWaitTimeArray : TArray<Integer>;
  49. fNumRetries : Integer;
  50. fLastException : Exception;
  51. fCircuitBreaked : Boolean;
  52. fOnRetry : TRetryEvent;
  53. fOnCircuitBreak : TCircuitBreakEvent;
  54. fTaskFailed : Boolean;
  55. procedure WaitBeforeRetry(aNumWaitMilliseconds : Integer);
  56. procedure SetWaitTimeMultiplierFactor(const Value: Double);
  57. public
  58. constructor Create;
  59. destructor Destroy; override;
  60. property MaxRetries : Integer read fMaxRetries write fMaxRetries;
  61. property WaitTimeBetweenRetriesMS : Integer read fWaitTimeBetweenRetries write fWaitTimeBetweenRetries;
  62. property WaitTimeMultiplierFactor : Double read fWaitTimeMultiplierFactor write SetWaitTimeMultiplierFactor;
  63. property WaitTimeMSArray : TArray<Integer> read fWaitTimeArray write fWaitTimeArray;
  64. property OnRetry : TRetryEvent read fOnRetry write fOnRetry;
  65. property OnCircuitBreak : TCircuitBreakEvent read fOnCircuitBreak write fOnCircuitBreak;
  66. property TaskFailed : Boolean read fTaskFailed;
  67. property CircuitBreaked : Boolean read fCircuitBreaked;
  68. property LastException : Exception read fLastException;
  69. property NumRetries : Integer read fNumRetries;
  70. procedure FailedExecution(aException : Exception);
  71. procedure SuccessExecution;
  72. procedure Reset;
  73. function NeedToRetry : Boolean;
  74. end;
  75. EFaultControlConfigError = class(Exception);
  76. implementation
  77. { TFaultControl }
  78. constructor TFaultControl.Create;
  79. begin
  80. fTaskFailed := False;
  81. fLastException := nil;
  82. fOnRetry := nil;
  83. fOnCircuitBreak := nil;
  84. fCircuitBreaked := False;
  85. fNumRetries := 0;
  86. fMaxRetries := 0;
  87. fWaitTimeBetweenRetries := 0;
  88. fWaitTimeMultiplierFactor := 1;
  89. fWaitTimeArray := [];
  90. end;
  91. function TFaultControl.NeedToRetry: Boolean;
  92. var
  93. waitretryMS : Integer;
  94. begin
  95. Result := False;
  96. if fTaskFailed then
  97. begin
  98. if (fMaxRetries <> 0) and (not fCircuitBreaked) then
  99. begin
  100. if (fNumRetries < fMaxRetries) or (fMaxRetries = -1) then
  101. begin
  102. Inc(fNumRetries);
  103. if Assigned(fOnRetry) then
  104. begin
  105. //can cancel next retries
  106. fOnRetry(fLastException,fCircuitBreaked);
  107. //if cancelled next retries, decrease current retries
  108. if fCircuitBreaked then fNumRetries := fNumRetries - 1;
  109. Result := not fCircuitBreaked;
  110. end
  111. else Result := True;
  112. //wait between retries
  113. if (Result) and (fMaxRetries <> 0) then
  114. begin
  115. if IsEmptyArray(fWaitTimeArray) then
  116. begin
  117. if fWaitTimeMultiplierFactor = 1 then waitretryMS := fWaitTimeBetweenRetries
  118. else waitretryMS := fWaitTimeBetweenRetries * Round(fNumRetries * fWaitTimeMultiplierFactor);
  119. end
  120. else waitretryMS := fWaitTimeArray[fNumRetries - 1];
  121. if waitretryMS > 0 then WaitBeforeRetry(waitretryMS);
  122. end;
  123. end
  124. else fCircuitBreaked := True;
  125. if fCircuitBreaked then
  126. begin
  127. if Assigned(fOnCircuitBreak) then fOnCircuitBreak;
  128. if Assigned(fLastException) then raise fLastException;
  129. end;
  130. end;
  131. end;
  132. end;
  133. procedure TFaultControl.Reset;
  134. begin
  135. fCircuitBreaked := False;
  136. fTaskFailed := False;
  137. fNumRetries := 0;
  138. fLastException := nil;
  139. end;
  140. procedure TFaultControl.SetWaitTimeMultiplierFactor(const Value: Double);
  141. begin
  142. if Value = 0 then raise EFaultControlConfigError.Create('WaitTimeMultiplierFactor cannot be 0')
  143. else fWaitTimeMultiplierFactor := Value;
  144. end;
  145. procedure TFaultControl.SuccessExecution;
  146. begin
  147. fTaskFailed := False;
  148. end;
  149. procedure TFaultControl.WaitBeforeRetry(aNumWaitMilliseconds: Integer);
  150. var
  151. fEvent : TSimpleEvent;
  152. begin
  153. //Sleep(aNumWaitMilliseconds);
  154. {$IFDEF FPC}
  155. fEvent := TSimpleEvent.Create;
  156. {$ELSE}
  157. fEvent := TSimpleEvent.Create(nil,True,False,'');
  158. {$ENDIF}
  159. try
  160. fEvent.WaitFor(aNumWaitMilliseconds);
  161. finally
  162. fEvent.SetEvent;
  163. fEvent.Free;
  164. end;
  165. end;
  166. destructor TFaultControl.Destroy;
  167. begin
  168. //if Assigned(fLastException) then fLastException.Free;
  169. inherited;
  170. end;
  171. procedure TFaultControl.FailedExecution(aException: Exception);
  172. begin
  173. fTaskFailed := True;
  174. //free older exception
  175. if Assigned(fLastException) then fLastException.Free;
  176. fLastException := aException;
  177. if fMaxRetries = 0 then raise aException
  178. else if fNumRetries = fMaxRetries then
  179. begin
  180. aException.Message := Format('Max %d retries reached: %s',[fMaxRetries,aException.Message]);
  181. raise fLastException;
  182. end;
  183. end;
  184. { TFaultPolicy }
  185. constructor TFaultPolicy.Create;
  186. begin
  187. fMaxRetries := 0;
  188. fWaitTimeBetweenRetries := 0;
  189. fWaitTimeMultiplierFactor := 1;
  190. end;
  191. end.