Execution Cancellation

  1. Propagating Cancellations
  2. Cooperative Cancellation
  3. Timeout vs Context Cancellation

Failsafe-go supports execution cancellation, which can be triggered in a few ways. It can be triggered by a Timeout:

// Cancel Connect call after 1 second
failsafe.With(timeout.New[any](time.Second)).Get(Connect)

By a Context:

ctx, _ := context.WithTimeout(context.Background(), time.Second)

// Connect will be canceled by the ctx after 1 second
failsafe.With(retryPolicy).WithContext(ctx).Get(Connect)

Or by an async ExecutionResult:

result := failsafe.With(retryPolicy).RunAsync(Connect)
result.Cancel()

Outstanding hedge executions are also canceled once a successful result is received.

Propagating Cancellations

For executions that may be canceled by a policy such as a timeout or hedge policy, a child context is created and made available via Context(), which should be used to propagate these cancellations to downstream code:

failsafe.With(timeout).
  GetWithExecution(func(exec failsafe.Execution[*http.Response]) (*http.Response, error) {
    request, err := http.NewRequestWithContext(exec.Context(), http.MethodGet, url, nil)
    return client.Do(request)
  })

Cooperative Cancellation

Executions can cooperate with a cancellation by periodically checking IsCanceled():

failsafe.With(timeout).RunWithExecution(func(exec failsafe.Execution[any]) error {
  for {
    if !exec.IsCanceled() {
      if err := DoWork(); err != nil {
        return err
      }
    }
  }
  return nil
})

Executions can also use the Canceled() channel to detect when an execution is canceled:

failsafe.With(timeout).RunWithExecution(func(exec failsafe.Execution[any]) error {
  for {
    select {
    case <-exec.Canceled():
      return nil
    case task := <-GetTask():
      Process(task)
    }
  }
  return nil
})

Timeout vs Context Cancellation

While a cancellation from a Timeout can still be retried by an outer RetryPolicy, a cancellation from a Context or ExecutionResult cannot be.