Skip to content

Timeout

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

Timeout Pattern Guide

The Timeout pattern aborts an operation that takes too long, so a hung dependency cannot hang your application.

When to Use

  • Calls to network services that may hang indefinitely
  • Third-party APIs without their own reliable timeout
  • Enforcing an SLA on how long an operation is allowed to take
  • Protecting the UI thread from a slow background call

When NOT to Use

  • Operations that already have a robust timeout of their own (e.g. an HTTP client with a configured timeout lower than yours)
  • Non-idempotent operations that cannot be safely abandoned mid-flight (the underlying task keeps running in the background even after the timeout fires)
  • As a substitute for cancellation support in the called API - Timeout abandons the wait, it does not stop the work

Quick Start

uses Murphy.Policy.Timeout;

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

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

Common Scenarios

Enforcing a Hard Deadline

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

  Policy.Execute(
    procedure
    begin
      HTTP.Get('https://api.example.com/data');
    end);
end;

Logging on Timeout

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

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

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;

Combined with Retry

Retry the operation with a fresh timeout budget on each attempt:

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(2))
  ]);

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

Best Practices

  1. Pick a duration tied to a real requirement - a UI budget, an SLA, or an upstream deadline
  2. Remember losing work keeps running - the abandoned task is not cancelled, only its result is ignored; only use Timeout on operations safe to abandon
  3. Use OnTimeout for observability - log elapsed time to tune the threshold over time
  4. Combine with Retry - retry a timed-out attempt rather than giving up immediately
  5. Note that Handle() is ignored - the timeout fires regardless of exception type; any exception types passed are not used by this pattern

See Also


Back to Index

Clone this wiki locally