123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- { ***************************************************************************
- Copyright (c) 2016-2019 Kike Pérez
- Unit : Quick.FaultControl
- Description : Thread workers retry and circuit break
- Author : Kike Pérez
- Version : 1.5
- Created : 20/06/2019
- Modified : 02/12/2019
- This file is part of QuickLib: https://github.com/exilon/QuickLib
- ***************************************************************************
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- *************************************************************************** }
- unit Quick.FaultControl;
- {$i QuickLib.inc}
- interface
- uses
- SysUtils,
- SyncObjs,
- Quick.Commons;
- type
- TRetryEvent = procedure(aRaisedException : Exception; var vStopRetries : Boolean) of object;
- TMaxRetriesEvent = procedure of object;
- TCircuitBreakEvent = procedure of object;
- TFaultPolicy = class
- private
- fMaxRetries : Integer;
- fWaitTimeBetweenRetries : Integer;
- fWaitTimeMultiplierFactor : Double;
- public
- constructor Create;
- property MaxRetries : Integer read fMaxRetries write fMaxRetries;
- property WaitTimeBetweenRetries : Integer read fWaitTimeBetweenRetries write fWaitTimeBetweenRetries;
- property WaitTimeMultiplierFactor : Double read fWaitTimeMultiplierFactor write fWaitTimeMultiplierFactor;
- end;
- TFaultControl = class
- private
- fMaxRetries : Integer;
- fWaitTimeBetweenRetries : Integer;
- fWaitTimeMultiplierFactor : Double;
- fWaitTimeArray : TArray<Integer>;
- fNumRetries : Integer;
- fLastException : Exception;
- fCircuitBreaked : Boolean;
- fOnRetry : TRetryEvent;
- fOnCircuitBreak : TCircuitBreakEvent;
- fTaskFailed : Boolean;
- procedure WaitBeforeRetry(aNumWaitMilliseconds : Integer);
- procedure SetWaitTimeMultiplierFactor(const Value: Double);
- public
- constructor Create;
- destructor Destroy; override;
- property MaxRetries : Integer read fMaxRetries write fMaxRetries;
- property WaitTimeBetweenRetriesMS : Integer read fWaitTimeBetweenRetries write fWaitTimeBetweenRetries;
- property WaitTimeMultiplierFactor : Double read fWaitTimeMultiplierFactor write SetWaitTimeMultiplierFactor;
- property WaitTimeMSArray : TArray<Integer> read fWaitTimeArray write fWaitTimeArray;
- property OnRetry : TRetryEvent read fOnRetry write fOnRetry;
- property OnCircuitBreak : TCircuitBreakEvent read fOnCircuitBreak write fOnCircuitBreak;
- property TaskFailed : Boolean read fTaskFailed;
- property CircuitBreaked : Boolean read fCircuitBreaked;
- property LastException : Exception read fLastException;
- property NumRetries : Integer read fNumRetries;
- procedure FailedExecution(aException : Exception);
- procedure SuccessExecution;
- procedure Reset;
- function NeedToRetry : Boolean;
- end;
- EFaultControlConfigError = class(Exception);
- implementation
- { TFaultControl }
- constructor TFaultControl.Create;
- begin
- fTaskFailed := False;
- fLastException := nil;
- fOnRetry := nil;
- fOnCircuitBreak := nil;
- fCircuitBreaked := False;
- fNumRetries := 0;
- fMaxRetries := 0;
- fWaitTimeBetweenRetries := 0;
- fWaitTimeMultiplierFactor := 1;
- {$IFDEF DELPHIXE7_UP}
- fWaitTimeArray := [];
- {$ELSE}
- fWaitTimeArray := nil;
- {$ENDIF}
- end;
- function TFaultControl.NeedToRetry: Boolean;
- var
- waitretryMS : Integer;
- begin
- Result := False;
- if fTaskFailed then
- begin
- if (fMaxRetries <> 0) and (not fCircuitBreaked) then
- begin
- if (fNumRetries < fMaxRetries) or (fMaxRetries = -1) then
- begin
- Inc(fNumRetries);
- if Assigned(fOnRetry) then
- begin
- //can cancel next retries
- fOnRetry(fLastException,fCircuitBreaked);
- //if cancelled next retries, decrease current retries
- if fCircuitBreaked then fNumRetries := fNumRetries - 1;
- Result := not fCircuitBreaked;
- end
- else Result := True;
- //wait between retries
- if (Result) and (fMaxRetries <> 0) then
- begin
- if IsEmptyArray(fWaitTimeArray) then
- begin
- if fWaitTimeMultiplierFactor = 1 then waitretryMS := fWaitTimeBetweenRetries
- else waitretryMS := fWaitTimeBetweenRetries * Round(fNumRetries * fWaitTimeMultiplierFactor);
- end
- else waitretryMS := fWaitTimeArray[fNumRetries - 1];
- if waitretryMS > 0 then WaitBeforeRetry(waitretryMS);
- end;
- end
- else fCircuitBreaked := True;
- if fCircuitBreaked then
- begin
- if Assigned(fOnCircuitBreak) then fOnCircuitBreak;
- if Assigned(fLastException) then raise fLastException;
- end;
- end;
- end;
- end;
- procedure TFaultControl.Reset;
- begin
- fCircuitBreaked := False;
- fTaskFailed := False;
- fNumRetries := 0;
- fLastException := nil;
- end;
- procedure TFaultControl.SetWaitTimeMultiplierFactor(const Value: Double);
- begin
- if Value = 0 then raise EFaultControlConfigError.Create('WaitTimeMultiplierFactor cannot be 0')
- else fWaitTimeMultiplierFactor := Value;
- end;
- procedure TFaultControl.SuccessExecution;
- begin
- fTaskFailed := False;
- end;
- procedure TFaultControl.WaitBeforeRetry(aNumWaitMilliseconds: Integer);
- var
- fEvent : TSimpleEvent;
- begin
- //Sleep(aNumWaitMilliseconds);
- {$IFDEF FPC}
- fEvent := TSimpleEvent.Create;
- {$ELSE}
- fEvent := TSimpleEvent.Create(nil,True,False,'');
- {$ENDIF}
- try
- fEvent.WaitFor(aNumWaitMilliseconds);
- finally
- fEvent.SetEvent;
- fEvent.Free;
- end;
- end;
- destructor TFaultControl.Destroy;
- begin
- //if Assigned(fLastException) then fLastException.Free;
- inherited;
- end;
- procedure TFaultControl.FailedExecution(aException: Exception);
- begin
- fTaskFailed := True;
- //free older exception
- if Assigned(fLastException) then fLastException.Free;
- fLastException := aException;
- if fMaxRetries = 0 then raise aException
- else if fNumRetries = fMaxRetries then
- begin
- aException.Message := Format('Max %d retries reached: %s',[fMaxRetries,aException.Message]);
- raise fLastException;
- end;
- end;
- { TFaultPolicy }
- constructor TFaultPolicy.Create;
- begin
- fMaxRetries := 0;
- fWaitTimeBetweenRetries := 0;
- fWaitTimeMultiplierFactor := 1;
- end;
- end.
|