# Retry Policy API **Unit**: `Murphy.Policy.Retry` The Retry policy automatically retries failed operations with configurable retry counts and delays between attempts. ## Overview 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 ## Table of Contents - [TRetryContext Class](#tretrycontext-class) - [IRetryPolicy Interface](#iretrypolicy-interface) - [TRetryPolicy Class](#tretrypolicy-class) - [TRetryBuilder Class](#tretrybuilder-class) - [Usage Examples](#usage-examples) --- ## TRetryContext Class Contains information about the current retry attempt and allows dynamic modification of retry behavior. ### Declaration ```pascal 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; ``` ### Properties #### Attempts ```pascal 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. #### Exception ```pascal 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. #### WaitDelay ```pascal 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**: ```pascal 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; ``` --- ## IRetryPolicy Interface The interface for the Retry policy pattern. ### Declaration ```pascal 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): IRetryPolicy; end; ``` ### Methods #### Execute ```pascal procedure Execute(AProc: TProc); ``` **Description**: Executes the provided procedure with automatic retry on failure. **Parameters**: - `AProc: TProc` - Anonymous procedure to execute **Behavior**: 1. Executes `AProc` 2. If successful, returns immediately 3. If exception is thrown and handled by policy: - Checks `When` predicate (if configured) - Waits for specified delay - Retries up to configured limit 4. Re-raises exception if: - Exception type not handled by policy - `When` predicate returns `False` - Retry limit exceeded **Example**: ```pascal RetryPolicy.Execute( procedure begin HTTP.Get('https://api.example.com/data'); end); ``` #### Retry ```pascal 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**: ```pascal // 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 `1` retry if not specified - `ATimes` can be `0` for no retries #### RetryForever ```pascal function RetryForever: IRetryPolicy; ``` **Description**: Configures the policy to retry indefinitely until success. **Returns**: `IRetryPolicy` - Self for method chaining **Example**: ```pascal Policy := TRetryBuilder .Handle(Exception) .RetryForever .Wait(TTimeSpan.FromSeconds(5)) .Build; ``` **Warning**: Use with caution! Ensure you have: - A `When` predicate 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 #### RetryOnce ```pascal 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**: ```pascal // These are equivalent: Policy1 := TRetryBuilder.Handle(Exception).RetryOnce.Build; Policy2 := TRetryBuilder.Handle(Exception).Retry(1).Build; ``` #### Wait ```pascal 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**: ```pascal // 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 `When` predicate via `Context.WaitDelay` #### When ```pascal function When(APredicate: TPredicate): IRetryPolicy; ``` **Description**: Configures a predicate to control retry behavior dynamically based on the current context. **Parameters**: - `APredicate: TPredicate` - 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**: ```pascal // 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; ``` --- ## TRetryPolicy Class Concrete implementation of the Retry policy pattern. ### Declaration ```pascal type TRetryPolicy = class(TPolicy, IRetryPolicy) private FRetryTimes: Integer; FWaitDelay: TTimeSpan; FWhenPredicate: TPredicate; public constructor Create(AExceptionTypes: TArray); 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): IRetryPolicy; end; ``` ### Constructor ```pascal constructor Create(AExceptionTypes: TArray); override; ``` **Description**: Creates a retry policy instance with default configuration. **Parameters**: - `AExceptionTypes: TArray` - 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. ### Implementation Details The `Execute` method (Murphy.Policy.Retry.pas:88) implements the retry logic: 1. Creates `TRetryContext` to track state 2. Enters retry loop: - Increments attempt counter - Executes user code - On success: breaks loop and returns - On exception: - Checks if exception is handled - Checks `When` predicate (if configured) - Checks retry limit - Waits for delay - Continues to next iteration 3. Cleans up context **Key Points**: - `FRetryTimes = -1` means retry forever - Exception is re-raised if not handled, predicate returns false, or limit exceeded - Delay is applied via `Scheduler.WaitFor` (respects test mode) --- ## TRetryBuilder Class Builder class for creating retry policies with fluent configuration. ### Declaration ```pascal type TRetryBuilder = class sealed(TPolicyBuilder) public class function Handle(AExceptionTypes: TArray): IRetryPolicy; override; end; ``` ### Class Methods #### Handle ```pascal class function Handle(AExceptionTypes: TArray): IRetryPolicy; override; ``` **Description**: Creates a new retry policy builder configured to handle specified exception types. **Parameters**: - `AExceptionTypes: TArray` - Array of exception types to handle **Returns**: `IRetryPolicy` - Policy instance ready for further configuration **Usage**: ```pascal // 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`. --- ## Usage Examples ### Example 1: Basic Retry ```pascal 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; ``` ### Example 2: Retry Forever with Predicate ```pascal 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; ``` ### Example 3: Exponential Backoff ```pascal 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; ``` ### Example 4: Conditional Retry Based on Exception Details ```pascal 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; ``` --- ## See Also - [Retry Pattern Guide](../Patterns/Retry.md) - Comprehensive usage guide with more examples - [Base Policy API](Base-Policy.md) - Inherited functionality - [Combining Patterns](../Patterns/Combining-Patterns.md) - Using retry with other policies - [Testing](../Advanced/Testing.md) - Unit testing retry policies --- [← Base Policy](Base-Policy.md) | [API Reference Index](README.md) | [Circuit Breaker Policy →](CircuitBreaker-Policy.md)