-
Notifications
You must be signed in to change notification settings - Fork 1
Hedging Policy
Unit: Murphy.Policy.Hedging
The Hedging policy launches parallel attempts when the primary one is too slow or fails, returning the first attempt that succeeds. It reduces tail latency at the cost of extra work.
The Hedging pattern is useful for:
- Reducing tail latency for calls to replicated or redundant services
- Riding through occasional slow or flaky calls without failing the whole request
Important: Hedged actions must be idempotent. Losing attempts are abandoned but keep running in the background - they are not cancelled, only ignored.
- THedgingContext Class
- IHedgingPolicy Interface
- THedgingPolicy Class
- THedgingBuilder Class
- Usage Examples
Contains information about a hedged attempt being launched, passed to the OnHedging callback.
type
THedgingContext = class(TObject)
private
FAttemptNumber: Integer;
public
property AttemptNumber: Integer read FAttemptNumber;
end;property AttemptNumber: Integer read FAttemptNumber;Description: The 1-based index of the hedged (extra, non-primary) attempt about to be launched.
Type: Integer (read-only)
The interface for the Hedging policy pattern. Extends IExecutablePolicy, so a Hedging policy can participate in a Policy Wrap.
type
IHedgingPolicy = interface(IExecutablePolicy)
['{5F746C39-BE24-4544-823E-FC7B7A4955D0}']
function Delay(ADelay: TTimeSpan): IHedgingPolicy;
function MaxAttempts(ACount: Integer): IHedgingPolicy;
function OnHedging(ACallback: TProc<THedgingContext>): IHedgingPolicy;
end;procedure Execute(AProc: TProc);Description: Executes AProc, launching additional parallel attempts if the primary one does not complete within the hedging delay, up to MaxAttempts extra attempts.
Behavior:
- Launches the primary attempt immediately, on its own task
- If it does not complete within
Delay, launches a hedged attempt in parallel, and keeps doing so (up toMaxAttemptsextra attempts) each time the delay elapses without any attempt succeeding - A handled exception from any attempt triggers the next hedged attempt immediately, without waiting out the remaining delay
- An unhandled exception is propagated immediately
- The first attempt to succeed wins:
Executereturns and all other attempts are abandoned (they keep running in the background) - If every attempt fails, the most recent failure is re-raised
Note: There is no dedicated "all attempts failed" exception - the last underlying failure is re-raised as-is.
function Delay(ADelay: TTimeSpan): IHedgingPolicy;Description: Configures how long to wait before launching the next hedged attempt.
Parameters:
-
ADelay: TTimeSpan- Time to wait before hedging
Returns: IHedgingPolicy - Self for method chaining
Default: 2 seconds if not specified
function MaxAttempts(ACount: Integer): IHedgingPolicy;Description: Configures how many extra parallel attempts may be launched, in addition to the primary one.
Parameters:
-
ACount: Integer- Number of extra attempts (must be>= 0)
Returns: IHedgingPolicy - Self for method chaining
Default: 1 if not specified
Raises: EArgumentException if ACount < 0
function OnHedging(ACallback: TProc<THedgingContext>): IHedgingPolicy;Description: Configures a callback invoked just before each hedged (extra) attempt is launched.
Parameters:
-
ACallback: TProc<THedgingContext>- Procedure receiving the hedging context
Returns: IHedgingPolicy - Self for method chaining
Use Cases: Logging or metrics on how often hedging kicks in
Concrete implementation of the Hedging policy pattern.
type
THedgingPolicy = class(TPolicy, IHedgingPolicy)
private
FHedgingDelay: TTimeSpan;
FMaxAttempts: Integer;
FOnHedgingCallback: TProc<THedgingContext>;
public
constructor Create(AExceptionTypes: TArray<ExceptClass>); override;
procedure Execute(AProc: TProc);
function Delay(ADelay: TTimeSpan): IHedgingPolicy;
function MaxAttempts(ACount: Integer): IHedgingPolicy;
function OnHedging(ACallback: TProc<THedgingContext>): IHedgingPolicy;
end;constructor Create(AExceptionTypes: TArray<ExceptClass>); override;Defaults:
-
FHedgingDelay:2 seconds -
FMaxAttempts:1
Note: Typically called by THedgingBuilder, not directly by users.
Each attempt runs on its own TTask, coordinated through a shared, reference-counted execution state (THedgingExecution). The state tracks how many attempts have completed, whether a winner has already been declared, and the most recent error. Losing attempts release their reference to the shared state when they eventually finish, so the state outlives Execute if attempts are still abandoned in flight.
Builder class for creating Hedging policies with fluent configuration.
type
THedgingBuilder = class sealed(TPolicyBuilder<IHedgingPolicy>)
public
class function Handle(AExceptionTypes: TArray<ExceptClass>): IHedgingPolicy; override;
end;class function Handle(AExceptionTypes: TArray<ExceptClass>): IHedgingPolicy; override;Description: Creates a new Hedging policy instance. An attempt failing with a handled exception triggers the next parallel attempt immediately; an unhandled exception is propagated at once.
Returns: IHedgingPolicy - Policy instance ready for further configuration
uses
Murphy.Policy.Hedging;
var
Policy: IHedgingPolicy;
begin
Policy := THedgingBuilder
.Handle(ENetworkError)
.MaxAttempts(2)
.Delay(TTimeSpan.FromMilliseconds(500))
.Build;
Policy.Execute(
procedure
begin
CallReplicatedService;
end);
end;var
Policy: IHedgingPolicy;
begin
Policy := THedgingBuilder
.Handle(ENetworkError)
.MaxAttempts(3)
.Delay(TTimeSpan.FromMilliseconds(200))
.OnHedging(procedure(Context: THedgingContext)
begin
WriteLn(Format('Launching hedged attempt #%d', [Context.AttemptNumber]));
end)
.Build;
Policy.Execute(procedure begin CallReplicatedService; end);
end;var
Policy: IHedgingPolicy;
begin
Policy := THedgingBuilder.Handle(ENetworkError).MaxAttempts(2).Build;
try
Policy.Execute(procedure begin CallReplicatedService; end);
except
on E: ENetworkError do
WriteLn('All hedged attempts failed: ' + E.Message);
end;
end;- Hedging Pattern Guide - Comprehensive usage guide with more examples
-
Base Policy API - Inherited functionality, including
IExecutablePolicy - Policy Wrap API - Composing Hedging with other policies
- Combining Patterns - Using Hedging with other policies