Skip to content

Hedging Policy

Marco Breveglieri edited this page Jul 20, 2026 · 1 revision

Hedging Policy API

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.

Overview

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.

Table of Contents


THedgingContext Class

Contains information about a hedged attempt being launched, passed to the OnHedging callback.

Declaration

type
  THedgingContext = class(TObject)
  private
    FAttemptNumber: Integer;
  public
    property AttemptNumber: Integer read FAttemptNumber;
  end;

Properties

AttemptNumber

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)


IHedgingPolicy Interface

The interface for the Hedging policy pattern. Extends IExecutablePolicy, so a Hedging policy can participate in a Policy Wrap.

Declaration

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;

Methods

Execute

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:

  1. Launches the primary attempt immediately, on its own task
  2. If it does not complete within Delay, launches a hedged attempt in parallel, and keeps doing so (up to MaxAttempts extra attempts) each time the delay elapses without any attempt succeeding
  3. A handled exception from any attempt triggers the next hedged attempt immediately, without waiting out the remaining delay
  4. An unhandled exception is propagated immediately
  5. The first attempt to succeed wins: Execute returns and all other attempts are abandoned (they keep running in the background)
  6. 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.

Delay

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

MaxAttempts

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

OnHedging

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


THedgingPolicy Class

Concrete implementation of the Hedging policy pattern.

Declaration

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

constructor Create(AExceptionTypes: TArray<ExceptClass>); override;

Defaults:

  • FHedgingDelay: 2 seconds
  • FMaxAttempts: 1

Note: Typically called by THedgingBuilder, not directly by users.

Implementation Details

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.


THedgingBuilder Class

Builder class for creating Hedging policies with fluent configuration.

Declaration

type
  THedgingBuilder = class sealed(TPolicyBuilder<IHedgingPolicy>)
  public
    class function Handle(AExceptionTypes: TArray<ExceptClass>): IHedgingPolicy; override;
  end;

Handle

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


Usage Examples

Example 1: Basic Hedging

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;

Example 2: Observing Hedged Attempts

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;

Example 3: Handling Total Failure

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;

See Also


← Cache Policy | API Reference Index | Policy Wrap →

Clone this wiki locally