2020-01-03 10:13:16 +08:00
|
|
|
// Copyright 2020 Kentaro Hibino. All rights reserved.
|
|
|
|
// Use of this source code is governed by a MIT license
|
|
|
|
// that can be found in the LICENSE file.
|
|
|
|
|
2019-11-20 13:19:46 +08:00
|
|
|
package asynq
|
|
|
|
|
|
|
|
import (
|
2021-11-16 08:34:26 +08:00
|
|
|
"context"
|
2020-03-18 21:49:39 +08:00
|
|
|
"fmt"
|
2021-08-02 07:23:58 +08:00
|
|
|
"strings"
|
2019-11-20 13:19:46 +08:00
|
|
|
"time"
|
|
|
|
|
2020-07-02 21:21:20 +08:00
|
|
|
"github.com/google/uuid"
|
2019-12-22 23:15:45 +08:00
|
|
|
"github.com/hibiken/asynq/internal/base"
|
2021-05-11 12:19:57 +08:00
|
|
|
"github.com/hibiken/asynq/internal/errors"
|
2019-12-04 13:01:26 +08:00
|
|
|
"github.com/hibiken/asynq/internal/rdb"
|
2023-09-19 16:16:51 +08:00
|
|
|
"github.com/redis/go-redis/v9"
|
2019-11-20 13:19:46 +08:00
|
|
|
)
|
|
|
|
|
2019-12-07 14:00:09 +08:00
|
|
|
// A Client is responsible for scheduling tasks.
|
|
|
|
//
|
2019-12-09 22:52:43 +08:00
|
|
|
// A Client is used to register tasks that should be processed
|
2019-12-07 14:00:09 +08:00
|
|
|
// immediately or some time in the future.
|
|
|
|
//
|
|
|
|
// Clients are safe for concurrent use by multiple goroutines.
|
2019-11-20 13:19:46 +08:00
|
|
|
type Client struct {
|
2022-03-08 21:59:58 +08:00
|
|
|
broker base.Broker
|
2023-09-19 16:16:51 +08:00
|
|
|
// When a Client has been created with an existing Redis connection, we do
|
|
|
|
// not want to close it.
|
|
|
|
sharedConnection bool
|
2019-11-20 13:19:46 +08:00
|
|
|
}
|
|
|
|
|
2020-09-27 08:33:29 +08:00
|
|
|
// NewClient returns a new Client instance given a redis connection option.
|
2020-01-15 13:19:06 +08:00
|
|
|
func NewClient(r RedisConnOpt) *Client {
|
2023-09-19 16:16:51 +08:00
|
|
|
redisClient, ok := r.MakeRedisClient().(redis.UniversalClient)
|
2021-01-29 22:37:35 +08:00
|
|
|
if !ok {
|
|
|
|
panic(fmt.Sprintf("asynq: unsupported RedisConnOpt type %T", r))
|
|
|
|
}
|
2023-09-19 16:16:51 +08:00
|
|
|
client := NewClientFromRedisClient(redisClient)
|
|
|
|
client.sharedConnection = false
|
|
|
|
return client
|
|
|
|
}
|
|
|
|
|
2024-10-19 14:05:17 +08:00
|
|
|
// NewClientFromRedisClient returns a new instance of Client given a redis.UniversalClient
|
|
|
|
// Warning: The underlying redis connection pool will not be closed by Asynq, you are responsible for closing it.
|
2023-09-19 16:16:51 +08:00
|
|
|
func NewClientFromRedisClient(c redis.UniversalClient) *Client {
|
|
|
|
return &Client{broker: rdb.NewRDB(c), sharedConnection: true}
|
2019-11-20 13:19:46 +08:00
|
|
|
}
|
|
|
|
|
2020-10-10 21:46:47 +08:00
|
|
|
type OptionType int
|
|
|
|
|
|
|
|
const (
|
|
|
|
MaxRetryOpt OptionType = iota
|
|
|
|
QueueOpt
|
|
|
|
TimeoutOpt
|
|
|
|
DeadlineOpt
|
|
|
|
UniqueOpt
|
2022-06-24 14:55:53 +08:00
|
|
|
UniqueKeyOpt
|
2020-10-10 21:46:47 +08:00
|
|
|
ProcessAtOpt
|
|
|
|
ProcessInOpt
|
2021-09-12 12:11:04 +08:00
|
|
|
TaskIDOpt
|
2021-11-06 07:52:54 +08:00
|
|
|
RetentionOpt
|
2022-03-05 03:51:08 +08:00
|
|
|
GroupOpt
|
2020-10-10 21:46:47 +08:00
|
|
|
)
|
|
|
|
|
2020-01-17 11:50:45 +08:00
|
|
|
// Option specifies the task processing behavior.
|
2020-10-10 21:46:47 +08:00
|
|
|
type Option interface {
|
|
|
|
// String returns a string representation of the option.
|
|
|
|
String() string
|
|
|
|
|
|
|
|
// Type describes the type of the option.
|
|
|
|
Type() OptionType
|
|
|
|
|
|
|
|
// Value returns a value used to create this option.
|
|
|
|
Value() interface{}
|
|
|
|
}
|
2019-12-21 23:42:32 +08:00
|
|
|
|
2020-01-06 22:53:40 +08:00
|
|
|
// Internal option representations.
|
|
|
|
type (
|
2020-09-05 21:29:08 +08:00
|
|
|
retryOption int
|
|
|
|
queueOption string
|
2021-09-12 12:11:04 +08:00
|
|
|
taskIDOption string
|
2020-09-05 21:29:08 +08:00
|
|
|
timeoutOption time.Duration
|
|
|
|
deadlineOption time.Time
|
|
|
|
uniqueOption time.Duration
|
2022-06-24 14:55:53 +08:00
|
|
|
uniqueKeyOption string
|
2020-09-05 21:29:08 +08:00
|
|
|
processAtOption time.Time
|
|
|
|
processInOption time.Duration
|
2021-11-06 07:52:54 +08:00
|
|
|
retentionOption time.Duration
|
2022-03-05 03:51:08 +08:00
|
|
|
groupOption string
|
2020-01-06 22:53:40 +08:00
|
|
|
)
|
2019-12-21 23:42:32 +08:00
|
|
|
|
|
|
|
// MaxRetry returns an option to specify the max number of times
|
2020-01-06 23:21:14 +08:00
|
|
|
// the task will be retried.
|
2019-12-21 23:42:32 +08:00
|
|
|
//
|
|
|
|
// Negative retry count is treated as zero retry.
|
|
|
|
func MaxRetry(n int) Option {
|
|
|
|
if n < 0 {
|
|
|
|
n = 0
|
|
|
|
}
|
|
|
|
return retryOption(n)
|
|
|
|
}
|
|
|
|
|
2020-10-10 21:46:47 +08:00
|
|
|
func (n retryOption) String() string { return fmt.Sprintf("MaxRetry(%d)", int(n)) }
|
|
|
|
func (n retryOption) Type() OptionType { return MaxRetryOpt }
|
2020-12-01 22:53:33 +08:00
|
|
|
func (n retryOption) Value() interface{} { return int(n) }
|
2020-10-10 21:46:47 +08:00
|
|
|
|
2020-01-17 11:50:45 +08:00
|
|
|
// Queue returns an option to specify the queue to enqueue the task into.
|
2022-04-11 08:55:21 +08:00
|
|
|
func Queue(name string) Option {
|
|
|
|
return queueOption(name)
|
2020-01-06 22:53:40 +08:00
|
|
|
}
|
|
|
|
|
2022-04-11 08:55:21 +08:00
|
|
|
func (name queueOption) String() string { return fmt.Sprintf("Queue(%q)", string(name)) }
|
|
|
|
func (name queueOption) Type() OptionType { return QueueOpt }
|
|
|
|
func (name queueOption) Value() interface{} { return string(name) }
|
2020-10-10 21:46:47 +08:00
|
|
|
|
2021-09-12 12:11:04 +08:00
|
|
|
// TaskID returns an option to specify the task ID.
|
|
|
|
func TaskID(id string) Option {
|
|
|
|
return taskIDOption(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (id taskIDOption) String() string { return fmt.Sprintf("TaskID(%q)", string(id)) }
|
|
|
|
func (id taskIDOption) Type() OptionType { return TaskIDOpt }
|
|
|
|
func (id taskIDOption) Value() interface{} { return string(id) }
|
|
|
|
|
2020-02-12 13:53:59 +08:00
|
|
|
// Timeout returns an option to specify how long a task may run.
|
2020-06-24 20:42:06 +08:00
|
|
|
// If the timeout elapses before the Handler returns, then the task
|
|
|
|
// will be retried.
|
2020-02-12 13:53:59 +08:00
|
|
|
//
|
|
|
|
// Zero duration means no limit.
|
2020-06-24 20:42:06 +08:00
|
|
|
//
|
|
|
|
// If there's a conflicting Deadline option, whichever comes earliest
|
|
|
|
// will be used.
|
2020-02-12 13:53:59 +08:00
|
|
|
func Timeout(d time.Duration) Option {
|
|
|
|
return timeoutOption(d)
|
|
|
|
}
|
|
|
|
|
2020-10-10 21:46:47 +08:00
|
|
|
func (d timeoutOption) String() string { return fmt.Sprintf("Timeout(%v)", time.Duration(d)) }
|
|
|
|
func (d timeoutOption) Type() OptionType { return TimeoutOpt }
|
2020-12-01 22:53:33 +08:00
|
|
|
func (d timeoutOption) Value() interface{} { return time.Duration(d) }
|
2020-10-10 21:46:47 +08:00
|
|
|
|
2020-03-08 12:24:03 +08:00
|
|
|
// Deadline returns an option to specify the deadline for the given task.
|
2020-06-24 20:42:06 +08:00
|
|
|
// If it reaches the deadline before the Handler returns, then the task
|
|
|
|
// will be retried.
|
|
|
|
//
|
|
|
|
// If there's a conflicting Timeout option, whichever comes earliest
|
|
|
|
// will be used.
|
2020-03-08 12:24:03 +08:00
|
|
|
func Deadline(t time.Time) Option {
|
|
|
|
return deadlineOption(t)
|
|
|
|
}
|
|
|
|
|
2020-12-01 22:53:33 +08:00
|
|
|
func (t deadlineOption) String() string {
|
|
|
|
return fmt.Sprintf("Deadline(%v)", time.Time(t).Format(time.UnixDate))
|
|
|
|
}
|
2020-10-10 21:46:47 +08:00
|
|
|
func (t deadlineOption) Type() OptionType { return DeadlineOpt }
|
2020-12-01 22:53:33 +08:00
|
|
|
func (t deadlineOption) Value() interface{} { return time.Time(t) }
|
2020-10-10 21:46:47 +08:00
|
|
|
|
2020-03-18 21:49:39 +08:00
|
|
|
// Unique returns an option to enqueue a task only if the given task is unique.
|
|
|
|
// Task enqueued with this option is guaranteed to be unique within the given ttl.
|
2022-03-05 03:51:08 +08:00
|
|
|
// Once the task gets processed successfully or once the TTL has expired,
|
|
|
|
// another task with the same uniqueness may be enqueued.
|
2020-03-18 21:49:39 +08:00
|
|
|
// ErrDuplicateTask error is returned when enqueueing a duplicate task.
|
2021-11-10 07:53:48 +08:00
|
|
|
// TTL duration must be greater than or equal to 1 second.
|
2020-03-18 21:49:39 +08:00
|
|
|
//
|
2022-06-24 14:55:53 +08:00
|
|
|
// By default, the uniqueness of a task is based on the following properties:
|
2023-09-19 16:16:51 +08:00
|
|
|
// - Task Type
|
|
|
|
// - Task Payload
|
|
|
|
// - Queue Name
|
2022-06-24 14:55:53 +08:00
|
|
|
// UniqueKey can be used to specify a custom string for calculating uniqueness, instead of task payload.
|
2020-03-18 21:49:39 +08:00
|
|
|
func Unique(ttl time.Duration) Option {
|
|
|
|
return uniqueOption(ttl)
|
|
|
|
}
|
|
|
|
|
2020-10-10 21:46:47 +08:00
|
|
|
func (ttl uniqueOption) String() string { return fmt.Sprintf("Unique(%v)", time.Duration(ttl)) }
|
|
|
|
func (ttl uniqueOption) Type() OptionType { return UniqueOpt }
|
2020-12-01 22:53:33 +08:00
|
|
|
func (ttl uniqueOption) Value() interface{} { return time.Duration(ttl) }
|
2020-10-10 21:46:47 +08:00
|
|
|
|
2022-06-24 14:55:53 +08:00
|
|
|
// UniqueKey returns an option to define the custom uniqueness of a task.
|
|
|
|
// If uniqueKey is not empty, the uniqueness of a task is based on the following properties:
|
2024-11-21 02:26:35 +08:00
|
|
|
// - Task Type
|
|
|
|
// - UniqueKey
|
|
|
|
// - Queue Name
|
2022-06-24 14:55:53 +08:00
|
|
|
// Otherwise, task payload will be used, see Unique.
|
|
|
|
//
|
|
|
|
// UniqueKey should be used together with Unique.
|
|
|
|
func UniqueKey(uniqueKey string) Option {
|
|
|
|
return uniqueKeyOption(uniqueKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (uniqueKey uniqueKeyOption) String() string {
|
|
|
|
return fmt.Sprintf("UniqueKey(%q)", string(uniqueKey))
|
|
|
|
}
|
|
|
|
func (uniqueKey uniqueKeyOption) Type() OptionType { return UniqueKeyOpt }
|
|
|
|
func (uniqueKey uniqueKeyOption) Value() interface{} { return string(uniqueKey) }
|
|
|
|
|
2020-09-05 21:29:08 +08:00
|
|
|
// ProcessAt returns an option to specify when to process the given task.
|
|
|
|
//
|
|
|
|
// If there's a conflicting ProcessIn option, the last option passed to Enqueue overrides the others.
|
|
|
|
func ProcessAt(t time.Time) Option {
|
|
|
|
return processAtOption(t)
|
|
|
|
}
|
|
|
|
|
2020-12-01 22:53:33 +08:00
|
|
|
func (t processAtOption) String() string {
|
|
|
|
return fmt.Sprintf("ProcessAt(%v)", time.Time(t).Format(time.UnixDate))
|
|
|
|
}
|
2020-10-10 21:46:47 +08:00
|
|
|
func (t processAtOption) Type() OptionType { return ProcessAtOpt }
|
2020-12-01 22:53:33 +08:00
|
|
|
func (t processAtOption) Value() interface{} { return time.Time(t) }
|
2020-10-10 21:46:47 +08:00
|
|
|
|
2020-09-05 21:29:08 +08:00
|
|
|
// ProcessIn returns an option to specify when to process the given task relative to the current time.
|
|
|
|
//
|
|
|
|
// If there's a conflicting ProcessAt option, the last option passed to Enqueue overrides the others.
|
|
|
|
func ProcessIn(d time.Duration) Option {
|
|
|
|
return processInOption(d)
|
|
|
|
}
|
|
|
|
|
2020-10-10 21:46:47 +08:00
|
|
|
func (d processInOption) String() string { return fmt.Sprintf("ProcessIn(%v)", time.Duration(d)) }
|
|
|
|
func (d processInOption) Type() OptionType { return ProcessInOpt }
|
2020-12-01 22:53:33 +08:00
|
|
|
func (d processInOption) Value() interface{} { return time.Duration(d) }
|
|
|
|
|
2021-11-06 07:52:54 +08:00
|
|
|
// Retention returns an option to specify the duration of retention period for the task.
|
|
|
|
// If this option is provided, the task will be stored as a completed task after successful processing.
|
|
|
|
// A completed task will be deleted after the specified duration elapses.
|
|
|
|
func Retention(d time.Duration) Option {
|
|
|
|
return retentionOption(d)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ttl retentionOption) String() string { return fmt.Sprintf("Retention(%v)", time.Duration(ttl)) }
|
|
|
|
func (ttl retentionOption) Type() OptionType { return RetentionOpt }
|
|
|
|
func (ttl retentionOption) Value() interface{} { return time.Duration(ttl) }
|
|
|
|
|
2022-04-11 08:55:21 +08:00
|
|
|
// Group returns an option to specify the group used for the task.
|
|
|
|
// Tasks in a given queue with the same group will be aggregated into one task before passed to Handler.
|
|
|
|
func Group(name string) Option {
|
|
|
|
return groupOption(name)
|
2022-03-05 03:51:08 +08:00
|
|
|
}
|
|
|
|
|
2022-04-11 08:55:21 +08:00
|
|
|
func (name groupOption) String() string { return fmt.Sprintf("Group(%q)", string(name)) }
|
|
|
|
func (name groupOption) Type() OptionType { return GroupOpt }
|
|
|
|
func (name groupOption) Value() interface{} { return string(name) }
|
2022-03-05 03:51:08 +08:00
|
|
|
|
2020-03-18 21:49:39 +08:00
|
|
|
// ErrDuplicateTask indicates that the given task could not be enqueued since it's a duplicate of another task.
|
|
|
|
//
|
|
|
|
// ErrDuplicateTask error only applies to tasks enqueued with a Unique option.
|
|
|
|
var ErrDuplicateTask = errors.New("task already exists")
|
|
|
|
|
2021-09-12 12:11:04 +08:00
|
|
|
// ErrTaskIDConflict indicates that the given task could not be enqueued since its task ID already exists.
|
|
|
|
//
|
|
|
|
// ErrTaskIDConflict error only applies to tasks enqueued with a TaskID option.
|
|
|
|
var ErrTaskIDConflict = errors.New("task ID conflicts with another task")
|
|
|
|
|
2019-12-21 23:42:32 +08:00
|
|
|
type option struct {
|
2020-03-18 21:49:39 +08:00
|
|
|
retry int
|
|
|
|
queue string
|
2021-09-12 12:11:04 +08:00
|
|
|
taskID string
|
2020-03-18 21:49:39 +08:00
|
|
|
timeout time.Duration
|
|
|
|
deadline time.Time
|
|
|
|
uniqueTTL time.Duration
|
2022-06-24 14:55:53 +08:00
|
|
|
uniqueKey string
|
2020-09-05 21:29:08 +08:00
|
|
|
processAt time.Time
|
2021-11-06 07:52:54 +08:00
|
|
|
retention time.Duration
|
2022-04-11 08:55:21 +08:00
|
|
|
group string
|
2019-12-21 23:42:32 +08:00
|
|
|
}
|
|
|
|
|
2020-08-31 20:39:45 +08:00
|
|
|
// composeOptions merges user provided options into the default options
|
|
|
|
// and returns the composed option.
|
|
|
|
// It also validates the user provided options and returns an error if any of
|
|
|
|
// the user provided options fail the validations.
|
|
|
|
func composeOptions(opts ...Option) (option, error) {
|
2019-12-21 23:42:32 +08:00
|
|
|
res := option{
|
2020-09-05 21:29:08 +08:00
|
|
|
retry: defaultMaxRetry,
|
|
|
|
queue: base.DefaultQueueName,
|
2021-09-12 12:11:04 +08:00
|
|
|
taskID: uuid.NewString(),
|
2022-05-05 08:07:09 +08:00
|
|
|
timeout: 0, // do not set to defaultTimeout here
|
2020-09-05 21:29:08 +08:00
|
|
|
deadline: time.Time{},
|
|
|
|
processAt: time.Now(),
|
2019-12-21 23:42:32 +08:00
|
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
|
|
switch opt := opt.(type) {
|
|
|
|
case retryOption:
|
|
|
|
res.retry = int(opt)
|
2020-01-06 22:53:40 +08:00
|
|
|
case queueOption:
|
2021-07-15 21:12:13 +08:00
|
|
|
qname := string(opt)
|
|
|
|
if err := base.ValidateQueueName(qname); err != nil {
|
2020-08-31 20:53:17 +08:00
|
|
|
return option{}, err
|
2020-08-31 20:39:45 +08:00
|
|
|
}
|
2021-07-15 21:12:13 +08:00
|
|
|
res.queue = qname
|
2021-09-12 12:11:04 +08:00
|
|
|
case taskIDOption:
|
|
|
|
id := string(opt)
|
2022-03-05 22:41:44 +08:00
|
|
|
if isBlank(id) {
|
|
|
|
return option{}, errors.New("task ID cannot be empty")
|
2021-09-12 12:11:04 +08:00
|
|
|
}
|
|
|
|
res.taskID = id
|
2020-02-12 13:53:59 +08:00
|
|
|
case timeoutOption:
|
|
|
|
res.timeout = time.Duration(opt)
|
2020-03-08 12:24:03 +08:00
|
|
|
case deadlineOption:
|
|
|
|
res.deadline = time.Time(opt)
|
2020-03-18 21:49:39 +08:00
|
|
|
case uniqueOption:
|
2021-11-10 07:53:48 +08:00
|
|
|
ttl := time.Duration(opt)
|
|
|
|
if ttl < 1*time.Second {
|
|
|
|
return option{}, errors.New("Unique TTL cannot be less than 1s")
|
|
|
|
}
|
|
|
|
res.uniqueTTL = ttl
|
2022-06-24 14:55:53 +08:00
|
|
|
case uniqueKeyOption:
|
|
|
|
res.uniqueKey = string(opt)
|
2020-09-05 21:29:08 +08:00
|
|
|
case processAtOption:
|
|
|
|
res.processAt = time.Time(opt)
|
|
|
|
case processInOption:
|
|
|
|
res.processAt = time.Now().Add(time.Duration(opt))
|
2021-11-06 07:52:54 +08:00
|
|
|
case retentionOption:
|
|
|
|
res.retention = time.Duration(opt)
|
2022-03-05 22:41:44 +08:00
|
|
|
case groupOption:
|
|
|
|
key := string(opt)
|
|
|
|
if isBlank(key) {
|
|
|
|
return option{}, errors.New("group key cannot be empty")
|
|
|
|
}
|
2022-04-11 08:55:21 +08:00
|
|
|
res.group = key
|
2019-12-21 23:42:32 +08:00
|
|
|
default:
|
|
|
|
// ignore unexpected option
|
|
|
|
}
|
|
|
|
}
|
2020-08-31 20:39:45 +08:00
|
|
|
return res, nil
|
2019-12-21 23:42:32 +08:00
|
|
|
}
|
|
|
|
|
2022-03-05 22:41:44 +08:00
|
|
|
// isBlank returns true if the given s is empty or consist of all whitespaces.
|
|
|
|
func isBlank(s string) bool {
|
|
|
|
return strings.TrimSpace(s) == ""
|
2021-09-12 12:11:04 +08:00
|
|
|
}
|
|
|
|
|
2020-06-17 12:12:50 +08:00
|
|
|
const (
|
|
|
|
// Default max retry count used if nothing is specified.
|
|
|
|
defaultMaxRetry = 25
|
|
|
|
|
|
|
|
// Default timeout used if both timeout and deadline are not specified.
|
|
|
|
defaultTimeout = 30 * time.Minute
|
|
|
|
)
|
|
|
|
|
|
|
|
// Value zero indicates no timeout and no deadline.
|
|
|
|
var (
|
|
|
|
noTimeout time.Duration = 0
|
|
|
|
noDeadline time.Time = time.Unix(0, 0)
|
|
|
|
)
|
2020-04-26 22:48:38 +08:00
|
|
|
|
2020-09-08 21:52:34 +08:00
|
|
|
// Close closes the connection with redis.
|
2020-09-05 21:29:08 +08:00
|
|
|
func (c *Client) Close() error {
|
2023-09-19 16:16:51 +08:00
|
|
|
if c.sharedConnection {
|
|
|
|
return fmt.Errorf("redis connection is shared so the Client can't be closed through asynq")
|
|
|
|
}
|
2022-03-08 21:59:58 +08:00
|
|
|
return c.broker.Close()
|
2020-04-26 22:48:38 +08:00
|
|
|
}
|
|
|
|
|
2021-11-16 08:34:26 +08:00
|
|
|
// Enqueue enqueues the given task to a queue.
|
2020-04-26 22:48:38 +08:00
|
|
|
//
|
2021-05-15 21:43:18 +08:00
|
|
|
// Enqueue returns TaskInfo and nil error if the task is enqueued successfully, otherwise returns a non-nil error.
|
2020-04-26 22:48:38 +08:00
|
|
|
//
|
|
|
|
// The argument opts specifies the behavior of task processing.
|
|
|
|
// If there are conflicting Option values the last one overrides others.
|
2021-09-10 20:56:17 +08:00
|
|
|
// Any options provided to NewTask can be overridden by options passed to Enqueue.
|
2022-05-05 08:07:09 +08:00
|
|
|
// By default, max retry is set to 25 and timeout is set to 30 minutes.
|
2021-05-15 21:43:18 +08:00
|
|
|
//
|
|
|
|
// If no ProcessAt or ProcessIn options are provided, the task will be pending immediately.
|
2021-11-16 08:34:26 +08:00
|
|
|
//
|
|
|
|
// Enqueue uses context.Background internally; to specify the context, use EnqueueContext.
|
2021-05-15 21:43:18 +08:00
|
|
|
func (c *Client) Enqueue(task *Task, opts ...Option) (*TaskInfo, error) {
|
2021-11-16 08:34:26 +08:00
|
|
|
return c.EnqueueContext(context.Background(), task, opts...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// EnqueueContext enqueues the given task to a queue.
|
|
|
|
//
|
|
|
|
// EnqueueContext returns TaskInfo and nil error if the task is enqueued successfully, otherwise returns a non-nil error.
|
|
|
|
//
|
|
|
|
// The argument opts specifies the behavior of task processing.
|
|
|
|
// If there are conflicting Option values the last one overrides others.
|
|
|
|
// Any options provided to NewTask can be overridden by options passed to Enqueue.
|
2022-05-05 08:07:09 +08:00
|
|
|
// By default, max retry is set to 25 and timeout is set to 30 minutes.
|
2021-11-16 08:34:26 +08:00
|
|
|
//
|
|
|
|
// If no ProcessAt or ProcessIn options are provided, the task will be pending immediately.
|
|
|
|
//
|
|
|
|
// The first argument context applies to the enqueue operation. To specify task timeout and deadline, use Timeout and Deadline option instead.
|
|
|
|
func (c *Client) EnqueueContext(ctx context.Context, task *Task, opts ...Option) (*TaskInfo, error) {
|
2022-03-11 07:59:58 +08:00
|
|
|
if task == nil {
|
|
|
|
return nil, fmt.Errorf("task cannot be nil")
|
|
|
|
}
|
2021-08-02 07:23:58 +08:00
|
|
|
if strings.TrimSpace(task.Type()) == "" {
|
2021-08-03 06:11:49 +08:00
|
|
|
return nil, fmt.Errorf("task typename cannot be empty")
|
2021-08-01 22:24:45 +08:00
|
|
|
}
|
2021-09-10 20:56:17 +08:00
|
|
|
// merge task options with the options provided at enqueue time.
|
|
|
|
opts = append(task.opts, opts...)
|
2020-08-31 20:39:45 +08:00
|
|
|
opt, err := composeOptions(opts...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-06-17 12:12:50 +08:00
|
|
|
deadline := noDeadline
|
|
|
|
if !opt.deadline.IsZero() {
|
|
|
|
deadline = opt.deadline
|
|
|
|
}
|
|
|
|
timeout := noTimeout
|
|
|
|
if opt.timeout != 0 {
|
|
|
|
timeout = opt.timeout
|
|
|
|
}
|
|
|
|
if deadline.Equal(noDeadline) && timeout == noTimeout {
|
|
|
|
// If neither deadline nor timeout are set, use default timeout.
|
|
|
|
timeout = defaultTimeout
|
|
|
|
}
|
2020-08-07 20:36:54 +08:00
|
|
|
var uniqueKey string
|
|
|
|
if opt.uniqueTTL > 0 {
|
2022-06-24 14:55:53 +08:00
|
|
|
if opt.uniqueKey != "" {
|
|
|
|
uniqueKey = base.CustomUniqueKey(opt.queue, task.Type(), opt.uniqueKey)
|
|
|
|
} else {
|
|
|
|
uniqueKey = base.UniqueKey(opt.queue, task.Type(), task.Payload())
|
|
|
|
}
|
2020-08-07 20:36:54 +08:00
|
|
|
}
|
2019-12-22 23:15:45 +08:00
|
|
|
msg := &base.TaskMessage{
|
2021-09-12 12:11:04 +08:00
|
|
|
ID: opt.taskID,
|
2021-03-21 04:42:13 +08:00
|
|
|
Type: task.Type(),
|
|
|
|
Payload: task.Payload(),
|
2020-03-18 21:49:39 +08:00
|
|
|
Queue: opt.queue,
|
|
|
|
Retry: opt.retry,
|
2020-06-22 23:33:58 +08:00
|
|
|
Deadline: deadline.Unix(),
|
|
|
|
Timeout: int64(timeout.Seconds()),
|
2020-08-07 20:36:54 +08:00
|
|
|
UniqueKey: uniqueKey,
|
2022-04-11 08:55:21 +08:00
|
|
|
GroupKey: opt.group,
|
2021-11-06 07:52:54 +08:00
|
|
|
Retention: int64(opt.retention.Seconds()),
|
2020-03-18 21:49:39 +08:00
|
|
|
}
|
2020-06-14 20:31:24 +08:00
|
|
|
now := time.Now()
|
2021-05-15 21:43:18 +08:00
|
|
|
var state base.TaskState
|
2022-03-05 22:41:44 +08:00
|
|
|
if opt.processAt.After(now) {
|
|
|
|
err = c.schedule(ctx, msg, opt.processAt, opt.uniqueTTL)
|
|
|
|
state = base.TaskStateScheduled
|
2022-04-11 08:55:21 +08:00
|
|
|
} else if opt.group != "" {
|
2022-03-05 22:41:44 +08:00
|
|
|
// Use zero value for processAt since we don't know when the task will be aggregated and processed.
|
|
|
|
opt.processAt = time.Time{}
|
2022-04-11 08:55:21 +08:00
|
|
|
err = c.addToGroup(ctx, msg, opt.group, opt.uniqueTTL)
|
2022-03-05 22:41:44 +08:00
|
|
|
state = base.TaskStateAggregating
|
|
|
|
} else {
|
2020-09-05 21:29:08 +08:00
|
|
|
opt.processAt = now
|
2021-11-16 08:34:26 +08:00
|
|
|
err = c.enqueue(ctx, msg, opt.uniqueTTL)
|
2021-05-15 21:43:18 +08:00
|
|
|
state = base.TaskStatePending
|
2019-11-20 13:19:46 +08:00
|
|
|
}
|
2020-07-03 20:49:52 +08:00
|
|
|
switch {
|
2021-05-11 12:19:57 +08:00
|
|
|
case errors.Is(err, errors.ErrDuplicateTask):
|
2020-07-03 20:49:52 +08:00
|
|
|
return nil, fmt.Errorf("%w", ErrDuplicateTask)
|
2021-09-12 12:11:04 +08:00
|
|
|
case errors.Is(err, errors.ErrTaskIdConflict):
|
|
|
|
return nil, fmt.Errorf("%w", ErrTaskIDConflict)
|
2020-07-03 20:49:52 +08:00
|
|
|
case err != nil:
|
|
|
|
return nil, err
|
2020-03-18 21:49:39 +08:00
|
|
|
}
|
2021-11-06 07:52:54 +08:00
|
|
|
return newTaskInfo(msg, state, opt.processAt, nil), nil
|
2019-11-20 13:19:46 +08:00
|
|
|
}
|
|
|
|
|
2024-10-19 14:44:06 +08:00
|
|
|
// Ping performs a ping against the redis connection.
|
|
|
|
func (c *Client) Ping() error {
|
|
|
|
return c.broker.Ping()
|
|
|
|
}
|
|
|
|
|
2021-11-16 08:34:26 +08:00
|
|
|
func (c *Client) enqueue(ctx context.Context, msg *base.TaskMessage, uniqueTTL time.Duration) error {
|
2020-03-18 21:49:39 +08:00
|
|
|
if uniqueTTL > 0 {
|
2022-03-08 21:59:58 +08:00
|
|
|
return c.broker.EnqueueUnique(ctx, msg, uniqueTTL)
|
2020-03-18 21:49:39 +08:00
|
|
|
}
|
2022-03-08 21:59:58 +08:00
|
|
|
return c.broker.Enqueue(ctx, msg)
|
2020-03-18 21:49:39 +08:00
|
|
|
}
|
|
|
|
|
2021-11-16 08:34:26 +08:00
|
|
|
func (c *Client) schedule(ctx context.Context, msg *base.TaskMessage, t time.Time, uniqueTTL time.Duration) error {
|
2020-03-18 21:49:39 +08:00
|
|
|
if uniqueTTL > 0 {
|
2024-10-26 13:48:12 +08:00
|
|
|
ttl := time.Until(t.Add(uniqueTTL))
|
2022-03-08 21:59:58 +08:00
|
|
|
return c.broker.ScheduleUnique(ctx, msg, t, ttl)
|
2019-11-20 13:19:46 +08:00
|
|
|
}
|
2022-03-08 21:59:58 +08:00
|
|
|
return c.broker.Schedule(ctx, msg, t)
|
2019-11-20 13:19:46 +08:00
|
|
|
}
|
2022-03-05 22:41:44 +08:00
|
|
|
|
2022-04-11 08:55:21 +08:00
|
|
|
func (c *Client) addToGroup(ctx context.Context, msg *base.TaskMessage, group string, uniqueTTL time.Duration) error {
|
2022-03-05 22:41:44 +08:00
|
|
|
if uniqueTTL > 0 {
|
2022-04-11 08:55:21 +08:00
|
|
|
return c.broker.AddToGroupUnique(ctx, msg, group, uniqueTTL)
|
2022-03-05 22:41:44 +08:00
|
|
|
}
|
2022-04-11 08:55:21 +08:00
|
|
|
return c.broker.AddToGroup(ctx, msg, group)
|
2022-03-05 22:41:44 +08:00
|
|
|
}
|