Skip to content

Timeout Policy

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

Timeout Policy API

Unit: Murphy.Policy.Timeout

The Timeout policy aborts an operation that exceeds a configured duration, so a hung dependency cannot hang the caller.

Overview

The Timeout pattern is useful for bounding how long an operation is allowed to run, such as:

  • Network calls without a reliable timeout of their own
  • Enforcing an SLA on a critical code path
  • Preventing a slow dependency from blocking a calling thread indefinitely

Table of Contents


TTimeoutContext Class

Contains information about a timeout that occurred, passed to the OnTimeout callback.

Declaration

type
  TTimeoutContext = class(TObject)
  private
    FElapsedTime: TTimeSpan;
    FTimeoutDuration: TTimeSpan;
  public
    property ElapsedTime: TTimeSpan read FElapsedTime;
    property TimeoutDuration: TTimeSpan read FTimeoutDuration;
  end;

Properties

ElapsedTime

property ElapsedTime: TTimeSpan read FElapsedTime;

Description: How much time actually elapsed before the timeout fired.

Type: TTimeSpan (read-only)

TimeoutDuration

property TimeoutDuration: TTimeSpan read FTimeoutDuration;

Description: The configured timeout duration that was exceeded.

Type: TTimeSpan (read-only)


ITimeoutPolicy Interface

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

Declaration

type
  ITimeoutPolicy = interface(IExecutablePolicy)
    ['{8B3E9F12-4A7C-4D1E-9B5A-2E8C7F6D4A91}']
    function After(ADuration: TTimeSpan): ITimeoutPolicy;
    function OnTimeout(ACallback: TProc<TTimeoutContext>): ITimeoutPolicy;
  end;

Methods

Execute

procedure Execute(AProc: TProc);

Description: Executes AProc on a background task and waits up to the configured duration.

Behavior:

  1. Runs AProc on a TTask
  2. If it completes within the timeout, any exception it raised is re-raised as-is on the calling thread
  3. If it does not complete in time:
    • The OnTimeout callback (if configured) is invoked with a TTimeoutContext
    • ETimeoutRejectedException is raised
    • The background task is abandoned and keeps running; it cleans up its own exception if it eventually raises one

Note: Exception filtering configured via Handle() is not applied by this policy - it is accepted only for API consistency with other builders. Any exception raised by AProc within the timeout is re-raised untouched, and the timeout itself fires regardless of exception type.

After

function After(ADuration: TTimeSpan): ITimeoutPolicy;

Description: Configures the maximum duration allowed for the operation.

Parameters:

  • ADuration: TTimeSpan - Maximum time to wait before aborting

Returns: ITimeoutPolicy - Self for method chaining

Default: 30 seconds if not specified

Example:

Policy := TTimeoutBuilder
  .Handle([])
  .After(TTimeSpan.FromSeconds(5))
  .Build;

OnTimeout

function OnTimeout(ACallback: TProc<TTimeoutContext>): ITimeoutPolicy;

Description: Configures a callback invoked when the operation times out, before ETimeoutRejectedException is raised.

Parameters:

  • ACallback: TProc<TTimeoutContext> - Procedure receiving the timeout context

Returns: ITimeoutPolicy - Self for method chaining

Use Cases: Logging elapsed time, recording metrics, alerting on repeated timeouts

Example:

Policy := TTimeoutBuilder
  .Handle([])
  .After(TTimeSpan.FromSeconds(5))
  .OnTimeout(procedure(Context: TTimeoutContext)
             begin
               Log(Format('Timed out after %s', [Context.ElapsedTime.ToString]));
             end)
  .Build;

TTimeoutPolicy Class

Concrete implementation of the Timeout policy pattern.

Declaration

type
  TTimeoutPolicy = class(TPolicy, ITimeoutPolicy)
  private
    FTimeoutDuration: TTimeSpan;
    FOnTimeoutCallback: TProc<TTimeoutContext>;
  public
    constructor Create(AExceptionTypes: TArray<ExceptClass>); override;
    procedure Execute(AProc: TProc);
    function After(ADuration: TTimeSpan): ITimeoutPolicy;
    function OnTimeout(ACallback: TProc<TTimeoutContext>): ITimeoutPolicy;
  end;

Constructor

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

Defaults:

  • FTimeoutDuration: 30 seconds
  • FOnTimeoutCallback: nil

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

Implementation Details

Execute runs AProc on a TTask and waits with TTask.Wait(Timeout). If the wait times out, the task is abandoned rather than cancelled: it keeps executing, and if it later raises an exception or completes, that outcome is discarded because nobody is waiting for it anymore. This means the underlying operation must be safe to abandon.


TTimeoutBuilder Class

Builder class for creating Timeout policies with fluent configuration.

Declaration

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

Handle

class function Handle(AExceptionTypes: TArray<ExceptClass>): ITimeoutPolicy; override;

Description: Creates a new Timeout policy instance. The exception types passed here are not used by this policy: any exception raised by the operation is re-raised as-is, and the timeout applies regardless of any exception filter. Pass [] for clarity.

Returns: ITimeoutPolicy - Policy instance ready for further configuration


Usage Examples

Example 1: Basic Timeout

uses
  Murphy.Policy.Timeout;

var
  Policy: ITimeoutPolicy;
begin
  Policy := TTimeoutBuilder
    .Handle([])
    .After(TTimeSpan.FromSeconds(5))
    .Build;

  Policy.Execute(
    procedure
    begin
      CallSlowService;
    end);
end;

Example 2: Handling the Timeout Exception

var
  Policy: ITimeoutPolicy;
begin
  Policy := TTimeoutBuilder.Handle([]).After(TTimeSpan.FromSeconds(2)).Build;

  try
    Policy.Execute(procedure begin CallSlowService; end);
  except
    on E: ETimeoutRejectedException do
      WriteLn('Operation aborted: took too long');
  end;
end;

Example 3: Timeout Inside a Policy Wrap

uses Murphy.Policy.Retry, Murphy.Policy.Timeout, Murphy.Policy.Wrap;

var
  Policy: IPolicyWrap;
begin
  Policy := TPolicyWrapBuilder.Wrap([
    TRetryBuilder.Handle(ETimeoutRejectedException).Retry(3),
    TTimeoutBuilder.Handle([]).After(TTimeSpan.FromSeconds(1))
  ]);

  Policy.Execute(procedure begin CallExternalService; end);
end;

See Also


← Rate Limit Policy | API Reference Index | Bulkhead Policy →

Clone this wiki locally