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 theFallback
Fallback
calls theRetryPolicy
RetryPolicy
calls theCircuitBreaker
CircuitBreaker
returnsErrOpen
if the breaker is open, else calls thefunc
func
executes and returns a result or errorCircuitBreaker
records the result as either a success or failure, based on its configuration, possibly changing the state of the breaker, then returnsRetryPolicy
records the result as either a success or failure, based on its configuration, and either retries or returnsFallback
handles the result or error according to its configuration and returns a fallback result or error if neededfailsafe.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: