This page describes how to configure task retry.

Default Behavior

By default, asynq will retry a task for a maximum of 25 times. Each time the task is retried, it uses an exponential backoff strategy to calculate the retry delay. If a task exhausts all of its retry attempts (default is 25 times), the task will be moved to the archived state for debugging and inspection purposes, and will not be retried automatically (you can still manually run the task using the CLI or WebUI).

The following task retry properties can be customized:

  • Maximum retry attempts for each task
  • Time interval to wait before a failed task can be retried (i.e., delay)
  • Whether to use the task's retry count
  • Whether to skip retries and send the task directly to the archive

The remaining parts of this page describe each custom option mentioned above.

Customizing Maximum Retry Attempts for Tasks

You can specify the maximum number of retry attempts for a task when enqueuing the task using the asynq.MaxRetry option.

Example:

client.Enqueue(task, asynq.MaxRetry(5))

This indicates that the task should be retried at most five times.

Alternatively, if you want to set the maximum retry attempts for a particular task, you can set it as a default option for the task.

task := asynq.NewTask("feed:import", nil, asynq.MaxRetry(5))
client.Enqueue(task) // MaxRetry set to 5

Customizing Retry Delay

You can specify how to calculate the retry delay using the RetryDelayFunc option in the Config struct.

The signature of RetryDelayFunc is as follows:

// n is the number of times the task has been retried
// e is the error returned by the task handler
// t is the related task
RetryDelayFunc func(n int, e error, t *asynq.Task) time.Duration

Example:

srv := asynq.NewServer(redis, asynq.Config{
    Concurrency: 20,
    RetryDelayFunc: func(n int, e error, t *asynq.Task) time.Duration {
        return 2 * time.Second
    },
})

This indicates that all failed tasks will wait for two seconds before being processed again.

The default behavior is exponential backoff, defined by DefaultRetryDelayFunc. The following example demonstrates how to customize retry delay for specific task types:

srv := asynq.NewServer(redis, asynq.Config{
    // For "foo" tasks, always use a 2-second delay, other tasks use the default behavior.
    RetryDelayFunc: func(n int, e error, t *asynq.Task) time.Duration {
        if t.Type() == "foo" {
            return 2 * time.Second 
        }
        return asynq.DefaultRetryDelayFunc(n, e, t) 
    },
})

Non-failure error

Sometimes, you may want to return an error from the handler and retry the task later, but without using the task's retry count. For example, you may want to retry later because the work unit does not have enough resources to handle the task. During server initialization, you can choose to provide a Config for the IsFailure(error) bool function. This predicate function determines whether the error from the handler counts as a failure. If the function returns false (i.e., a non-failure error), the server will not use the task's retry count and simply schedule the task for later retry.

Example:

var ErrResourceNotAvailable = errors.New("no resource is available")

func HandleResourceIntensiveTask(ctx context.Context, task *asynq.Task) error {
    if !IsResourceAvailable() {
        return ErrResourceNotAvailable
    }
    // ... logic for handling resource-intensive task
}

// ...

srv := asynq.NewServer(redisConnOpt, asynq.Config{
    // ... other configuration options
    IsFailure: func(err error) bool {
        return err != ErrResourceNotAvailable // non-failure error if no resource is available
    },
})

Skip retry

If Handler.ProcessTask returns a SkipRetry error, the task will be archived regardless of the remaining retry count. The returned error can be SkipRetry or an error wrapped with SkipRetry error.

func ExampleHandler(ctx context.Context, task *asynq.Task) error {
    // Task handling logic here...
    // If the handler knows the task should not be retried, return SkipRetry
    return fmt.Errorf(": %w", asynq.SkipRetry)
}