2
0

Quick.FaultControl.pas 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  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. {$IFDEF DELPHIXE7_UP}
  90. fWaitTimeArray := [];
  91. {$ELSE}
  92. fWaitTimeArray := nil;
  93. {$ENDIF}
  94. end;
  95. function TFaultControl.NeedToRetry: Boolean;
  96. var
  97. waitretryMS : Integer;
  98. begin
  99. Result := False;
  100. if fTaskFailed then
  101. begin
  102. if (fMaxRetries <> 0) and (not fCircuitBreaked) then
  103. begin
  104. if (fNumRetries < fMaxRetries) or (fMaxRetries = -1) then
  105. begin
  106. Inc(fNumRetries);
  107. if Assigned(fOnRetry) then
  108. begin
  109. //can cancel next retries
  110. fOnRetry(fLastException,fCircuitBreaked);
  111. //if cancelled next retries, decrease current retries
  112. if fCircuitBreaked then fNumRetries := fNumRetries - 1;
  113. Result := not fCircuitBreaked;
  114. end
  115. else Result := True;
  116. //wait between retries
  117. if (Result) and (fMaxRetries <> 0) then
  118. begin
  119. if IsEmptyArray(fWaitTimeArray) then
  120. begin
  121. if fWaitTimeMultiplierFactor = 1 then waitretryMS := fWaitTimeBetweenRetries
  122. else waitretryMS := fWaitTimeBetweenRetries * Round(fNumRetries * fWaitTimeMultiplierFactor);
  123. end
  124. else waitretryMS := fWaitTimeArray[fNumRetries - 1];
  125. if waitretryMS > 0 then WaitBeforeRetry(waitretryMS);
  126. end;
  127. end
  128. else fCircuitBreaked := True;
  129. if fCircuitBreaked then
  130. begin
  131. if Assigned(fOnCircuitBreak) then fOnCircuitBreak;
  132. if Assigned(fLastException) then raise fLastException;
  133. end;
  134. end;
  135. end;
  136. end;
  137. procedure TFaultControl.Reset;
  138. begin
  139. fCircuitBreaked := False;
  140. fTaskFailed := False;
  141. fNumRetries := 0;
  142. fLastException := nil;
  143. end;
  144. procedure TFaultControl.SetWaitTimeMultiplierFactor(const Value: Double);
  145. begin
  146. if Value = 0 then raise EFaultControlConfigError.Create('WaitTimeMultiplierFactor cannot be 0')
  147. else fWaitTimeMultiplierFactor := Value;
  148. end;
  149. procedure TFaultControl.SuccessExecution;
  150. begin
  151. fTaskFailed := False;
  152. end;
  153. procedure TFaultControl.WaitBeforeRetry(aNumWaitMilliseconds: Integer);
  154. var
  155. fEvent : TSimpleEvent;
  156. begin
  157. //Sleep(aNumWaitMilliseconds);
  158. {$IFDEF FPC}
  159. fEvent := TSimpleEvent.Create;
  160. {$ELSE}
  161. fEvent := TSimpleEvent.Create(nil,True,False,'');
  162. {$ENDIF}
  163. try
  164. fEvent.WaitFor(aNumWaitMilliseconds);
  165. finally
  166. fEvent.SetEvent;
  167. fEvent.Free;
  168. end;
  169. end;
  170. destructor TFaultControl.Destroy;
  171. begin
  172. //if Assigned(fLastException) then fLastException.Free;
  173. inherited;
  174. end;
  175. procedure TFaultControl.FailedExecution(aException: Exception);
  176. begin
  177. fTaskFailed := True;
  178. //free older exception
  179. if Assigned(fLastException) then fLastException.Free;
  180. fLastException := aException;
  181. if fMaxRetries = 0 then raise aException
  182. else if fNumRetries = fMaxRetries then
  183. begin
  184. aException.Message := Format('Max %d retries reached: %s',[fMaxRetries,aException.Message]);
  185. raise fLastException;
  186. end;
  187. end;
  188. { TFaultPolicy }
  189. constructor TFaultPolicy.Create;
  190. begin
  191. fMaxRetries := 0;
  192. fWaitTimeBetweenRetries := 0;
  193. fWaitTimeMultiplierFactor := 1;
  194. end;
  195. end.