-
Notifications
You must be signed in to change notification settings - Fork 1
Retry Policy
Unit: Murphy.Policy.Retry
The Retry policy automatically retries failed operations with configurable retry counts and delays between attempts.
The Retry pattern is useful for handling transient failures - temporary problems that may resolve if you try again, such as:
- Network timeouts
- Temporary service unavailability
- Database connection failures
- Resource contention
Contains information about the current retry attempt and allows dynamic modification of retry behavior.
type
TRetryContext = class(TObject)
private
FAttempts: Integer;
FException: Exception;
FWaitDelay: TTimeSpan;
public
property Attempts: Integer read FAttempts;
property Exception: Exception read FException;
property WaitDelay: TTimeSpan read FWaitDelay write FWaitDelay;
end;property Attempts: Integer read FAttempts;Description: The number of attempts made so far (including the current one).
Type: Integer (read-only)
Values:
-
1- First attempt (initial execution) -
2- First retry -
3- Second retry - And so on...
Usage: Check attempt count in When predicate to implement custom retry logic.
property Exception: Exception read FException;Description: The exception that caused the current retry attempt.
Type: Exception (read-only)
Usage: Access exception details in When predicate to make decisions based on the specific error.
property WaitDelay: TTimeSpan read FWaitDelay write FWaitDelay;Description: The delay that will be applied before the next retry attempt.
Type: TTimeSpan (read-write)
Usage: Modify this property in a When predicate to implement exponential backoff or other dynamic delay strategies.
Example:
Policy := TRetryBuilder
.Handle(Exception)
.Retry(5)
.Wait(TTimeSpan.FromSeconds(1))
.When(function(Context: TRetryContext): Boolean
begin
// Exponential backoff: double the delay each time
Context.WaitDelay := TTimeSpan.FromMilliseconds(
Context.WaitDelay.TotalMilliseconds * 2
);
Result := True; // Continue retrying
end)
.Build;The interface for the Retry policy pattern.
type
IRetryPolicy = interface(IPolicy)
['{7F6C8429-D08C-4ADF-82F6-FD96747CCA4C}']
procedure Execute(AProc: TProc);
function Retry(ATimes: Integer = 1): IRetryPolicy;
function RetryForever: IRetryPolicy;
function RetryOnce: IRetryPolicy;
function Wait(ADelay: TTimeSpan): IRetryPolicy;
function When(APredicate: TPredicate<TRetryContext>): IRetryPolicy;
end;procedure Execute(AProc: TProc);Description: Executes the provided procedure with automatic retry on failure.
Parameters:
-
AProc: TProc- Anonymous procedure to execute
Behavior:
- Executes
AProc - If successful, returns immediately
- If exception is thrown and handled by policy:
- Checks
Whenpredicate (if configured) - Waits for specified delay
- Retries up to configured limit
- Checks
- Re-raises exception if:
- Exception type not handled by policy
-
Whenpredicate returnsFalse - Retry limit exceeded
Example:
RetryPolicy.Execute(
procedure
begin
HTTP.Get('https://api.example.com/data');
end);function Retry(ATimes: Integer = 1): IRetryPolicy;Description: Configures the number of retry attempts.
Parameters:
-
ATimes: Integer- Number of retries to attempt (default: 1)
Returns: IRetryPolicy - Self for method chaining
Total Attempts: ATimes + 1 (initial attempt plus retries)
Examples:
// Retry 3 times = 4 total attempts (1 initial + 3 retries)
Policy := TRetryBuilder.Handle(Exception).Retry(3).Build;
// Retry once = 2 total attempts (1 initial + 1 retry) - same as RetryOnce
Policy := TRetryBuilder.Handle(Exception).Retry(1).Build;
// No retries = 1 total attempt (initial only)
Policy := TRetryBuilder.Handle(Exception).Retry(0).Build;Notes:
- Default is
1retry if not specified -
ATimescan be0for no retries
function RetryForever: IRetryPolicy;Description: Configures the policy to retry indefinitely until success.
Returns: IRetryPolicy - Self for method chaining
Example:
Policy := TRetryBuilder
.Handle(Exception)
.RetryForever
.Wait(TTimeSpan.FromSeconds(5))
.Build;Warning: Use with caution! Ensure you have:
- A
Whenpredicate to eventually stop retrying, or - External monitoring to detect infinite retry loops, or
- Confidence that the operation will eventually succeed
Use Cases:
- Critical operations that must succeed
- Operations with guaranteed eventual consistency
- Background workers that should never give up
function RetryOnce: IRetryPolicy;Description: Convenience method to configure exactly one retry (2 total attempts).
Returns: IRetryPolicy - Self for method chaining
Equivalent to: Retry(1)
Example:
// These are equivalent:
Policy1 := TRetryBuilder.Handle(Exception).RetryOnce.Build;
Policy2 := TRetryBuilder.Handle(Exception).Retry(1).Build;function Wait(ADelay: TTimeSpan): IRetryPolicy;Description: Configures the delay between retry attempts.
Parameters:
-
ADelay: TTimeSpan- Time to wait between retries
Returns: IRetryPolicy - Self for method chaining
Default: 1 second if not specified
Examples:
// Wait 2 seconds between retries
Policy := TRetryBuilder
.Handle(Exception)
.Retry(3)
.Wait(TTimeSpan.FromSeconds(2))
.Build;
// Wait 500 milliseconds
Policy := TRetryBuilder
.Handle(Exception)
.Retry(5)
.Wait(TTimeSpan.FromMilliseconds(500))
.Build;
// Wait 1 minute
Policy := TRetryBuilder
.Handle(Exception)
.Retry(3)
.Wait(TTimeSpan.FromMinutes(1))
.Build;Notes:
- Delay applies between attempts, not before the first attempt
- In test mode (
MurphyTestModeEnabled = True), delays are skipped - Can be modified dynamically in
Whenpredicate viaContext.WaitDelay
function When(APredicate: TPredicate<TRetryContext>): IRetryPolicy;Description: Configures a predicate to control retry behavior dynamically based on the current context.
Parameters:
-
APredicate: TPredicate<TRetryContext>- Function that receives context and returns whether to retry
Predicate Returns:
-
True- Continue with retry -
False- Stop retrying and re-raise exception immediately
Returns: IRetryPolicy - Self for method chaining
Use Cases:
- Exponential backoff
- Conditional retry based on exception details
- Maximum total retry time
- Custom retry logic based on attempt count
Examples:
// Exponential backoff
Policy := TRetryBuilder
.Handle(Exception)
.Retry(5)
.Wait(TTimeSpan.FromSeconds(1))
.When(function(Context: TRetryContext): Boolean
begin
// Double the delay each retry
Context.WaitDelay := TTimeSpan.FromMilliseconds(
Context.WaitDelay.TotalMilliseconds * 2
);
Result := True;
end)
.Build;
// Retry only for specific HTTP status codes
Policy := TRetryBuilder
.Handle(EIdHTTPProtocolException)
.Retry(3)
.When(function(Context: TRetryContext): Boolean
begin
if Context.Exception is EIdHTTPProtocolException then
begin
var StatusCode := EIdHTTPProtocolException(Context.Exception).ErrorCode;
// Only retry on 503 (Service Unavailable) or 429 (Too Many Requests)
Result := (StatusCode = 503) or (StatusCode = 429);
end
else
Result := True;
end)
.Build;
// Stop after total retry time exceeds threshold
var
StartTime: TDateTime;
begin
StartTime := Now;
Policy := TRetryBuilder
.Handle(Exception)
.RetryForever
.Wait(TTimeSpan.FromSeconds(2))
.When(function(Context: TRetryContext): Boolean
begin
// Stop retrying after 30 seconds total
var ElapsedSeconds := SecondsBetween(Now, StartTime);
Result := ElapsedSeconds < 30;
end)
.Build;
end;Concrete implementation of the Retry policy pattern.
type
TRetryPolicy = class(TPolicy, IRetryPolicy)
private
FRetryTimes: Integer;
FWaitDelay: TTimeSpan;
FWhenPredicate: TPredicate<TRetryContext>;
public
constructor Create(AExceptionTypes: TArray<ExceptClass>); override;
procedure Execute(AProc: TProc);
function Retry(ATimes: Integer = 1): IRetryPolicy;
function RetryForever: IRetryPolicy;
function RetryOnce: IRetryPolicy;
function Wait(ADelay: TTimeSpan): IRetryPolicy;
function When(APredicate: TPredicate<TRetryContext>): IRetryPolicy;
end;constructor Create(AExceptionTypes: TArray<ExceptClass>); override;Description: Creates a retry policy instance with default configuration.
Parameters:
-
AExceptionTypes: TArray<ExceptClass>- Exception types to handle
Defaults:
-
FRetryTimes:1(one retry = 2 total attempts) -
FWaitDelay:1 second -
FWhenPredicate:nil(no predicate)
Note: Typically called by builder, not directly by users.
The Execute method (Murphy.Policy.Retry.pas:88) implements the retry logic:
- Creates
TRetryContextto track state - Enters retry loop:
- Increments attempt counter
- Executes user code
- On success: breaks loop and returns
- On exception:
- Checks if exception is handled
- Checks
Whenpredicate (if configured) - Checks retry limit
- Waits for delay
- Continues to next iteration
- Cleans up context
Key Points:
-
FRetryTimes = -1means retry forever - Exception is re-raised if not handled, predicate returns false, or limit exceeded
- Delay is applied via
Scheduler.WaitFor(respects test mode)
Builder class for creating retry policies with fluent configuration.
type
TRetryBuilder = class sealed(TPolicyBuilder<IRetryPolicy>)
public
class function Handle(AExceptionTypes: TArray<ExceptClass>): IRetryPolicy; override;
end;class function Handle(AExceptionTypes: TArray<ExceptClass>): IRetryPolicy; override;Description: Creates a new retry policy builder configured to handle specified exception types.
Parameters:
-
AExceptionTypes: TArray<ExceptClass>- Array of exception types to handle
Returns: IRetryPolicy - Policy instance ready for further configuration
Usage:
// Single exception
Policy := TRetryBuilder
.Handle(EIdHTTPProtocolException)
.Retry(3)
.Build;
// Multiple exceptions
Policy := TRetryBuilder
.Handle([
EIdHTTPProtocolException,
EIdSocketError
])
.Retry(3)
.Build;Note: The policy instance returned is both the policy and the builder - you can chain configuration methods and then use Execute directly without calling Build.
uses
Murphy.Policy.Retry;
var
Policy: IRetryPolicy;
begin
Policy := TRetryBuilder
.Handle(EIdHTTPProtocolException)
.Retry(3)
.Wait(TTimeSpan.FromSeconds(2))
.Build;
Policy.Execute(
procedure
begin
WriteLn('Attempting HTTP request...');
HTTP.Get('https://api.example.com/data');
WriteLn('Success!');
end);
end;var
Policy: IRetryPolicy;
AttemptCount: Integer = 0;
begin
Policy := TRetryBuilder
.Handle(Exception)
.RetryForever
.Wait(TTimeSpan.FromSeconds(5))
.When(function(Context: TRetryContext): Boolean
begin
Inc(AttemptCount);
WriteLn('Attempt ', AttemptCount, ' failed. Retrying...');
// Stop after 10 attempts even though policy is RetryForever
Result := AttemptCount < 10;
end)
.Build;
Policy.Execute(
procedure
begin
// Critical operation that must succeed
PerformCriticalOperation;
end);
end;var
Policy: IRetryPolicy;
begin
Policy := TRetryBuilder
.Handle(Exception)
.Retry(5)
.Wait(TTimeSpan.FromSeconds(1)) // Initial delay: 1 second
.When(function(Context: TRetryContext): Boolean
begin
WriteLn(Format('Attempt %d failed. Waiting %d ms before retry...',
[Context.Attempts, Trunc(Context.WaitDelay.TotalMilliseconds)]));
// Double the delay for next retry (exponential backoff)
Context.WaitDelay := TTimeSpan.FromMilliseconds(
Context.WaitDelay.TotalMilliseconds * 2
);
Result := True;
end)
.Build;
Policy.Execute(
procedure
begin
MakeNetworkRequest;
end);
// Delays: 1s, 2s, 4s, 8s, 16s
end;var
Policy: IRetryPolicy;
begin
Policy := TRetryBuilder
.Handle(EIdHTTPProtocolException)
.Retry(5)
.Wait(TTimeSpan.FromSeconds(2))
.When(function(Context: TRetryContext): Boolean
begin
if Context.Exception is EIdHTTPProtocolException then
begin
var Ex := EIdHTTPProtocolException(Context.Exception);
// Only retry on specific status codes
case Ex.ErrorCode of
408, // Request Timeout
429, // Too Many Requests
503, // Service Unavailable
504: // Gateway Timeout
begin
WriteLn(Format('Received %d, retrying...', [Ex.ErrorCode]));
Result := True;
end;
else
WriteLn(Format('Received %d, not retrying', [Ex.ErrorCode]));
Result := False;
end;
end
else
Result := True;
end)
.Build;
Policy.Execute(
procedure
begin
CallAPI;
end);
end;- Retry Pattern Guide - Comprehensive usage guide with more examples
- Base Policy API - Inherited functionality
- Combining Patterns - Using retry with other policies
- Testing - Unit testing retry policies
← Base Policy | API Reference Index | Circuit Breaker Policy →