Quick.FaultControl.pas 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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 : 18/08/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. public
  57. constructor Create;
  58. destructor Destroy; override;
  59. property MaxRetries : Integer read fMaxRetries write fMaxRetries;
  60. property WaitTimeBetweenRetriesMS : Integer read fWaitTimeBetweenRetries write fWaitTimeBetweenRetries;
  61. property WaitTimeMultiplierFactor : Double read fWaitTimeMultiplierFactor write fWaitTimeMultiplierFactor;
  62. property WaitTimeMSArray : TArray<Integer> read fWaitTimeArray write fWaitTimeArray;
  63. property OnRetry : TRetryEvent read fOnRetry write fOnRetry;
  64. property OnCircuitBreak : TCircuitBreakEvent read fOnCircuitBreak write fOnCircuitBreak;
  65. property TaskFailed : Boolean read fTaskFailed;
  66. property CircuitBreaked : Boolean read fCircuitBreaked;
  67. property LastException : Exception read fLastException;
  68. property NumRetries : Integer read fNumRetries;
  69. procedure FailedExecution(aException : Exception);
  70. procedure SuccessExecution;
  71. procedure Reset;
  72. function NeedToRetry : Boolean;
  73. end;
  74. implementation
  75. { TFaultControl }
  76. constructor TFaultControl.Create;
  77. begin
  78. fTaskFailed := False;
  79. fLastException := nil;
  80. fOnRetry := nil;
  81. fOnCircuitBreak := nil;
  82. fCircuitBreaked := False;
  83. fNumRetries := 0;
  84. fMaxRetries := 0;
  85. fWaitTimeBetweenRetries := 0;
  86. fWaitTimeMultiplierFactor := 1.0;
  87. fWaitTimeArray := [];
  88. end;
  89. function TFaultControl.NeedToRetry: Boolean;
  90. var
  91. waitretryMS : Integer;
  92. begin
  93. Result := False;
  94. if fTaskFailed then
  95. begin
  96. if (fMaxRetries <> 0) and (not fCircuitBreaked) then
  97. begin
  98. if (fNumRetries < fMaxRetries) or (fMaxRetries = -1) then
  99. begin
  100. Inc(fNumRetries);
  101. if Assigned(fOnRetry) then
  102. begin
  103. //can cancel next retries
  104. fOnRetry(fLastException,fCircuitBreaked);
  105. //if cancelled next retries, decrease current retries
  106. if fCircuitBreaked then fNumRetries := fNumRetries - 1;
  107. Result := not fCircuitBreaked;
  108. end
  109. else Result := True;
  110. //wait between retries
  111. if (Result) and (fMaxRetries <> 0) then
  112. begin
  113. if IsEmptyArray(fWaitTimeArray) then waitretryMS := fWaitTimeBetweenRetries * Round(fNumRetries * fWaitTimeMultiplierFactor)
  114. else waitretryMS := fWaitTimeArray[fNumRetries - 1];
  115. if waitretryMS > 0 then WaitBeforeRetry(waitretryMS);
  116. end;
  117. end
  118. else fCircuitBreaked := True;
  119. if fCircuitBreaked then
  120. begin
  121. if Assigned(fOnCircuitBreak) then fOnCircuitBreak;
  122. if Assigned(fLastException) then raise fLastException;
  123. end;
  124. end;
  125. end;
  126. end;
  127. procedure TFaultControl.Reset;
  128. begin
  129. fCircuitBreaked := False;
  130. fTaskFailed := False;
  131. fNumRetries := 0;
  132. fLastException := nil;
  133. end;
  134. procedure TFaultControl.SuccessExecution;
  135. begin
  136. fTaskFailed := False;
  137. end;
  138. procedure TFaultControl.WaitBeforeRetry(aNumWaitMilliseconds: Integer);
  139. var
  140. fEvent : TSimpleEvent;
  141. begin
  142. //Sleep(aNumWaitMilliseconds);
  143. {$IFDEF FPC}
  144. fEvent := TSimpleEvent.Create;
  145. {$ELSE}
  146. fEvent := TSimpleEvent.Create(nil,True,False,'');
  147. {$ENDIF}
  148. try
  149. fEvent.WaitFor(aNumWaitMilliseconds);
  150. finally
  151. fEvent.SetEvent;
  152. fEvent.Free;
  153. end;
  154. end;
  155. destructor TFaultControl.Destroy;
  156. begin
  157. //if Assigned(fLastException) then fLastException.Free;
  158. inherited;
  159. end;
  160. procedure TFaultControl.FailedExecution(aException: Exception);
  161. begin
  162. fTaskFailed := True;
  163. //free older exception
  164. if Assigned(fLastException) then fLastException.Free;
  165. fLastException := aException;
  166. if fMaxRetries = 0 then raise aException
  167. else if fNumRetries = fMaxRetries then
  168. begin
  169. aException.Message := Format('Max %d retries reached: %s',[fMaxRetries,aException.Message]);
  170. raise fLastException;
  171. end;
  172. end;
  173. { TFaultPolicy }
  174. constructor TFaultPolicy.Create;
  175. begin
  176. fMaxRetries := 0;
  177. fWaitTimeBetweenRetries := 0;
  178. fWaitTimeMultiplierFactor := 1;
  179. end;
  180. end.