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)
}