Policies

  1. Failure Handling
  2. Policy Composition
    1. Executing a Policy Composition
    2. Example Execution
    3. Composition and Error Handling
    4. Composition Recommendations
  3. Policy Reuse
  4. Supported Policies

Failsafe-go provides several resilience policies including Retry, Circuit Breaker, Rate Limiter, Timeout, Fallback, Hedge, Bulkhead, and Cache. While each policy handles failures in different ways, some of their common features are described below.

Failure Handling

Policies add resilience by detecting failures and handling them. Each policy determines which execution results, errors, or conditions represent a failure and how to handle them.

Some policies, such as a Retry, Circuit Breaker, and Fallback, allow you to specify which errors or results to handle as failures. By default these policies handle any error that is returned. But they can be configured to handle more specific errors, error types, or results:

builder.
  HandleErrors(ErrClosed, ErrShutdown).
  HandleErrorTypes(net.OpError{}, new(net.Error)).
  HandleResult(nil)

They can also be configured to handle specific conditions:

builder.HandleIf(func(response *http.Response, err error) bool {
  return response != nil && response.StatusCode == 500
})

If multiple handle methods are configured, they are logically OR’ed. The default error handling condition is only replaced by another condition that handles errors. A HandleResult setting will not replace the default error handling.

Policy Composition

Policies can be composed in any way desired, including multiple policies of the same type. Policies handle execution results in reverse order, similar to the way that function composition works. For example, consider:

failsafe.Get(fn, fallback, retryPolicy, circuitBreaker, timeout)

This results in the following composition when executing the fn and handling its result:

Fallback(RetryPolicy(CircuitBreaker(Timeout(fn))))

Executing a Policy Composition

The process for executing a policy composition begins with Failsafe-go calling the outer-most policy. That policy in turn calls the next inner policy, and so on, until the user-provided func is reached. A result or error is returned back through the policy layers, and handled if needed by any policy along the way.

Each policy makes its own decision to allow an execution attempt to proceed and how to handle an execution result or error. For example, a RetryPolicy may retry an execution, which calls the next inner policy again, or it may return the result or error. A CircuitBreaker may return an error before an execution attempt even makes it to the func.

Example Execution

Consider an execution of the following policy composition:

  • failsafe.Get calls the Fallback
  • Fallback calls the RetryPolicy
  • RetryPolicy calls the CircuitBreaker
  • CircuitBreaker returns ErrOpen if the breaker is open, else calls the func
  • func executes and returns a result or error
  • CircuitBreaker records the result as either a success or failure, based on its configuration, possibly changing the state of the breaker, then returns
  • RetryPolicy records the result as either a success or failure, based on its configuration, and either retries or returns
  • Fallback handles the result or error according to its configuration and returns a fallback result or error if needed
  • failsafe.Get returns the final result or error to the caller

Composition and Error Handling

While policies handle all error instances by default, it’s common to configure a policy to handle more specific errors, as described above:

policyBuilder.HandleErrors(ErrClosed)

But when doing so for a policy that is composed around other policies, you may want to also configure an outer policy to handle errors returned by any inner policies, depending on your use case:

policyBuilder.HandleErrors(
  retrypolicy.ErrExceeded,
  circuitbreaker.ErrOpen,
  timeout.ErrExceeded
)

Composition Recommendations

A common policy composition ordering might place a Fallback as the outer-most policy, followed by a CachePolicy, a RetryPolicy or HedgePolicy, a CircuitBreaker or RateLimiter, a Bulkhead, and a Timeout as the inner-most policy:

failsafe.NewExecutor[any](fallback, cachePolicy, retryPolicy, circuitBreaker, bulkhead, timeout)

That said, it really depends on how the policies are being used, and different compositions make sense for different use cases.

Policy Reuse

All policies are safe to reuse across different executions. While some policies are stateless, others such as Circuit Breaker, Rate Limiter, and Bulkhead are stateful, and are specifically meant to be shared across different executions that access the same resources.

Supported Policies

Read about the built-in policies that Failsafe supports: