mirror of
https://github.com/hibiken/asynq.git
synced 2026-07-01 15:16:17 +08:00
Compare commits
64 Commits
sohail/rel
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42ec936c22 | ||
|
|
785bb7208c | ||
|
|
3a7ec97482 | ||
|
|
d671a4c427 | ||
|
|
f81d452160 | ||
|
|
1af926b147 | ||
|
|
23905a286f | ||
|
|
a32ac05d09 | ||
|
|
68f03688e3 | ||
|
|
06a06970d6 | ||
|
|
5586efeae7 | ||
|
|
dd3c923f44 | ||
|
|
f81c78e68d | ||
|
|
f8d6677814 | ||
|
|
07898eade0 | ||
|
|
5216f1c3be | ||
|
|
dbfdfbac5a | ||
|
|
5c391f3ffb | ||
|
|
7ae0b3fe22 | ||
|
|
71ebcfa129 | ||
|
|
4e62d7e29d | ||
|
|
f919a605d5 | ||
|
|
2fd155e31d | ||
|
|
d704b68a42 | ||
|
|
a8db5b5571 | ||
|
|
e4248e2749 | ||
|
|
c4876e7247 | ||
|
|
dd2c3de356 | ||
|
|
ff887e1f89 | ||
|
|
5de9b1faf0 | ||
|
|
8261a03f0d | ||
|
|
74c47eb8bb | ||
|
|
e9037f003d | ||
|
|
604175e6ca | ||
|
|
1831a07efe | ||
|
|
d64f0b7ed0 | ||
|
|
a889ef0b08 | ||
|
|
093ba04266 | ||
|
|
c327bc40a2 | ||
|
|
ea0c6e93f0 | ||
|
|
489e21920b | ||
|
|
043dcfbf56 | ||
|
|
02907551b4 | ||
|
|
127fac2e90 | ||
|
|
106c07adaa | ||
|
|
1c7195ff1a | ||
|
|
12cbba4926 | ||
|
|
80479b528d | ||
|
|
e14c312fe3 | ||
|
|
ad1f587403 | ||
|
|
8b32b38fd5 | ||
|
|
96a84fac0c | ||
|
|
d2c207fbb8 | ||
|
|
1a7c61ac49 | ||
|
|
87375b5534 | ||
|
|
ffd75ebb5f | ||
|
|
d64fd328cb | ||
|
|
4f00f52c1d | ||
|
|
580d69e88f | ||
|
|
c97652d408 | ||
|
|
4644d37ef4 | ||
|
|
45c0fc6ad9 | ||
|
|
fd3eb86d95 | ||
|
|
3dbda60333 |
13
.github/workflows/benchstat.yml
vendored
13
.github/workflows/benchstat.yml
vendored
@@ -9,6 +9,7 @@ on: [pull_request]
|
||||
jobs:
|
||||
incoming:
|
||||
runs-on: ubuntu-latest
|
||||
if: false
|
||||
services:
|
||||
redis:
|
||||
image: redis:7
|
||||
@@ -24,13 +25,14 @@ jobs:
|
||||
- name: Benchmark
|
||||
run: go test -run=^$ -bench=. -count=5 -timeout=60m ./... | tee -a new.txt
|
||||
- name: Upload Benchmark
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bench-incoming
|
||||
path: new.txt
|
||||
|
||||
current:
|
||||
runs-on: ubuntu-latest
|
||||
if: false
|
||||
services:
|
||||
redis:
|
||||
image: redis:7
|
||||
@@ -48,13 +50,14 @@ jobs:
|
||||
- name: Benchmark
|
||||
run: go test -run=^$ -bench=. -count=5 -timeout=60m ./... | tee -a old.txt
|
||||
- name: Upload Benchmark
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bench-current
|
||||
path: old.txt
|
||||
|
||||
benchstat:
|
||||
needs: [incoming, current]
|
||||
if: false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -66,17 +69,17 @@ jobs:
|
||||
- name: Install benchstat
|
||||
run: go get -u golang.org/x/perf/cmd/benchstat
|
||||
- name: Download Incoming
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bench-incoming
|
||||
- name: Download Current
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bench-current
|
||||
- name: Benchstat Results
|
||||
run: benchstat old.txt new.txt | tee -a benchstat.txt
|
||||
- name: Upload benchstat results
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: benchstat
|
||||
path: benchstat.txt
|
||||
|
||||
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
go-version: [1.22.x, 1.23.x]
|
||||
go-version: [1.24.x, 1.25.x]
|
||||
runs-on: ${{ matrix.os }}
|
||||
services:
|
||||
redis:
|
||||
@@ -39,13 +39,13 @@ jobs:
|
||||
run: go test -run=^$ -bench=. -loglevel=debug ./...
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v1
|
||||
uses: codecov/codecov-action@v5
|
||||
|
||||
build-tool:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
go-version: [1.22.x, 1.23.x]
|
||||
go-version: [1.24.x, 1.25.x]
|
||||
runs-on: ${{ matrix.os }}
|
||||
services:
|
||||
redis:
|
||||
@@ -70,6 +70,7 @@ jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
if: false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
39
CHANGELOG.md
39
CHANGELOG.md
@@ -7,6 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.26.0] - 2026-02-03
|
||||
|
||||
### Upgrades
|
||||
- Prepare CI for Go 1.24.x and 1.25.x (commit: e9037f0)
|
||||
|
||||
### Added
|
||||
- Add Headers support to tasks (PR: https://github.com/hibiken/asynq/pull/1070)
|
||||
- Add `--tls` option to dash command (PR: https://github.com/hibiken/asynq/pull/1073)
|
||||
- Add `--username` CLI flag for Redis ACL authentication (PR: https://github.com/hibiken/asynq/pull/1083)
|
||||
- Add `UpdateTaskPayload` method for inspector (PR: https://github.com/hibiken/asynq/pull/1042)
|
||||
|
||||
### Fixes
|
||||
- Fix: Correct error message text in ResultWriter.Write (PR: https://github.com/hibiken/asynq/pull/1054)
|
||||
- Fix: Wrap all fmt.Errorf errors with %w (PR: https://github.com/hibiken/asynq/pull/1047)
|
||||
- Fix: ServeMux.NotFoundHandler returns ErrHandlerNotFound error (PR: https://github.com/hibiken/asynq/pull/1031)
|
||||
|
||||
### Changed
|
||||
- Docs: Update server.go documentation (PR: https://github.com/hibiken/asynq/pull/1010)
|
||||
- Chore: Fix godoc comment (PR: https://github.com/hibiken/asynq/pull/1009)
|
||||
|
||||
## [0.25.1] - 2024-12-11
|
||||
|
||||
### Upgrades
|
||||
|
||||
* Some packages
|
||||
|
||||
### Added
|
||||
|
||||
* Add `HeartbeatInterval` option to the scheduler (PR: https://github.com/hibiken/asynq/pull/956)
|
||||
* Add `RedisUniversalClient` support to periodic task manager (PR: https://github.com/hibiken/asynq/pull/958)
|
||||
* Add `--insecure` flag to CLI dash command (PR: https://github.com/hibiken/asynq/pull/980)
|
||||
* Add logging for registration errors (PR: https://github.com/hibiken/asynq/pull/657)
|
||||
|
||||
### Fixes
|
||||
- Perf: Use string concat inplace of fmt.Sprintf in hotpath (PR: https://github.com/hibiken/asynq/pull/962)
|
||||
- Perf: Init map with size (PR: https://github.com/hibiken/asynq/pull/673)
|
||||
- Fix: `Scheduler` and `PeriodicTaskManager` graceful shutdown (PR: https://github.com/hibiken/asynq/pull/977)
|
||||
- Fix: `Server` graceful shutdown on UNIX systems (PR: https://github.com/hibiken/asynq/pull/982)
|
||||
|
||||
## [0.25.0] - 2024-10-29
|
||||
|
||||
### Upgrades
|
||||
|
||||
@@ -156,7 +156,7 @@ func (a *aggregator) aggregate(t time.Time) {
|
||||
}
|
||||
tasks := make([]*Task, len(msgs))
|
||||
for i, m := range msgs {
|
||||
tasks[i] = NewTask(m.Type, m.Payload)
|
||||
tasks[i] = NewTaskWithHeaders(m.Type, m.Payload, m.Headers)
|
||||
}
|
||||
aggregatedTask := a.ga.Aggregate(gname, tasks)
|
||||
ctx, cancel := context.WithDeadline(context.Background(), deadline)
|
||||
|
||||
56
asynq.go
56
asynq.go
@@ -8,14 +8,15 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/hibiken/asynq/internal/base"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// Task represents a unit of work to be performed.
|
||||
@@ -26,6 +27,9 @@ type Task struct {
|
||||
// payload holds data needed to perform the task.
|
||||
payload []byte
|
||||
|
||||
// headers holds additional metadata for the task.
|
||||
headers map[string]string
|
||||
|
||||
// opts holds options for the task.
|
||||
opts []Option
|
||||
|
||||
@@ -33,8 +37,10 @@ type Task struct {
|
||||
w *ResultWriter
|
||||
}
|
||||
|
||||
func (t *Task) Type() string { return t.typename }
|
||||
func (t *Task) Payload() []byte { return t.payload }
|
||||
func (t *Task) Type() string { return t.typename }
|
||||
func (t *Task) Payload() []byte { return t.payload }
|
||||
func (t *Task) Headers() map[string]string { return t.headers }
|
||||
func (t *Task) Options() []Option { return t.opts }
|
||||
|
||||
// ResultWriter returns a pointer to the ResultWriter associated with the task.
|
||||
//
|
||||
@@ -48,6 +54,21 @@ func NewTask(typename string, payload []byte, opts ...Option) *Task {
|
||||
return &Task{
|
||||
typename: typename,
|
||||
payload: payload,
|
||||
headers: nil,
|
||||
opts: opts,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTaskWithHeaders returns a new Task given a type name, payload data, and headers.
|
||||
// Options can be passed to configure task processing behavior.
|
||||
// TODO: In the next major (breaking) release, fold this functionality into NewTask
|
||||
//
|
||||
// so that headers are supported directly. After that, remove this method.
|
||||
func NewTaskWithHeaders(typename string, payload []byte, headers map[string]string, opts ...Option) *Task {
|
||||
return &Task{
|
||||
typename: typename,
|
||||
payload: payload,
|
||||
headers: maps.Clone(headers),
|
||||
opts: opts,
|
||||
}
|
||||
}
|
||||
@@ -57,6 +78,7 @@ func newTask(typename string, payload []byte, w *ResultWriter) *Task {
|
||||
return &Task{
|
||||
typename: typename,
|
||||
payload: payload,
|
||||
headers: make(map[string]string),
|
||||
w: w,
|
||||
}
|
||||
}
|
||||
@@ -75,6 +97,9 @@ type TaskInfo struct {
|
||||
// Payload is the payload data of the task.
|
||||
Payload []byte
|
||||
|
||||
// Headers holds additional metadata for the task.
|
||||
Headers map[string]string
|
||||
|
||||
// State indicates the task state.
|
||||
State TaskState
|
||||
|
||||
@@ -145,6 +170,7 @@ func newTaskInfo(msg *base.TaskMessage, state base.TaskState, nextProcessAt time
|
||||
Queue: msg.Queue,
|
||||
Type: msg.Type,
|
||||
Payload: msg.Payload, // Do we need to make a copy?
|
||||
Headers: msg.Headers,
|
||||
MaxRetry: msg.Retry,
|
||||
Retried: msg.Retried,
|
||||
LastErr: msg.ErrorMsg,
|
||||
@@ -442,14 +468,15 @@ func (opt RedisClusterClientOpt) MakeRedisClient() interface{} {
|
||||
//
|
||||
// Three URI schemes are supported, which are redis:, rediss:, redis-socket:, and redis-sentinel:.
|
||||
// Supported formats are:
|
||||
// redis://[:password@]host[:port][/dbnumber]
|
||||
// rediss://[:password@]host[:port][/dbnumber]
|
||||
// redis-socket://[:password@]path[?db=dbnumber]
|
||||
// redis-sentinel://[:password@]host1[:port][,host2:[:port]][,hostN:[:port]][?master=masterName]
|
||||
//
|
||||
// redis://[:password@]host[:port][/dbnumber]
|
||||
// rediss://[:password@]host[:port][/dbnumber]
|
||||
// redis-socket://[:password@]path[?db=dbnumber]
|
||||
// redis-sentinel://[:password@]host1[:port][,host2:[:port]][,hostN:[:port]][/dbnumber][?master=masterName]
|
||||
func ParseRedisURI(uri string) (RedisConnOpt, error) {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("asynq: could not parse redis uri: %v", err)
|
||||
return nil, fmt.Errorf("asynq: could not parse redis uri: %w", err)
|
||||
}
|
||||
switch u.Scheme {
|
||||
case "redis", "rediss":
|
||||
@@ -519,11 +546,20 @@ func parseRedisSocketURI(u *url.URL) (RedisConnOpt, error) {
|
||||
func parseRedisSentinelURI(u *url.URL) (RedisConnOpt, error) {
|
||||
addrs := strings.Split(u.Host, ",")
|
||||
master := u.Query().Get("master")
|
||||
var db int
|
||||
var err error
|
||||
if len(u.Path) > 0 {
|
||||
xs := strings.Split(strings.Trim(u.Path, "/"), "/")
|
||||
db, err = strconv.Atoi(xs[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("asynq: could not parse redis sentinel uri: database number should be the first segment of the path")
|
||||
}
|
||||
}
|
||||
var password string
|
||||
if v, ok := u.User.Password(); ok {
|
||||
password = v
|
||||
}
|
||||
return RedisFailoverClientOpt{MasterName: master, SentinelAddrs: addrs, SentinelPassword: password}, nil
|
||||
return RedisFailoverClientOpt{MasterName: master, SentinelAddrs: addrs, SentinelPassword: password, DB: db}, nil
|
||||
}
|
||||
|
||||
// ResultWriter is a client interface to write result data for a task.
|
||||
@@ -539,7 +575,7 @@ type ResultWriter struct {
|
||||
func (w *ResultWriter) Write(data []byte) (n int, err error) {
|
||||
select {
|
||||
case <-w.ctx.Done():
|
||||
return 0, fmt.Errorf("failed to result task result: %v", w.ctx.Err())
|
||||
return 0, fmt.Errorf("failed to write task result: %w", w.ctx.Err())
|
||||
default:
|
||||
}
|
||||
return w.broker.WriteResult(w.qname, w.id, data)
|
||||
|
||||
@@ -10,12 +10,13 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/hibiken/asynq/internal/log"
|
||||
h "github.com/hibiken/asynq/internal/testutil"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
//============================================================================
|
||||
@@ -148,6 +149,23 @@ func TestParseRedisURI(t *testing.T) {
|
||||
SentinelPassword: "mypassword",
|
||||
},
|
||||
},
|
||||
{
|
||||
"redis-sentinel://localhost:5000,localhost:5001,localhost:5002/3?master=mymaster",
|
||||
RedisFailoverClientOpt{
|
||||
MasterName: "mymaster",
|
||||
SentinelAddrs: []string{"localhost:5000", "localhost:5001", "localhost:5002"},
|
||||
DB: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
"redis-sentinel://:mypassword@localhost:5000,localhost:5001,localhost:5002/7?master=mymaster",
|
||||
RedisFailoverClientOpt{
|
||||
MasterName: "mymaster",
|
||||
SentinelAddrs: []string{"localhost:5000", "localhost:5001", "localhost:5002"},
|
||||
SentinelPassword: "mypassword",
|
||||
DB: 7,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
@@ -188,6 +206,10 @@ func TestParseRedisURIErrors(t *testing.T) {
|
||||
"non integer for db numbers for socket",
|
||||
"redis-socket:///some/path/to/redis?db=one",
|
||||
},
|
||||
{
|
||||
"non integer for db number for sentinel",
|
||||
"redis-sentinel://localhost:5000/abc?master=mymaster",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
@@ -198,3 +220,30 @@ func TestParseRedisURIErrors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskOptions(t *testing.T) {
|
||||
opts := []Option{
|
||||
MaxRetry(3),
|
||||
Queue("critical"),
|
||||
Timeout(5 * time.Minute),
|
||||
}
|
||||
task := NewTask("mytask", []byte("payload"), opts...)
|
||||
|
||||
got := task.Options()
|
||||
if len(got) != len(opts) {
|
||||
t.Fatalf("task.Options() returned %d options, want %d", len(got), len(opts))
|
||||
}
|
||||
for i, o := range opts {
|
||||
if got[i].String() != o.String() {
|
||||
t.Errorf("task.Options()[%d] = %v, want %v", i, got[i], o)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskOptionsNil(t *testing.T) {
|
||||
task := NewTask("mytask", []byte("payload"))
|
||||
got := task.Options()
|
||||
if got != nil {
|
||||
t.Errorf("task.Options() = %v, want nil for task with no options", got)
|
||||
}
|
||||
}
|
||||
|
||||
173
client.go
173
client.go
@@ -6,7 +6,9 @@ package asynq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -60,6 +62,7 @@ const (
|
||||
TaskIDOpt
|
||||
RetentionOpt
|
||||
GroupOpt
|
||||
HeaderOpt
|
||||
)
|
||||
|
||||
// Option specifies the task processing behavior.
|
||||
@@ -86,6 +89,7 @@ type (
|
||||
processInOption time.Duration
|
||||
retentionOption time.Duration
|
||||
groupOption string
|
||||
headerOption [2]string
|
||||
)
|
||||
|
||||
// MaxRetry returns an option to specify the max number of times
|
||||
@@ -217,6 +221,27 @@ func (name groupOption) String() string { return fmt.Sprintf("Group(%q)", st
|
||||
func (name groupOption) Type() OptionType { return GroupOpt }
|
||||
func (name groupOption) Value() interface{} { return string(name) }
|
||||
|
||||
// Header returns an option to associate the key-value header to the task.
|
||||
//
|
||||
// This option is composable with other Client options and can be used together
|
||||
// with other options like MaxRetry, Queue, etc. For use cases where headers
|
||||
// need to be combined with other options, using Header option is recommended.
|
||||
//
|
||||
// Alternatively, NewTaskWithHeaders can be used to create a task with headers
|
||||
// directly, which may be preferable when headers are an intrinsic part of the
|
||||
// task definition rather than enqueue-time configuration.
|
||||
func Header(key, value string) Option {
|
||||
return headerOption{key, value}
|
||||
}
|
||||
|
||||
func (h headerOption) String() string {
|
||||
var bytes []byte
|
||||
bytes, _ = json.Marshal(h)
|
||||
return fmt.Sprintf("Header(%s)", bytes)
|
||||
}
|
||||
func (h headerOption) Type() OptionType { return HeaderOpt }
|
||||
func (h headerOption) Value() interface{} { return [2]string{h[0], h[1]} }
|
||||
|
||||
// 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.
|
||||
@@ -237,6 +262,7 @@ type option struct {
|
||||
processAt time.Time
|
||||
retention time.Duration
|
||||
group string
|
||||
headers map[string]string
|
||||
}
|
||||
|
||||
// composeOptions merges user provided options into the default options
|
||||
@@ -251,6 +277,7 @@ func composeOptions(opts ...Option) (option, error) {
|
||||
timeout: 0, // do not set to defaultTimeout here
|
||||
deadline: time.Time{},
|
||||
processAt: time.Now(),
|
||||
headers: make(map[string]string),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
switch opt := opt.(type) {
|
||||
@@ -290,6 +317,9 @@ func composeOptions(opts ...Option) (option, error) {
|
||||
return option{}, errors.New("group key cannot be empty")
|
||||
}
|
||||
res.group = key
|
||||
case headerOption:
|
||||
key, value := opt[0], opt[1]
|
||||
res.headers[key] = value
|
||||
default:
|
||||
// ignore unexpected option
|
||||
}
|
||||
@@ -385,6 +415,7 @@ func (c *Client) EnqueueContext(ctx context.Context, task *Task, opts ...Option)
|
||||
ID: opt.taskID,
|
||||
Type: task.Type(),
|
||||
Payload: task.Payload(),
|
||||
Headers: maps.Clone(task.Headers()),
|
||||
Queue: opt.queue,
|
||||
Retry: opt.retry,
|
||||
Deadline: deadline.Unix(),
|
||||
@@ -393,6 +424,12 @@ func (c *Client) EnqueueContext(ctx context.Context, task *Task, opts ...Option)
|
||||
GroupKey: opt.group,
|
||||
Retention: int64(opt.retention.Seconds()),
|
||||
}
|
||||
if len(opt.headers) > 0 {
|
||||
if msg.Headers == nil {
|
||||
msg.Headers = make(map[string]string)
|
||||
}
|
||||
maps.Copy(msg.Headers, opt.headers)
|
||||
}
|
||||
now := time.Now()
|
||||
var state base.TaskState
|
||||
if opt.processAt.After(now) {
|
||||
@@ -419,6 +456,142 @@ func (c *Client) EnqueueContext(ctx context.Context, task *Task, opts ...Option)
|
||||
return newTaskInfo(msg, state, opt.processAt, nil), nil
|
||||
}
|
||||
|
||||
// BatchEnqueueResult holds the result of enqueuing a single task within a batch.
|
||||
type BatchEnqueueResult struct {
|
||||
TaskInfo *TaskInfo
|
||||
Err error
|
||||
}
|
||||
|
||||
// BatchEnqueueContext enqueues multiple tasks in a single Redis pipeline round-trip,
|
||||
// returning a per-task result slice aligned with the input tasks slice.
|
||||
//
|
||||
// # Atomicity Guarantees
|
||||
//
|
||||
// There is no all-or-nothing guarantee across the batch. Each task is executed as
|
||||
// an independent Lua script inside a Redis pipeline. Individual scripts are atomic
|
||||
// (the existence check, hash write, and list/sorted-set push for one task cannot
|
||||
// be partially applied), but the pipeline as a whole is not wrapped in a
|
||||
// MULTI/EXEC transaction. This means:
|
||||
//
|
||||
// - Partial success is possible: some tasks may be enqueued while others are not.
|
||||
// - A task whose ID already exists in Redis is silently skipped (treated as a
|
||||
// no-op by the Lua script), and its result will still show success.
|
||||
// - If the Redis pipeline call itself fails (e.g. connection lost, context
|
||||
// cancelled), every task that passed client-side validation receives that
|
||||
// error — none of them can be assumed to have been enqueued.
|
||||
//
|
||||
// # Validation Errors (pre-pipeline)
|
||||
//
|
||||
// The following are caught before any Redis call and rejected in the
|
||||
// corresponding BatchEnqueueResult.Err without affecting other tasks:
|
||||
//
|
||||
// - nil task
|
||||
// - empty task type name
|
||||
// - invalid options
|
||||
// - group tasks (not supported in batch mode)
|
||||
// - unique tasks (not supported in batch mode)
|
||||
//
|
||||
// # Supported Task Types
|
||||
//
|
||||
// Immediate and scheduled (via [ProcessAt] or [ProcessIn]) tasks are supported.
|
||||
// Group and unique tasks are rejected as described above.
|
||||
func (c *Client) BatchEnqueueContext(ctx context.Context, tasks []*Task, opts ...Option) []BatchEnqueueResult {
|
||||
results := make([]BatchEnqueueResult, len(tasks))
|
||||
if len(tasks) == 0 {
|
||||
return results
|
||||
}
|
||||
|
||||
type itemMeta struct {
|
||||
state base.TaskState
|
||||
processAt time.Time
|
||||
}
|
||||
|
||||
items := make([]base.BatchEnqueueItem, 0, len(tasks))
|
||||
itemIndexes := make([]int, 0, len(tasks))
|
||||
itemMetas := make([]itemMeta, 0, len(tasks))
|
||||
|
||||
for i, task := range tasks {
|
||||
if task == nil {
|
||||
results[i] = BatchEnqueueResult{Err: fmt.Errorf("task cannot be nil")}
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(task.Type()) == "" {
|
||||
results[i] = BatchEnqueueResult{Err: fmt.Errorf("task typename cannot be empty")}
|
||||
continue
|
||||
}
|
||||
merged := append(task.opts, opts...)
|
||||
opt, err := composeOptions(merged...)
|
||||
if err != nil {
|
||||
results[i] = BatchEnqueueResult{Err: err}
|
||||
continue
|
||||
}
|
||||
if opt.group != "" {
|
||||
results[i] = BatchEnqueueResult{Err: fmt.Errorf("batch enqueue does not support group tasks")}
|
||||
continue
|
||||
}
|
||||
if opt.uniqueTTL > 0 {
|
||||
results[i] = BatchEnqueueResult{Err: fmt.Errorf("batch enqueue does not support unique tasks")}
|
||||
continue
|
||||
}
|
||||
deadline := noDeadline
|
||||
if !opt.deadline.IsZero() {
|
||||
deadline = opt.deadline
|
||||
}
|
||||
timeout := noTimeout
|
||||
if opt.timeout != 0 {
|
||||
timeout = opt.timeout
|
||||
}
|
||||
if deadline.Equal(noDeadline) && timeout == noTimeout {
|
||||
timeout = defaultTimeout
|
||||
}
|
||||
msg := &base.TaskMessage{
|
||||
ID: opt.taskID,
|
||||
Type: task.Type(),
|
||||
Payload: task.Payload(),
|
||||
Headers: task.Headers(),
|
||||
Queue: opt.queue,
|
||||
Retry: opt.retry,
|
||||
Deadline: deadline.Unix(),
|
||||
Timeout: int64(timeout.Seconds()),
|
||||
Retention: int64(opt.retention.Seconds()),
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
scheduled := opt.processAt.After(now)
|
||||
|
||||
item := base.BatchEnqueueItem{Msg: msg}
|
||||
var meta itemMeta
|
||||
if scheduled {
|
||||
item.ProcessAt = opt.processAt
|
||||
meta = itemMeta{state: base.TaskStateScheduled, processAt: opt.processAt}
|
||||
} else {
|
||||
meta = itemMeta{state: base.TaskStatePending, processAt: now}
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
itemIndexes = append(itemIndexes, i)
|
||||
itemMetas = append(itemMetas, meta)
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
return results
|
||||
}
|
||||
|
||||
_, err := c.broker.BatchEnqueue(ctx, items)
|
||||
if err != nil {
|
||||
for _, idx := range itemIndexes {
|
||||
results[idx] = BatchEnqueueResult{Err: err}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
for j, idx := range itemIndexes {
|
||||
info := newTaskInfo(items[j].Msg, itemMetas[j].state, itemMetas[j].processAt, nil)
|
||||
results[idx] = BatchEnqueueResult{TaskInfo: info}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// Ping performs a ping against the redis connection.
|
||||
func (c *Client) Ping() error {
|
||||
return c.broker.Ping()
|
||||
|
||||
742
client_test.go
742
client_test.go
@@ -13,6 +13,8 @@ import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/hibiken/asynq/internal/base"
|
||||
"github.com/hibiken/asynq/internal/rdb"
|
||||
"github.com/hibiken/asynq/internal/testbroker"
|
||||
h "github.com/hibiken/asynq/internal/testutil"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
@@ -1191,3 +1193,743 @@ func TestClientEnqueueUniqueWithProcessAtOption(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientEnqueueWithHeaders(t *testing.T) {
|
||||
r := setup(t)
|
||||
client := NewClient(getRedisConnOpt(t))
|
||||
defer client.Close()
|
||||
|
||||
now := time.Now()
|
||||
headers := map[string]string{
|
||||
"user-id": "123",
|
||||
"request-id": "abc-def-ghi",
|
||||
"priority": "high",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
desc string
|
||||
task *Task
|
||||
opts []Option
|
||||
wantInfo *TaskInfo
|
||||
wantPending map[string][]*base.TaskMessage
|
||||
}{
|
||||
{
|
||||
desc: "Task with headers",
|
||||
task: NewTaskWithHeaders("send_email", h.JSON(map[string]interface{}{"to": "user@example.com"}), headers),
|
||||
opts: []Option{},
|
||||
wantInfo: &TaskInfo{
|
||||
Queue: "default",
|
||||
Type: "send_email",
|
||||
Payload: h.JSON(map[string]interface{}{"to": "user@example.com"}),
|
||||
Headers: headers,
|
||||
State: TaskStatePending,
|
||||
MaxRetry: defaultMaxRetry,
|
||||
Retried: 0,
|
||||
LastErr: "",
|
||||
LastFailedAt: time.Time{},
|
||||
Timeout: defaultTimeout,
|
||||
Deadline: time.Time{},
|
||||
NextProcessAt: now,
|
||||
},
|
||||
wantPending: map[string][]*base.TaskMessage{
|
||||
"default": {
|
||||
{
|
||||
Type: "send_email",
|
||||
Payload: h.JSON(map[string]interface{}{"to": "user@example.com"}),
|
||||
Headers: headers,
|
||||
Retry: defaultMaxRetry,
|
||||
Queue: "default",
|
||||
Timeout: int64(defaultTimeout.Seconds()),
|
||||
Deadline: noDeadline.Unix(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Task with empty headers",
|
||||
task: NewTaskWithHeaders("process_data", []byte("data"), map[string]string{}),
|
||||
opts: []Option{},
|
||||
wantInfo: &TaskInfo{
|
||||
Queue: "default",
|
||||
Type: "process_data",
|
||||
Payload: []byte("data"),
|
||||
Headers: map[string]string{},
|
||||
State: TaskStatePending,
|
||||
MaxRetry: defaultMaxRetry,
|
||||
Retried: 0,
|
||||
LastErr: "",
|
||||
LastFailedAt: time.Time{},
|
||||
Timeout: defaultTimeout,
|
||||
Deadline: time.Time{},
|
||||
NextProcessAt: now,
|
||||
},
|
||||
wantPending: map[string][]*base.TaskMessage{
|
||||
"default": {
|
||||
{
|
||||
Type: "process_data",
|
||||
Payload: []byte("data"),
|
||||
Headers: nil,
|
||||
Retry: defaultMaxRetry,
|
||||
Queue: "default",
|
||||
Timeout: int64(defaultTimeout.Seconds()),
|
||||
Deadline: noDeadline.Unix(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Task with nil headers",
|
||||
task: NewTaskWithHeaders("cleanup", nil, nil),
|
||||
opts: []Option{},
|
||||
wantInfo: &TaskInfo{
|
||||
Queue: "default",
|
||||
Type: "cleanup",
|
||||
Payload: nil,
|
||||
Headers: nil,
|
||||
State: TaskStatePending,
|
||||
MaxRetry: defaultMaxRetry,
|
||||
Retried: 0,
|
||||
LastErr: "",
|
||||
LastFailedAt: time.Time{},
|
||||
Timeout: defaultTimeout,
|
||||
Deadline: time.Time{},
|
||||
NextProcessAt: now,
|
||||
},
|
||||
wantPending: map[string][]*base.TaskMessage{
|
||||
"default": {
|
||||
{
|
||||
Type: "cleanup",
|
||||
Payload: nil,
|
||||
Headers: nil,
|
||||
Retry: defaultMaxRetry,
|
||||
Queue: "default",
|
||||
Timeout: int64(defaultTimeout.Seconds()),
|
||||
Deadline: noDeadline.Unix(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Task with headers and custom options",
|
||||
task: NewTaskWithHeaders("notify", []byte("notification"), map[string]string{"channel": "email"}),
|
||||
opts: []Option{MaxRetry(5), Queue("notifications")},
|
||||
wantInfo: &TaskInfo{
|
||||
Queue: "notifications",
|
||||
Type: "notify",
|
||||
Payload: []byte("notification"),
|
||||
Headers: map[string]string{"channel": "email"},
|
||||
State: TaskStatePending,
|
||||
MaxRetry: 5,
|
||||
Retried: 0,
|
||||
LastErr: "",
|
||||
LastFailedAt: time.Time{},
|
||||
Timeout: defaultTimeout,
|
||||
Deadline: time.Time{},
|
||||
NextProcessAt: now,
|
||||
},
|
||||
wantPending: map[string][]*base.TaskMessage{
|
||||
"notifications": {
|
||||
{
|
||||
Type: "notify",
|
||||
Payload: []byte("notification"),
|
||||
Headers: map[string]string{"channel": "email"},
|
||||
Retry: 5,
|
||||
Queue: "notifications",
|
||||
Timeout: int64(defaultTimeout.Seconds()),
|
||||
Deadline: noDeadline.Unix(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Task with header option",
|
||||
task: NewTask("store_data", []byte("data"), Header("channel", "email"), Header("user-id", "bob1234")),
|
||||
opts: []Option{},
|
||||
wantInfo: &TaskInfo{
|
||||
Queue: "default",
|
||||
Type: "store_data",
|
||||
Payload: []byte("data"),
|
||||
Headers: map[string]string{"channel": "email", "user-id": "bob1234"},
|
||||
State: TaskStatePending,
|
||||
MaxRetry: 25,
|
||||
Retried: 0,
|
||||
LastErr: "",
|
||||
LastFailedAt: time.Time{},
|
||||
Timeout: defaultTimeout,
|
||||
Deadline: time.Time{},
|
||||
NextProcessAt: now,
|
||||
},
|
||||
wantPending: map[string][]*base.TaskMessage{
|
||||
"default": {
|
||||
{
|
||||
Type: "store_data",
|
||||
Payload: []byte("data"),
|
||||
Headers: map[string]string{"channel": "email", "user-id": "bob1234"},
|
||||
Retry: 25,
|
||||
Queue: "default",
|
||||
Timeout: int64(defaultTimeout.Seconds()),
|
||||
Deadline: noDeadline.Unix(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Enqueue task with header option",
|
||||
task: NewTask("store_data", []byte("data")),
|
||||
opts: []Option{Header("channel", "email"), Header("user-id", "bob1234"), MaxRetry(5)},
|
||||
wantInfo: &TaskInfo{
|
||||
Queue: "default",
|
||||
Type: "store_data",
|
||||
Payload: []byte("data"),
|
||||
Headers: map[string]string{"channel": "email", "user-id": "bob1234"},
|
||||
State: TaskStatePending,
|
||||
MaxRetry: 5,
|
||||
Retried: 0,
|
||||
LastErr: "",
|
||||
LastFailedAt: time.Time{},
|
||||
Timeout: defaultTimeout,
|
||||
Deadline: time.Time{},
|
||||
NextProcessAt: now,
|
||||
},
|
||||
wantPending: map[string][]*base.TaskMessage{
|
||||
"default": {
|
||||
{
|
||||
Type: "store_data",
|
||||
Payload: []byte("data"),
|
||||
Headers: map[string]string{"channel": "email", "user-id": "bob1234"},
|
||||
Retry: 5,
|
||||
Queue: "default",
|
||||
Timeout: int64(defaultTimeout.Seconds()),
|
||||
Deadline: noDeadline.Unix(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
h.FlushDB(t, r)
|
||||
|
||||
gotInfo, err := client.Enqueue(tc.task, tc.opts...)
|
||||
if err != nil {
|
||||
t.Errorf("%s: Enqueue failed: %v", tc.desc, err)
|
||||
continue
|
||||
}
|
||||
|
||||
cmpOptions := []cmp.Option{
|
||||
cmpopts.IgnoreFields(TaskInfo{}, "ID"),
|
||||
cmpopts.EquateApproxTime(500 * time.Millisecond),
|
||||
}
|
||||
if diff := cmp.Diff(tc.wantInfo, gotInfo, cmpOptions...); diff != "" {
|
||||
t.Errorf("%s;\nEnqueue(task) returned %v, want %v; (-want,+got)\n%s",
|
||||
tc.desc, gotInfo, tc.wantInfo, diff)
|
||||
}
|
||||
|
||||
for qname, want := range tc.wantPending {
|
||||
got := h.GetPendingMessages(t, r, qname)
|
||||
if diff := cmp.Diff(want, got, h.IgnoreIDOpt); diff != "" {
|
||||
t.Errorf("%s;\nmismatch found in %q; (-want,+got)\n%s", tc.desc, base.PendingKey(qname), diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientEnqueueWithHeadersScheduled(t *testing.T) {
|
||||
r := setup(t)
|
||||
client := NewClient(getRedisConnOpt(t))
|
||||
defer client.Close()
|
||||
|
||||
now := time.Now()
|
||||
oneHourLater := now.Add(time.Hour)
|
||||
headers := map[string]string{
|
||||
"correlation-id": "xyz-123",
|
||||
"source": "api",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
desc string
|
||||
task *Task
|
||||
processAt time.Time
|
||||
opts []Option
|
||||
wantInfo *TaskInfo
|
||||
wantScheduled map[string][]base.Z
|
||||
}{
|
||||
{
|
||||
desc: "Schedule task with headers",
|
||||
task: NewTaskWithHeaders("scheduled_task", []byte("payload"), headers),
|
||||
processAt: oneHourLater,
|
||||
opts: []Option{},
|
||||
wantInfo: &TaskInfo{
|
||||
Queue: "default",
|
||||
Type: "scheduled_task",
|
||||
Payload: []byte("payload"),
|
||||
Headers: headers,
|
||||
State: TaskStateScheduled,
|
||||
MaxRetry: defaultMaxRetry,
|
||||
Retried: 0,
|
||||
LastErr: "",
|
||||
LastFailedAt: time.Time{},
|
||||
Timeout: defaultTimeout,
|
||||
Deadline: time.Time{},
|
||||
NextProcessAt: oneHourLater,
|
||||
},
|
||||
wantScheduled: map[string][]base.Z{
|
||||
"default": {
|
||||
{
|
||||
Message: &base.TaskMessage{
|
||||
Type: "scheduled_task",
|
||||
Payload: []byte("payload"),
|
||||
Headers: headers,
|
||||
Retry: defaultMaxRetry,
|
||||
Queue: "default",
|
||||
Timeout: int64(defaultTimeout.Seconds()),
|
||||
Deadline: noDeadline.Unix(),
|
||||
},
|
||||
Score: oneHourLater.Unix(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
h.FlushDB(t, r)
|
||||
|
||||
opts := append(tc.opts, ProcessAt(tc.processAt))
|
||||
gotInfo, err := client.Enqueue(tc.task, opts...)
|
||||
if err != nil {
|
||||
t.Errorf("%s: Enqueue failed: %v", tc.desc, err)
|
||||
continue
|
||||
}
|
||||
|
||||
cmpOptions := []cmp.Option{
|
||||
cmpopts.IgnoreFields(TaskInfo{}, "ID"),
|
||||
cmpopts.EquateApproxTime(500 * time.Millisecond),
|
||||
}
|
||||
if diff := cmp.Diff(tc.wantInfo, gotInfo, cmpOptions...); diff != "" {
|
||||
t.Errorf("%s;\nEnqueue(task, ProcessAt(%v)) returned %v, want %v; (-want,+got)\n%s",
|
||||
tc.desc, tc.processAt, gotInfo, tc.wantInfo, diff)
|
||||
}
|
||||
|
||||
for qname, want := range tc.wantScheduled {
|
||||
gotScheduled := h.GetScheduledEntries(t, r, qname)
|
||||
if diff := cmp.Diff(want, gotScheduled, h.IgnoreIDOpt, cmpopts.EquateEmpty()); diff != "" {
|
||||
t.Errorf("%s;\nmismatch found in %q; (-want,+got)\n%s", tc.desc, base.ScheduledKey(qname), diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTaskWithHeaders(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
typename string
|
||||
payload []byte
|
||||
headers map[string]string
|
||||
opts []Option
|
||||
want *Task
|
||||
}{
|
||||
{
|
||||
desc: "Task with headers",
|
||||
typename: "test_task",
|
||||
payload: []byte("test payload"),
|
||||
headers: map[string]string{"key1": "value1", "key2": "value2"},
|
||||
opts: []Option{MaxRetry(3)},
|
||||
want: &Task{
|
||||
typename: "test_task",
|
||||
payload: []byte("test payload"),
|
||||
headers: map[string]string{"key1": "value1", "key2": "value2"},
|
||||
opts: []Option{MaxRetry(3)},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Task with empty headers",
|
||||
typename: "empty_headers",
|
||||
payload: nil,
|
||||
headers: map[string]string{},
|
||||
opts: nil,
|
||||
want: &Task{
|
||||
typename: "empty_headers",
|
||||
payload: nil,
|
||||
headers: map[string]string{},
|
||||
opts: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Task with nil headers",
|
||||
typename: "nil_headers",
|
||||
payload: []byte("data"),
|
||||
headers: nil,
|
||||
opts: []Option{Queue("test")},
|
||||
want: &Task{
|
||||
typename: "nil_headers",
|
||||
payload: []byte("data"),
|
||||
headers: nil,
|
||||
opts: []Option{Queue("test")},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
got := NewTaskWithHeaders(tc.typename, tc.payload, tc.headers, tc.opts...)
|
||||
|
||||
if got.Type() != tc.want.typename {
|
||||
t.Errorf("%s: Type() = %q, want %q", tc.desc, got.Type(), tc.want.typename)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.want.payload, got.Payload()); diff != "" {
|
||||
t.Errorf("%s: Payload() mismatch (-want,+got)\n%s", tc.desc, diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.want.headers, got.Headers()); diff != "" {
|
||||
t.Errorf("%s: Headers() mismatch (-want,+got)\n%s", tc.desc, diff)
|
||||
}
|
||||
|
||||
if tc.headers != nil && got.Headers() != nil {
|
||||
tc.headers["modified"] = "test"
|
||||
if _, exists := got.Headers()["modified"]; exists {
|
||||
t.Errorf("%s: Headers should be cloned, but modification affected task headers", tc.desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskHeadersMethod(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
task *Task
|
||||
want map[string]string
|
||||
wantNil bool
|
||||
}{
|
||||
{
|
||||
desc: "Task created with NewTask has nil headers",
|
||||
task: NewTask("test", []byte("data")),
|
||||
want: nil,
|
||||
wantNil: true,
|
||||
},
|
||||
{
|
||||
desc: "Task created with NewTaskWithHeaders has headers",
|
||||
task: NewTaskWithHeaders("test", []byte("data"), map[string]string{"key": "value"}),
|
||||
want: map[string]string{"key": "value"},
|
||||
},
|
||||
{
|
||||
desc: "Task created with empty headers",
|
||||
task: NewTaskWithHeaders("test", []byte("data"), map[string]string{}),
|
||||
want: map[string]string{},
|
||||
},
|
||||
{
|
||||
desc: "Task created with nil headers",
|
||||
task: NewTaskWithHeaders("test", []byte("data"), nil),
|
||||
want: nil,
|
||||
wantNil: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
got := tc.task.Headers()
|
||||
|
||||
if tc.wantNil {
|
||||
if got != nil {
|
||||
t.Errorf("%s: Headers() = %v, want nil", tc.desc, got)
|
||||
}
|
||||
} else {
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("%s: Headers() mismatch (-want,+got)\n%s", tc.desc, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientEnqueueWithHeadersAndGroup(t *testing.T) {
|
||||
r := setup(t)
|
||||
client := NewClient(getRedisConnOpt(t))
|
||||
defer client.Close()
|
||||
|
||||
now := time.Now()
|
||||
headers := map[string]string{
|
||||
"batch-id": "batch-123",
|
||||
"priority": "high",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
desc string
|
||||
task *Task
|
||||
opts []Option
|
||||
wantInfo *TaskInfo
|
||||
wantGroups map[string]map[string][]base.Z
|
||||
}{
|
||||
{
|
||||
desc: "Task with headers and group",
|
||||
task: NewTaskWithHeaders("batch_process", []byte("item1"), headers),
|
||||
opts: []Option{Group("batch-123")},
|
||||
wantInfo: &TaskInfo{
|
||||
Queue: "default",
|
||||
Group: "batch-123",
|
||||
Type: "batch_process",
|
||||
Payload: []byte("item1"),
|
||||
Headers: headers,
|
||||
State: TaskStateAggregating,
|
||||
MaxRetry: defaultMaxRetry,
|
||||
Retried: 0,
|
||||
LastErr: "",
|
||||
LastFailedAt: time.Time{},
|
||||
Timeout: defaultTimeout,
|
||||
Deadline: time.Time{},
|
||||
NextProcessAt: time.Time{},
|
||||
},
|
||||
wantGroups: map[string]map[string][]base.Z{
|
||||
"default": {
|
||||
"batch-123": {
|
||||
{
|
||||
Message: &base.TaskMessage{
|
||||
Type: "batch_process",
|
||||
Payload: []byte("item1"),
|
||||
Headers: headers,
|
||||
Retry: defaultMaxRetry,
|
||||
Queue: "default",
|
||||
Timeout: int64(defaultTimeout.Seconds()),
|
||||
Deadline: noDeadline.Unix(),
|
||||
GroupKey: "batch-123",
|
||||
},
|
||||
Score: now.Unix(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
h.FlushDB(t, r)
|
||||
|
||||
gotInfo, err := client.Enqueue(tc.task, tc.opts...)
|
||||
if err != nil {
|
||||
t.Errorf("%s: Enqueue failed: %v", tc.desc, err)
|
||||
continue
|
||||
}
|
||||
|
||||
cmpOptions := []cmp.Option{
|
||||
cmpopts.IgnoreFields(TaskInfo{}, "ID"),
|
||||
cmpopts.EquateApproxTime(500 * time.Millisecond),
|
||||
}
|
||||
if diff := cmp.Diff(tc.wantInfo, gotInfo, cmpOptions...); diff != "" {
|
||||
t.Errorf("%s;\nEnqueue(task) returned %v, want %v; (-want,+got)\n%s",
|
||||
tc.desc, gotInfo, tc.wantInfo, diff)
|
||||
}
|
||||
|
||||
for qname, groups := range tc.wantGroups {
|
||||
for groupKey, want := range groups {
|
||||
got := h.GetGroupEntries(t, r, qname, groupKey)
|
||||
if diff := cmp.Diff(want, got, h.IgnoreIDOpt, cmpopts.EquateEmpty()); diff != "" {
|
||||
t.Errorf("%s;\nmismatch found in %q; (-want,+got)\n%s", tc.desc, base.GroupKey(qname, groupKey), diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchEnqueueContext_ImmediateTasks(t *testing.T) {
|
||||
r := setup(t)
|
||||
client := NewClient(getRedisConnOpt(t))
|
||||
defer client.Close()
|
||||
|
||||
tasks := []*Task{
|
||||
NewTask("task1", []byte("payload1")),
|
||||
NewTask("task2", []byte("payload2")),
|
||||
NewTask("task3", []byte("payload3")),
|
||||
}
|
||||
|
||||
results := client.BatchEnqueueContext(context.Background(), tasks)
|
||||
if len(results) != 3 {
|
||||
t.Fatalf("BatchEnqueueContext returned %d results, want 3", len(results))
|
||||
}
|
||||
for i, res := range results {
|
||||
if res.Err != nil {
|
||||
t.Errorf("results[%d].Err = %v, want nil", i, res.Err)
|
||||
}
|
||||
if res.TaskInfo == nil {
|
||||
t.Errorf("results[%d].TaskInfo is nil, want non-nil", i)
|
||||
continue
|
||||
}
|
||||
if res.TaskInfo.Queue != "default" {
|
||||
t.Errorf("results[%d].TaskInfo.Queue = %q, want %q", i, res.TaskInfo.Queue, "default")
|
||||
}
|
||||
if res.TaskInfo.State != TaskStatePending {
|
||||
t.Errorf("results[%d].TaskInfo.State = %v, want %v", i, res.TaskInfo.State, TaskStatePending)
|
||||
}
|
||||
}
|
||||
|
||||
gotPending := h.GetPendingMessages(t, r, "default")
|
||||
if len(gotPending) != 3 {
|
||||
t.Errorf("len(pending) = %d, want 3", len(gotPending))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchEnqueueContext_ScheduledTask(t *testing.T) {
|
||||
r := setup(t)
|
||||
client := NewClient(getRedisConnOpt(t))
|
||||
defer client.Close()
|
||||
|
||||
future := time.Now().Add(1 * time.Hour)
|
||||
tasks := []*Task{
|
||||
NewTask("scheduled_task", []byte("payload"), ProcessAt(future)),
|
||||
}
|
||||
|
||||
results := client.BatchEnqueueContext(context.Background(), tasks)
|
||||
if len(results) != 1 {
|
||||
t.Fatalf("BatchEnqueueContext returned %d results, want 1", len(results))
|
||||
}
|
||||
if results[0].Err != nil {
|
||||
t.Fatalf("results[0].Err = %v, want nil", results[0].Err)
|
||||
}
|
||||
if results[0].TaskInfo == nil {
|
||||
t.Fatal("results[0].TaskInfo is nil, want non-nil")
|
||||
}
|
||||
if results[0].TaskInfo.State != TaskStateScheduled {
|
||||
t.Errorf("results[0].TaskInfo.State = %v, want %v", results[0].TaskInfo.State, TaskStateScheduled)
|
||||
}
|
||||
|
||||
gotScheduled := h.GetScheduledMessages(t, r, "default")
|
||||
if len(gotScheduled) != 1 {
|
||||
t.Errorf("len(scheduled) = %d, want 1", len(gotScheduled))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchEnqueueContext_MixedBatch(t *testing.T) {
|
||||
r := setup(t)
|
||||
client := NewClient(getRedisConnOpt(t))
|
||||
defer client.Close()
|
||||
|
||||
future := time.Now().Add(1 * time.Hour)
|
||||
tasks := []*Task{
|
||||
NewTask("immediate1", []byte("p1")),
|
||||
NewTask("scheduled1", []byte("p2"), ProcessAt(future)),
|
||||
NewTask("immediate2", []byte("p3")),
|
||||
NewTask("grouped1", []byte("p4"), Group("mygroup")),
|
||||
NewTask("immediate3", []byte("p5")),
|
||||
}
|
||||
|
||||
results := client.BatchEnqueueContext(context.Background(), tasks)
|
||||
if len(results) != 5 {
|
||||
t.Fatalf("BatchEnqueueContext returned %d results, want 5", len(results))
|
||||
}
|
||||
|
||||
// Immediate tasks (indices 0, 2, 4) should succeed with Pending state.
|
||||
for _, idx := range []int{0, 2, 4} {
|
||||
if results[idx].Err != nil {
|
||||
t.Errorf("results[%d].Err = %v, want nil (immediate task)", idx, results[idx].Err)
|
||||
}
|
||||
if results[idx].TaskInfo == nil {
|
||||
t.Errorf("results[%d].TaskInfo is nil, want non-nil", idx)
|
||||
continue
|
||||
}
|
||||
if results[idx].TaskInfo.State != TaskStatePending {
|
||||
t.Errorf("results[%d].TaskInfo.State = %v, want %v", idx, results[idx].TaskInfo.State, TaskStatePending)
|
||||
}
|
||||
}
|
||||
|
||||
// Scheduled task (index 1) should succeed with Scheduled state.
|
||||
if results[1].Err != nil {
|
||||
t.Errorf("results[1].Err = %v, want nil (scheduled task)", results[1].Err)
|
||||
}
|
||||
if results[1].TaskInfo != nil && results[1].TaskInfo.State != TaskStateScheduled {
|
||||
t.Errorf("results[1].TaskInfo.State = %v, want %v", results[1].TaskInfo.State, TaskStateScheduled)
|
||||
}
|
||||
|
||||
// Grouped task (index 3) should be rejected.
|
||||
if results[3].Err == nil {
|
||||
t.Error("results[3].Err is nil, want error for group task")
|
||||
}
|
||||
|
||||
gotPending := h.GetPendingMessages(t, r, "default")
|
||||
if len(gotPending) != 3 {
|
||||
t.Errorf("len(pending) = %d, want 3", len(gotPending))
|
||||
}
|
||||
|
||||
gotScheduled := h.GetScheduledMessages(t, r, "default")
|
||||
if len(gotScheduled) != 1 {
|
||||
t.Errorf("len(scheduled) = %d, want 1", len(gotScheduled))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchEnqueueContext_ValidationErrors(t *testing.T) {
|
||||
setup(t)
|
||||
client := NewClient(getRedisConnOpt(t))
|
||||
defer client.Close()
|
||||
|
||||
tests := []struct {
|
||||
desc string
|
||||
tasks []*Task
|
||||
opts []Option
|
||||
}{
|
||||
{
|
||||
desc: "nil task",
|
||||
tasks: []*Task{nil},
|
||||
},
|
||||
{
|
||||
desc: "empty task typename",
|
||||
tasks: []*Task{NewTask("", []byte("payload"))},
|
||||
},
|
||||
{
|
||||
desc: "blank task typename",
|
||||
tasks: []*Task{NewTask(" ", []byte("payload"))},
|
||||
},
|
||||
{
|
||||
desc: "invalid option: unique TTL less than 1s",
|
||||
tasks: []*Task{NewTask("foo", nil)},
|
||||
opts: []Option{Unique(300 * time.Millisecond)},
|
||||
},
|
||||
{
|
||||
desc: "group task rejected",
|
||||
tasks: []*Task{NewTask("foo", nil, Group("mygroup"))},
|
||||
},
|
||||
{
|
||||
desc: "unique task rejected",
|
||||
tasks: []*Task{NewTask("foo", nil, Unique(time.Hour))},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
results := client.BatchEnqueueContext(context.Background(), tc.tasks, tc.opts...)
|
||||
if len(results) != len(tc.tasks) {
|
||||
t.Errorf("%s: got %d results, want %d", tc.desc, len(results), len(tc.tasks))
|
||||
continue
|
||||
}
|
||||
for i, res := range results {
|
||||
if res.Err == nil {
|
||||
t.Errorf("%s: results[%d].Err = nil, want non-nil error", tc.desc, i)
|
||||
}
|
||||
if res.TaskInfo != nil {
|
||||
t.Errorf("%s: results[%d].TaskInfo = %v, want nil", tc.desc, i, res.TaskInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchEnqueueContext_BrokerError(t *testing.T) {
|
||||
r := rdb.NewRDB(setup(t))
|
||||
defer r.Close()
|
||||
testBroker := testbroker.NewTestBroker(r)
|
||||
client := &Client{broker: testBroker, sharedConnection: true}
|
||||
|
||||
tasks := []*Task{
|
||||
NewTask("task1", []byte("p1")),
|
||||
NewTask("task2", []byte("p2")),
|
||||
}
|
||||
|
||||
testBroker.Sleep()
|
||||
results := client.BatchEnqueueContext(context.Background(), tasks)
|
||||
testBroker.Wakeup()
|
||||
|
||||
if len(results) != 2 {
|
||||
t.Fatalf("BatchEnqueueContext returned %d results, want 2", len(results))
|
||||
}
|
||||
for i, res := range results {
|
||||
if res.Err == nil {
|
||||
t.Errorf("results[%d].Err = nil, want non-nil error when broker is down", i)
|
||||
}
|
||||
if res.TaskInfo != nil {
|
||||
t.Errorf("results[%d].TaskInfo = %v, want nil on broker error", i, res.TaskInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
81
doc.go
81
doc.go
@@ -8,41 +8,41 @@ Package asynq provides a framework for Redis based distrubted task queue.
|
||||
Asynq uses Redis as a message broker. To connect to redis,
|
||||
specify the connection using one of RedisConnOpt types.
|
||||
|
||||
redisConnOpt = asynq.RedisClientOpt{
|
||||
Addr: "127.0.0.1:6379",
|
||||
Password: "xxxxx",
|
||||
DB: 2,
|
||||
}
|
||||
redisConnOpt = asynq.RedisClientOpt{
|
||||
Addr: "127.0.0.1:6379",
|
||||
Password: "xxxxx",
|
||||
DB: 2,
|
||||
}
|
||||
|
||||
The Client is used to enqueue a task.
|
||||
|
||||
client := asynq.NewClient(redisConnOpt)
|
||||
|
||||
client := asynq.NewClient(redisConnOpt)
|
||||
// Task is created with two parameters: its type and payload.
|
||||
// Payload data is simply an array of bytes. It can be encoded in JSON, Protocol Buffer, Gob, etc.
|
||||
b, err := json.Marshal(ExamplePayload{UserID: 42})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Task is created with two parameters: its type and payload.
|
||||
// Payload data is simply an array of bytes. It can be encoded in JSON, Protocol Buffer, Gob, etc.
|
||||
b, err := json.Marshal(ExamplePayload{UserID: 42})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
task := asynq.NewTask("example", b)
|
||||
|
||||
task := asynq.NewTask("example", b)
|
||||
// Enqueue the task to be processed immediately.
|
||||
info, err := client.Enqueue(task)
|
||||
|
||||
// Enqueue the task to be processed immediately.
|
||||
info, err := client.Enqueue(task)
|
||||
|
||||
// Schedule the task to be processed after one minute.
|
||||
info, err = client.Enqueue(t, asynq.ProcessIn(1*time.Minute))
|
||||
// Schedule the task to be processed after one minute.
|
||||
info, err = client.Enqueue(t, asynq.ProcessIn(1*time.Minute))
|
||||
|
||||
The Server is used to run the task processing workers with a given
|
||||
handler.
|
||||
srv := asynq.NewServer(redisConnOpt, asynq.Config{
|
||||
Concurrency: 10,
|
||||
})
|
||||
|
||||
if err := srv.Run(handler); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
srv := asynq.NewServer(redisConnOpt, asynq.Config{
|
||||
Concurrency: 10,
|
||||
})
|
||||
|
||||
if err := srv.Run(handler); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
Handler is an interface type with a method which
|
||||
takes a task and returns an error. Handler should return nil if
|
||||
@@ -50,23 +50,24 @@ the processing is successful, otherwise return a non-nil error.
|
||||
If handler panics or returns a non-nil error, the task will be retried in the future.
|
||||
|
||||
Example of a type that implements the Handler interface.
|
||||
type TaskHandler struct {
|
||||
// ...
|
||||
}
|
||||
|
||||
func (h *TaskHandler) ProcessTask(ctx context.Context, task *asynq.Task) error {
|
||||
switch task.Type {
|
||||
case "example":
|
||||
var data ExamplePayload
|
||||
if err := json.Unmarshal(task.Payload(), &data); err != nil {
|
||||
return err
|
||||
}
|
||||
// perform task with the data
|
||||
type TaskHandler struct {
|
||||
// ...
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unexpected task type %q", task.Type)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (h *TaskHandler) ProcessTask(ctx context.Context, task *asynq.Task) error {
|
||||
switch task.Type {
|
||||
case "example":
|
||||
var data ExamplePayload
|
||||
if err := json.Unmarshal(task.Payload(), &data); err != nil {
|
||||
return err
|
||||
}
|
||||
// perform task with the data
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unexpected task type %q", task.Type)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
package asynq
|
||||
|
||||
@@ -123,7 +123,7 @@ func ExampleResultWriter() {
|
||||
res := []byte("task result data")
|
||||
n, err := task.ResultWriter().Write(res) // implements io.Writer
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write task result: %v", err)
|
||||
return fmt.Errorf("failed to write task result: %w", err)
|
||||
}
|
||||
log.Printf(" %d bytes written", n)
|
||||
return nil
|
||||
|
||||
16
go.mod
16
go.mod
@@ -1,20 +1,20 @@
|
||||
module github.com/hibiken/asynq
|
||||
|
||||
go 1.22
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/redis/go-redis/v9 v9.7.0
|
||||
github.com/redis/go-redis/v9 v9.14.1
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/spf13/cast v1.7.0
|
||||
github.com/spf13/cast v1.10.0
|
||||
go.uber.org/goleak v1.3.0
|
||||
golang.org/x/sys v0.26.0
|
||||
golang.org/x/time v0.7.0
|
||||
google.golang.org/protobuf v1.35.1
|
||||
golang.org/x/sys v0.46.0
|
||||
golang.org/x/time v0.14.0
|
||||
google.golang.org/protobuf v1.36.10
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
)
|
||||
|
||||
28
go.sum
28
go.sum
@@ -2,16 +2,16 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -20,23 +20,23 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/redis/go-redis/v9 v9.14.1 h1:nDCrEiJmfOWhD76xlaw+HXT0c9hfNWeXgl0vIRYSDvQ=
|
||||
github.com/redis/go-redis/v9 v9.14.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
74
inspector.go
74
inspector.go
@@ -5,6 +5,7 @@
|
||||
package asynq
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -245,7 +246,7 @@ func (i *Inspector) GetTaskInfo(queue, id string) (*TaskInfo, error) {
|
||||
case errors.IsTaskNotFound(err):
|
||||
return nil, fmt.Errorf("asynq: %w", ErrTaskNotFound)
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("asynq: %v", err)
|
||||
return nil, fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
return newTaskInfo(info.Message, info.State, info.NextProcessAt, info.Result), nil
|
||||
}
|
||||
@@ -316,7 +317,7 @@ func Page(n int) ListOption {
|
||||
// By default, it retrieves the first 30 tasks.
|
||||
func (i *Inspector) ListPendingTasks(queue string, opts ...ListOption) ([]*TaskInfo, error) {
|
||||
if err := base.ValidateQueueName(queue); err != nil {
|
||||
return nil, fmt.Errorf("asynq: %v", err)
|
||||
return nil, fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
opt := composeListOptions(opts...)
|
||||
pgn := rdb.Pagination{Size: opt.pageSize, Page: opt.pageNum - 1}
|
||||
@@ -325,7 +326,7 @@ func (i *Inspector) ListPendingTasks(queue string, opts ...ListOption) ([]*TaskI
|
||||
case errors.IsQueueNotFound(err):
|
||||
return nil, fmt.Errorf("asynq: %w", ErrQueueNotFound)
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("asynq: %v", err)
|
||||
return nil, fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
var tasks []*TaskInfo
|
||||
for _, i := range infos {
|
||||
@@ -344,7 +345,7 @@ func (i *Inspector) ListPendingTasks(queue string, opts ...ListOption) ([]*TaskI
|
||||
// By default, it retrieves the first 30 tasks.
|
||||
func (i *Inspector) ListActiveTasks(queue string, opts ...ListOption) ([]*TaskInfo, error) {
|
||||
if err := base.ValidateQueueName(queue); err != nil {
|
||||
return nil, fmt.Errorf("asynq: %v", err)
|
||||
return nil, fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
opt := composeListOptions(opts...)
|
||||
pgn := rdb.Pagination{Size: opt.pageSize, Page: opt.pageNum - 1}
|
||||
@@ -353,11 +354,11 @@ func (i *Inspector) ListActiveTasks(queue string, opts ...ListOption) ([]*TaskIn
|
||||
case errors.IsQueueNotFound(err):
|
||||
return nil, fmt.Errorf("asynq: %w", ErrQueueNotFound)
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("asynq: %v", err)
|
||||
return nil, fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
expired, err := i.rdb.ListLeaseExpired(time.Now(), queue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("asynq: %v", err)
|
||||
return nil, fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
expiredSet := make(map[string]struct{}) // set of expired message IDs
|
||||
for _, msg := range expired {
|
||||
@@ -384,7 +385,7 @@ func (i *Inspector) ListActiveTasks(queue string, opts ...ListOption) ([]*TaskIn
|
||||
// By default, it retrieves the first 30 tasks.
|
||||
func (i *Inspector) ListAggregatingTasks(queue, group string, opts ...ListOption) ([]*TaskInfo, error) {
|
||||
if err := base.ValidateQueueName(queue); err != nil {
|
||||
return nil, fmt.Errorf("asynq: %v", err)
|
||||
return nil, fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
opt := composeListOptions(opts...)
|
||||
pgn := rdb.Pagination{Size: opt.pageSize, Page: opt.pageNum - 1}
|
||||
@@ -393,7 +394,7 @@ func (i *Inspector) ListAggregatingTasks(queue, group string, opts ...ListOption
|
||||
case errors.IsQueueNotFound(err):
|
||||
return nil, fmt.Errorf("asynq: %w", ErrQueueNotFound)
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("asynq: %v", err)
|
||||
return nil, fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
var tasks []*TaskInfo
|
||||
for _, i := range infos {
|
||||
@@ -413,7 +414,7 @@ func (i *Inspector) ListAggregatingTasks(queue, group string, opts ...ListOption
|
||||
// By default, it retrieves the first 30 tasks.
|
||||
func (i *Inspector) ListScheduledTasks(queue string, opts ...ListOption) ([]*TaskInfo, error) {
|
||||
if err := base.ValidateQueueName(queue); err != nil {
|
||||
return nil, fmt.Errorf("asynq: %v", err)
|
||||
return nil, fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
opt := composeListOptions(opts...)
|
||||
pgn := rdb.Pagination{Size: opt.pageSize, Page: opt.pageNum - 1}
|
||||
@@ -422,7 +423,7 @@ func (i *Inspector) ListScheduledTasks(queue string, opts ...ListOption) ([]*Tas
|
||||
case errors.IsQueueNotFound(err):
|
||||
return nil, fmt.Errorf("asynq: %w", ErrQueueNotFound)
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("asynq: %v", err)
|
||||
return nil, fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
var tasks []*TaskInfo
|
||||
for _, i := range infos {
|
||||
@@ -442,7 +443,7 @@ func (i *Inspector) ListScheduledTasks(queue string, opts ...ListOption) ([]*Tas
|
||||
// By default, it retrieves the first 30 tasks.
|
||||
func (i *Inspector) ListRetryTasks(queue string, opts ...ListOption) ([]*TaskInfo, error) {
|
||||
if err := base.ValidateQueueName(queue); err != nil {
|
||||
return nil, fmt.Errorf("asynq: %v", err)
|
||||
return nil, fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
opt := composeListOptions(opts...)
|
||||
pgn := rdb.Pagination{Size: opt.pageSize, Page: opt.pageNum - 1}
|
||||
@@ -451,7 +452,7 @@ func (i *Inspector) ListRetryTasks(queue string, opts ...ListOption) ([]*TaskInf
|
||||
case errors.IsQueueNotFound(err):
|
||||
return nil, fmt.Errorf("asynq: %w", ErrQueueNotFound)
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("asynq: %v", err)
|
||||
return nil, fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
var tasks []*TaskInfo
|
||||
for _, i := range infos {
|
||||
@@ -471,7 +472,7 @@ func (i *Inspector) ListRetryTasks(queue string, opts ...ListOption) ([]*TaskInf
|
||||
// By default, it retrieves the first 30 tasks.
|
||||
func (i *Inspector) ListArchivedTasks(queue string, opts ...ListOption) ([]*TaskInfo, error) {
|
||||
if err := base.ValidateQueueName(queue); err != nil {
|
||||
return nil, fmt.Errorf("asynq: %v", err)
|
||||
return nil, fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
opt := composeListOptions(opts...)
|
||||
pgn := rdb.Pagination{Size: opt.pageSize, Page: opt.pageNum - 1}
|
||||
@@ -480,7 +481,7 @@ func (i *Inspector) ListArchivedTasks(queue string, opts ...ListOption) ([]*Task
|
||||
case errors.IsQueueNotFound(err):
|
||||
return nil, fmt.Errorf("asynq: %w", ErrQueueNotFound)
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("asynq: %v", err)
|
||||
return nil, fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
var tasks []*TaskInfo
|
||||
for _, i := range infos {
|
||||
@@ -500,7 +501,7 @@ func (i *Inspector) ListArchivedTasks(queue string, opts ...ListOption) ([]*Task
|
||||
// By default, it retrieves the first 30 tasks.
|
||||
func (i *Inspector) ListCompletedTasks(queue string, opts ...ListOption) ([]*TaskInfo, error) {
|
||||
if err := base.ValidateQueueName(queue); err != nil {
|
||||
return nil, fmt.Errorf("asynq: %v", err)
|
||||
return nil, fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
opt := composeListOptions(opts...)
|
||||
pgn := rdb.Pagination{Size: opt.pageSize, Page: opt.pageNum - 1}
|
||||
@@ -509,7 +510,7 @@ func (i *Inspector) ListCompletedTasks(queue string, opts ...ListOption) ([]*Tas
|
||||
case errors.IsQueueNotFound(err):
|
||||
return nil, fmt.Errorf("asynq: %w", ErrQueueNotFound)
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("asynq: %v", err)
|
||||
return nil, fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
var tasks []*TaskInfo
|
||||
for _, i := range infos {
|
||||
@@ -583,6 +584,30 @@ func (i *Inspector) DeleteAllAggregatingTasks(queue, group string) (int, error)
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
// UpdateTaskPayload updates a task with the given id from the given queue with given payload.
|
||||
// The task needs to be in scheduled state,
|
||||
// otherwise UpdateTaskPayload will return an error.
|
||||
//
|
||||
// If a queue with the given name doesn't exist, it returns an error wrapping ErrQueueNotFound.
|
||||
// If a task with the given id doesn't exist in the queue, it returns an error wrapping ErrTaskNotFound.
|
||||
// If the task is not in scheduled state, it returns a non-nil error.
|
||||
func (i *Inspector) UpdateTaskPayload(queue, id string, payload []byte) error {
|
||||
if err := base.ValidateQueueName(queue); err != nil {
|
||||
return fmt.Errorf("asynq: %v", err)
|
||||
}
|
||||
err := i.rdb.UpdateTaskPayload(queue, id, payload)
|
||||
switch {
|
||||
case errors.IsQueueNotFound(err):
|
||||
return fmt.Errorf("asynq: %w", ErrQueueNotFound)
|
||||
case errors.IsTaskNotFound(err):
|
||||
return fmt.Errorf("asynq: %w", ErrTaskNotFound)
|
||||
case err != nil:
|
||||
return fmt.Errorf("asynq: %v", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// DeleteTask deletes a task with the given id from the given queue.
|
||||
// The task needs to be in pending, scheduled, retry, or archived state,
|
||||
// otherwise DeleteTask will return an error.
|
||||
@@ -592,7 +617,7 @@ func (i *Inspector) DeleteAllAggregatingTasks(queue, group string) (int, error)
|
||||
// If the task is in active state, it returns a non-nil error.
|
||||
func (i *Inspector) DeleteTask(queue, id string) error {
|
||||
if err := base.ValidateQueueName(queue); err != nil {
|
||||
return fmt.Errorf("asynq: %v", err)
|
||||
return fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
err := i.rdb.DeleteTask(queue, id)
|
||||
switch {
|
||||
@@ -601,7 +626,7 @@ func (i *Inspector) DeleteTask(queue, id string) error {
|
||||
case errors.IsTaskNotFound(err):
|
||||
return fmt.Errorf("asynq: %w", ErrTaskNotFound)
|
||||
case err != nil:
|
||||
return fmt.Errorf("asynq: %v", err)
|
||||
return fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -656,7 +681,7 @@ func (i *Inspector) RunAllAggregatingTasks(queue, group string) (int, error) {
|
||||
// If the task is in pending or active state, it returns a non-nil error.
|
||||
func (i *Inspector) RunTask(queue, id string) error {
|
||||
if err := base.ValidateQueueName(queue); err != nil {
|
||||
return fmt.Errorf("asynq: %v", err)
|
||||
return fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
err := i.rdb.RunTask(queue, id)
|
||||
switch {
|
||||
@@ -665,7 +690,7 @@ func (i *Inspector) RunTask(queue, id string) error {
|
||||
case errors.IsTaskNotFound(err):
|
||||
return fmt.Errorf("asynq: %w", ErrTaskNotFound)
|
||||
case err != nil:
|
||||
return fmt.Errorf("asynq: %v", err)
|
||||
return fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -728,7 +753,7 @@ func (i *Inspector) ArchiveTask(queue, id string) error {
|
||||
case errors.IsTaskNotFound(err):
|
||||
return fmt.Errorf("asynq: %w", ErrTaskNotFound)
|
||||
case err != nil:
|
||||
return fmt.Errorf("asynq: %v", err)
|
||||
return fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -978,6 +1003,13 @@ func parseOption(s string) (Option, error) {
|
||||
return nil, err
|
||||
}
|
||||
return Retention(d), nil
|
||||
case "Header":
|
||||
var h [2]string
|
||||
err := json.Unmarshal([]byte(arg), &h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Header(h[0], h[1]), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot not parse option string %q", s)
|
||||
}
|
||||
|
||||
@@ -2369,6 +2369,148 @@ func TestInspectorRunAllArchivedTasks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectorUpdateTaskPayloadUpdatesScheduledTaskPayload(t *testing.T) {
|
||||
r := setup(t)
|
||||
defer r.Close()
|
||||
m1_old := h.NewTaskMessage("task1", []byte("m1_old"))
|
||||
m1_new := h.NewTaskMessage("task1", nil)
|
||||
m1_new.ID = m1_old.ID
|
||||
m2_old := h.NewTaskMessage("task2", nil)
|
||||
m2_new := h.NewTaskMessage("task2", []byte("m2_new"))
|
||||
m2_new.ID = m2_old.ID
|
||||
m3_old := h.NewTaskMessageWithQueue("task3", []byte("m3_old"), "custom")
|
||||
m3_new := h.NewTaskMessageWithQueue("task3", []byte("m3_new"), "custom")
|
||||
m3_new.ID = m3_old.ID
|
||||
|
||||
now := time.Now()
|
||||
z1_old := base.Z{Message: m1_old, Score: now.Add(5 * time.Minute).Unix()}
|
||||
z1_new := base.Z{Message: m1_new, Score: now.Add(5 * time.Minute).Unix()}
|
||||
z2_old := base.Z{Message: m2_old, Score: now.Add(15 * time.Minute).Unix()}
|
||||
z2_new := base.Z{Message: m2_new, Score: now.Add(15 * time.Minute).Unix()}
|
||||
z3_old := base.Z{Message: m3_old, Score: now.Add(2 * time.Minute).Unix()}
|
||||
z3_new := base.Z{Message: m3_new, Score: now.Add(2 * time.Minute).Unix()}
|
||||
|
||||
inspector := NewInspector(getRedisConnOpt(t))
|
||||
|
||||
tests := []struct {
|
||||
scheduled map[string][]base.Z
|
||||
qname string
|
||||
id string
|
||||
newPayload []byte
|
||||
wantScheduled map[string][]base.Z
|
||||
}{
|
||||
{
|
||||
scheduled: map[string][]base.Z{
|
||||
"default": {z1_old, z2_old},
|
||||
"custom": {z3_old},
|
||||
},
|
||||
qname: "default",
|
||||
id: createScheduledTask(z2_old).ID,
|
||||
newPayload: m2_new.Payload,
|
||||
wantScheduled: map[string][]base.Z{
|
||||
"default": {z1_old, z2_new},
|
||||
"custom": {z3_old},
|
||||
},
|
||||
},
|
||||
{
|
||||
scheduled: map[string][]base.Z{
|
||||
"default": {z1_old, z2_old},
|
||||
"custom": {z3_old},
|
||||
},
|
||||
qname: "default",
|
||||
id: createScheduledTask(z1_old).ID,
|
||||
newPayload: m1_new.Payload,
|
||||
wantScheduled: map[string][]base.Z{
|
||||
"default": {z1_new, z2_old},
|
||||
"custom": {z3_old},
|
||||
},
|
||||
},
|
||||
{
|
||||
scheduled: map[string][]base.Z{
|
||||
"default": {z1_old, z2_old},
|
||||
"custom": {z3_old},
|
||||
},
|
||||
qname: "custom",
|
||||
id: createScheduledTask(z3_old).ID,
|
||||
newPayload: m3_new.Payload,
|
||||
wantScheduled: map[string][]base.Z{
|
||||
"default": {z1_old, z2_old},
|
||||
"custom": {z3_new},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
h.FlushDB(t, r)
|
||||
h.SeedAllScheduledQueues(t, r, tc.scheduled)
|
||||
|
||||
if err := inspector.UpdateTaskPayload(tc.qname, tc.id, tc.newPayload); err != nil {
|
||||
t.Errorf("UpdateTask(%q, %q) returned error: %v", tc.qname, tc.id, err)
|
||||
}
|
||||
for qname, want := range tc.wantScheduled {
|
||||
gotScheduled := h.GetScheduledEntries(t, r, qname)
|
||||
if diff := cmp.Diff(want, gotScheduled, h.SortZSetEntryOpt); diff != "" {
|
||||
t.Errorf("unexpected scheduled tasks in queue %q: (-want, +got)\n%s", qname, diff)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectorUpdateTaskPayloadError(t *testing.T) {
|
||||
r := setup(t)
|
||||
defer r.Close()
|
||||
m1 := h.NewTaskMessage("task1", nil)
|
||||
m2 := h.NewTaskMessage("task2", nil)
|
||||
m3 := h.NewTaskMessageWithQueue("task3", nil, "custom")
|
||||
|
||||
now := time.Now()
|
||||
z1 := base.Z{Message: m1, Score: now.Add(5 * time.Minute).Unix()}
|
||||
z2 := base.Z{Message: m2, Score: now.Add(15 * time.Minute).Unix()}
|
||||
z3 := base.Z{Message: m3, Score: now.Add(2 * time.Minute).Unix()}
|
||||
|
||||
inspector := NewInspector(getRedisConnOpt(t))
|
||||
|
||||
tests := []struct {
|
||||
tasks map[string][]base.Z
|
||||
qname string
|
||||
id string
|
||||
newPayload []byte
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
tasks: map[string][]base.Z{
|
||||
"default": {z1, z2},
|
||||
"custom": {z3},
|
||||
},
|
||||
qname: "nonexistent",
|
||||
id: createScheduledTask(z2).ID,
|
||||
newPayload: nil,
|
||||
wantErr: ErrQueueNotFound,
|
||||
},
|
||||
{
|
||||
tasks: map[string][]base.Z{
|
||||
"default": {z1, z2},
|
||||
"custom": {z3},
|
||||
},
|
||||
qname: "default",
|
||||
id: uuid.NewString(),
|
||||
newPayload: nil,
|
||||
wantErr: ErrTaskNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
h.FlushDB(t, r)
|
||||
h.SeedAllScheduledQueues(t, r, tc.tasks)
|
||||
|
||||
if err := inspector.UpdateTaskPayload(tc.qname, tc.id, tc.newPayload); !errors.Is(err, tc.wantErr) {
|
||||
t.Errorf("UpdateTask(%q, %q) = %v, want %v", tc.qname, tc.id, err, tc.wantErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectorDeleteTaskDeletesPendingTask(t *testing.T) {
|
||||
r := setup(t)
|
||||
defer r.Close()
|
||||
@@ -3384,6 +3526,7 @@ func TestParseOption(t *testing.T) {
|
||||
{ProcessAt(oneHourFromNow).String(), ProcessAtOpt, oneHourFromNow},
|
||||
{`ProcessIn(10m)`, ProcessInOpt, 10 * time.Minute},
|
||||
{`Retention(24h)`, RetentionOpt, 24 * time.Hour},
|
||||
{`Header(["email", "hello@example.com"])`, HeaderOpt, [2]string{"email", "hello@example.com"}},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
@@ -3431,6 +3574,14 @@ func TestParseOption(t *testing.T) {
|
||||
if cmp.Equal(gotVal, tc.wantVal.(time.Time)) {
|
||||
t.Fatalf("got value %v, want %v", gotVal, tc.wantVal)
|
||||
}
|
||||
case HeaderOpt:
|
||||
gotVal, ok := got.Value().([2]string)
|
||||
if !ok {
|
||||
t.Fatal("returned Option with non array value")
|
||||
}
|
||||
if gotVal != tc.wantVal.([2]string) {
|
||||
t.Fatalf("got value %v, want %v", gotVal, tc.wantVal)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("returned Option with unexpected type: %v", got.Type())
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
)
|
||||
|
||||
// Version of asynq library and CLI.
|
||||
const Version = "0.25.0"
|
||||
const Version = "0.26.0"
|
||||
|
||||
// DefaultQueueName is the queue name used if none are specified by user.
|
||||
const DefaultQueueName = "default"
|
||||
@@ -104,76 +104,76 @@ func ValidateQueueName(qname string) error {
|
||||
|
||||
// QueueKeyPrefix returns a prefix for all keys in the given queue.
|
||||
func QueueKeyPrefix(qname string) string {
|
||||
return fmt.Sprintf("asynq:{%s}:", qname)
|
||||
return "asynq:{" + qname + "}:"
|
||||
}
|
||||
|
||||
// TaskKeyPrefix returns a prefix for task key.
|
||||
func TaskKeyPrefix(qname string) string {
|
||||
return fmt.Sprintf("%st:", QueueKeyPrefix(qname))
|
||||
return QueueKeyPrefix(qname) + "t:"
|
||||
}
|
||||
|
||||
// TaskKey returns a redis key for the given task message.
|
||||
func TaskKey(qname, id string) string {
|
||||
return fmt.Sprintf("%s%s", TaskKeyPrefix(qname), id)
|
||||
return TaskKeyPrefix(qname) + id
|
||||
}
|
||||
|
||||
// PendingKey returns a redis key for the given queue name.
|
||||
func PendingKey(qname string) string {
|
||||
return fmt.Sprintf("%spending", QueueKeyPrefix(qname))
|
||||
return QueueKeyPrefix(qname) + "pending"
|
||||
}
|
||||
|
||||
// ActiveKey returns a redis key for the active tasks.
|
||||
func ActiveKey(qname string) string {
|
||||
return fmt.Sprintf("%sactive", QueueKeyPrefix(qname))
|
||||
return QueueKeyPrefix(qname) + "active"
|
||||
}
|
||||
|
||||
// ScheduledKey returns a redis key for the scheduled tasks.
|
||||
func ScheduledKey(qname string) string {
|
||||
return fmt.Sprintf("%sscheduled", QueueKeyPrefix(qname))
|
||||
return QueueKeyPrefix(qname) + "scheduled"
|
||||
}
|
||||
|
||||
// RetryKey returns a redis key for the retry tasks.
|
||||
func RetryKey(qname string) string {
|
||||
return fmt.Sprintf("%sretry", QueueKeyPrefix(qname))
|
||||
return QueueKeyPrefix(qname) + "retry"
|
||||
}
|
||||
|
||||
// ArchivedKey returns a redis key for the archived tasks.
|
||||
func ArchivedKey(qname string) string {
|
||||
return fmt.Sprintf("%sarchived", QueueKeyPrefix(qname))
|
||||
return QueueKeyPrefix(qname) + "archived"
|
||||
}
|
||||
|
||||
// LeaseKey returns a redis key for the lease.
|
||||
func LeaseKey(qname string) string {
|
||||
return fmt.Sprintf("%slease", QueueKeyPrefix(qname))
|
||||
return QueueKeyPrefix(qname) + "lease"
|
||||
}
|
||||
|
||||
func CompletedKey(qname string) string {
|
||||
return fmt.Sprintf("%scompleted", QueueKeyPrefix(qname))
|
||||
return QueueKeyPrefix(qname) + "completed"
|
||||
}
|
||||
|
||||
// PausedKey returns a redis key to indicate that the given queue is paused.
|
||||
func PausedKey(qname string) string {
|
||||
return fmt.Sprintf("%spaused", QueueKeyPrefix(qname))
|
||||
return QueueKeyPrefix(qname) + "paused"
|
||||
}
|
||||
|
||||
// ProcessedTotalKey returns a redis key for total processed count for the given queue.
|
||||
func ProcessedTotalKey(qname string) string {
|
||||
return fmt.Sprintf("%sprocessed", QueueKeyPrefix(qname))
|
||||
return QueueKeyPrefix(qname) + "processed"
|
||||
}
|
||||
|
||||
// FailedTotalKey returns a redis key for total failure count for the given queue.
|
||||
func FailedTotalKey(qname string) string {
|
||||
return fmt.Sprintf("%sfailed", QueueKeyPrefix(qname))
|
||||
return QueueKeyPrefix(qname) + "failed"
|
||||
}
|
||||
|
||||
// ProcessedKey returns a redis key for processed count for the given day for the queue.
|
||||
func ProcessedKey(qname string, t time.Time) string {
|
||||
return fmt.Sprintf("%sprocessed:%s", QueueKeyPrefix(qname), t.UTC().Format("2006-01-02"))
|
||||
return QueueKeyPrefix(qname) + "processed:" + t.UTC().Format("2006-01-02")
|
||||
}
|
||||
|
||||
// FailedKey returns a redis key for failure count for the given day for the queue.
|
||||
func FailedKey(qname string, t time.Time) string {
|
||||
return fmt.Sprintf("%sfailed:%s", QueueKeyPrefix(qname), t.UTC().Format("2006-01-02"))
|
||||
return QueueKeyPrefix(qname) + "failed:" + t.UTC().Format("2006-01-02")
|
||||
}
|
||||
|
||||
// ServerInfoKey returns a redis key for process info.
|
||||
@@ -188,47 +188,47 @@ func WorkersKey(hostname string, pid int, serverID string) string {
|
||||
|
||||
// SchedulerEntriesKey returns a redis key for the scheduler entries given scheduler ID.
|
||||
func SchedulerEntriesKey(schedulerID string) string {
|
||||
return fmt.Sprintf("asynq:schedulers:{%s}", schedulerID)
|
||||
return "asynq:schedulers:{" + schedulerID + "}"
|
||||
}
|
||||
|
||||
// SchedulerHistoryKey returns a redis key for the scheduler's history for the given entry.
|
||||
func SchedulerHistoryKey(entryID string) string {
|
||||
return fmt.Sprintf("asynq:scheduler_history:%s", entryID)
|
||||
return "asynq:scheduler_history:" + entryID
|
||||
}
|
||||
|
||||
// UniqueKey returns a redis key with the given type, payload, and queue name.
|
||||
func UniqueKey(qname, tasktype string, payload []byte) string {
|
||||
if payload == nil {
|
||||
return fmt.Sprintf("%sunique:%s:", QueueKeyPrefix(qname), tasktype)
|
||||
return QueueKeyPrefix(qname) + "unique:" + tasktype + ":"
|
||||
}
|
||||
checksum := md5.Sum(payload)
|
||||
return fmt.Sprintf("%sunique:%s:%s", QueueKeyPrefix(qname), tasktype, hex.EncodeToString(checksum[:]))
|
||||
return QueueKeyPrefix(qname) + "unique:" + tasktype + ":" + hex.EncodeToString(checksum[:])
|
||||
}
|
||||
|
||||
// GroupKeyPrefix returns a prefix for group key.
|
||||
func GroupKeyPrefix(qname string) string {
|
||||
return fmt.Sprintf("%sg:", QueueKeyPrefix(qname))
|
||||
return QueueKeyPrefix(qname) + "g:"
|
||||
}
|
||||
|
||||
// GroupKey returns a redis key used to group tasks belong in the same group.
|
||||
func GroupKey(qname, gkey string) string {
|
||||
return fmt.Sprintf("%s%s", GroupKeyPrefix(qname), gkey)
|
||||
return GroupKeyPrefix(qname) + gkey
|
||||
}
|
||||
|
||||
// AggregationSetKey returns a redis key used for an aggregation set.
|
||||
func AggregationSetKey(qname, gname, setID string) string {
|
||||
return fmt.Sprintf("%s:%s", GroupKey(qname, gname), setID)
|
||||
return GroupKey(qname, gname) + ":" + setID
|
||||
}
|
||||
|
||||
// AllGroups return a redis key used to store all group keys used in a given queue.
|
||||
func AllGroups(qname string) string {
|
||||
return fmt.Sprintf("%sgroups", QueueKeyPrefix(qname))
|
||||
return QueueKeyPrefix(qname) + "groups"
|
||||
}
|
||||
|
||||
// AllAggregationSets returns a redis key used to store all aggregation sets (set of tasks staged to be aggregated)
|
||||
// in a given queue.
|
||||
func AllAggregationSets(qname string) string {
|
||||
return fmt.Sprintf("%saggregation_sets", QueueKeyPrefix(qname))
|
||||
return QueueKeyPrefix(qname) + "aggregation_sets"
|
||||
}
|
||||
|
||||
// TaskMessage is the internal representation of a task with additional metadata fields.
|
||||
@@ -240,6 +240,9 @@ type TaskMessage struct {
|
||||
// Payload holds data needed to process the task.
|
||||
Payload []byte
|
||||
|
||||
// Headers holds additional metadata for the task.
|
||||
Headers map[string]string
|
||||
|
||||
// ID is a unique identifier for each task.
|
||||
ID string
|
||||
|
||||
@@ -304,6 +307,7 @@ func EncodeMessage(msg *TaskMessage) ([]byte, error) {
|
||||
return proto.Marshal(&pb.TaskMessage{
|
||||
Type: msg.Type,
|
||||
Payload: msg.Payload,
|
||||
Headers: msg.Headers,
|
||||
Id: msg.ID,
|
||||
Queue: msg.Queue,
|
||||
Retry: int32(msg.Retry),
|
||||
@@ -328,6 +332,7 @@ func DecodeMessage(data []byte) (*TaskMessage, error) {
|
||||
return &TaskMessage{
|
||||
Type: pbmsg.GetType(),
|
||||
Payload: pbmsg.GetPayload(),
|
||||
Headers: pbmsg.GetHeaders(),
|
||||
ID: pbmsg.GetId(),
|
||||
Queue: pbmsg.GetQueue(),
|
||||
Retry: int(pbmsg.GetRetry()),
|
||||
@@ -375,7 +380,7 @@ func EncodeServerInfo(info *ServerInfo) ([]byte, error) {
|
||||
if info == nil {
|
||||
return nil, fmt.Errorf("cannot encode nil server info")
|
||||
}
|
||||
queues := make(map[string]int32)
|
||||
queues := make(map[string]int32, len(info.Queues))
|
||||
for q, p := range info.Queues {
|
||||
queues[q] = int32(p)
|
||||
}
|
||||
@@ -400,7 +405,7 @@ func DecodeServerInfo(b []byte) (*ServerInfo, error) {
|
||||
if err := proto.Unmarshal(b, &pbmsg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queues := make(map[string]int)
|
||||
queues := make(map[string]int, len(pbmsg.GetQueues()))
|
||||
for q, p := range pbmsg.GetQueues() {
|
||||
queues[q] = int(p)
|
||||
}
|
||||
@@ -679,6 +684,14 @@ func (l *Lease) IsValid() bool {
|
||||
return l.expireAt.After(now) || l.expireAt.Equal(now)
|
||||
}
|
||||
|
||||
// BatchEnqueueItem pairs a task message with optional scheduling metadata for
|
||||
// batch enqueue operations. If ProcessAt is zero, the task is enqueued for
|
||||
// immediate processing; otherwise it is added to the scheduled set.
|
||||
type BatchEnqueueItem struct {
|
||||
Msg *TaskMessage
|
||||
ProcessAt time.Time // zero value → immediate
|
||||
}
|
||||
|
||||
// Broker is a message broker that supports operations to manage task queues.
|
||||
//
|
||||
// See rdb.RDB as a reference implementation.
|
||||
@@ -687,6 +700,12 @@ type Broker interface {
|
||||
Close() error
|
||||
Enqueue(ctx context.Context, msg *TaskMessage) error
|
||||
EnqueueUnique(ctx context.Context, msg *TaskMessage, ttl time.Duration) error
|
||||
// BatchEnqueue enqueues multiple tasks in a single round-trip. It returns the
|
||||
// count of newly enqueued tasks; duplicate IDs are silently skipped. The error
|
||||
// is non-nil only on infrastructure failure (e.g. lost connection), in which
|
||||
// case the count is meaningless. Individual task scripts are atomic but the
|
||||
// batch as a whole is not transactional — partial success is possible.
|
||||
BatchEnqueue(ctx context.Context, items []BatchEnqueueItem) (int, error)
|
||||
Dequeue(qnames ...string) (*TaskMessage, time.Time, error)
|
||||
Done(ctx context.Context, msg *TaskMessage) error
|
||||
MarkAsComplete(ctx context.Context, msg *TaskMessage) error
|
||||
|
||||
@@ -107,6 +107,7 @@ type Op string
|
||||
// only the last one is recorded.
|
||||
//
|
||||
// The types are:
|
||||
//
|
||||
// errors.Op
|
||||
// The operation being performed, usually the method
|
||||
// being invoked (Get, Put, etc.).
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.2
|
||||
// protoc v3.19.6
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v5.29.3
|
||||
// source: asynq.proto
|
||||
|
||||
package proto
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -27,16 +28,15 @@ const (
|
||||
|
||||
// TaskMessage is the internal representation of a task with additional
|
||||
// metadata fields.
|
||||
// Next ID: 15
|
||||
// Next ID: 16
|
||||
type TaskMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Type indicates the kind of the task to be performed.
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
|
||||
// Payload holds data needed to process the task.
|
||||
Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"`
|
||||
// Headers holds additional metadata for the task.
|
||||
Headers map[string]string `protobuf:"bytes,15,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
// Unique identifier for the task.
|
||||
Id string `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"`
|
||||
// Name of the queue to which this task belongs.
|
||||
@@ -71,16 +71,16 @@ type TaskMessage struct {
|
||||
// Time when the task completed in success in Unix time,
|
||||
// the number of seconds elapsed since January 1, 1970 UTC.
|
||||
// This field is populated if result_ttl > 0 upon completion.
|
||||
CompletedAt int64 `protobuf:"varint,13,opt,name=completed_at,json=completedAt,proto3" json:"completed_at,omitempty"`
|
||||
CompletedAt int64 `protobuf:"varint,13,opt,name=completed_at,json=completedAt,proto3" json:"completed_at,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *TaskMessage) Reset() {
|
||||
*x = TaskMessage{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_asynq_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_asynq_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *TaskMessage) String() string {
|
||||
@@ -91,7 +91,7 @@ func (*TaskMessage) ProtoMessage() {}
|
||||
|
||||
func (x *TaskMessage) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_asynq_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -120,6 +120,13 @@ func (x *TaskMessage) GetPayload() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *TaskMessage) GetHeaders() map[string]string {
|
||||
if x != nil {
|
||||
return x.Headers
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *TaskMessage) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
@@ -206,10 +213,7 @@ func (x *TaskMessage) GetCompletedAt() int64 {
|
||||
|
||||
// ServerInfo holds information about a running server.
|
||||
type ServerInfo struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Host machine the server is running on.
|
||||
Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
|
||||
// PID of the server process.
|
||||
@@ -221,7 +225,7 @@ type ServerInfo struct {
|
||||
// List of queue names with their priorities.
|
||||
// The server will consume tasks from the queues and prioritize
|
||||
// queues with higher priority numbers.
|
||||
Queues map[string]int32 `protobuf:"bytes,5,rep,name=queues,proto3" json:"queues,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
|
||||
Queues map[string]int32 `protobuf:"bytes,5,rep,name=queues,proto3" json:"queues,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
|
||||
// If set, the server will always consume tasks from a queue with higher
|
||||
// priority.
|
||||
StrictPriority bool `protobuf:"varint,6,opt,name=strict_priority,json=strictPriority,proto3" json:"strict_priority,omitempty"`
|
||||
@@ -231,15 +235,15 @@ type ServerInfo struct {
|
||||
StartTime *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"`
|
||||
// Number of workers currently processing tasks.
|
||||
ActiveWorkerCount int32 `protobuf:"varint,9,opt,name=active_worker_count,json=activeWorkerCount,proto3" json:"active_worker_count,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ServerInfo) Reset() {
|
||||
*x = ServerInfo{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_asynq_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_asynq_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ServerInfo) String() string {
|
||||
@@ -250,7 +254,7 @@ func (*ServerInfo) ProtoMessage() {}
|
||||
|
||||
func (x *ServerInfo) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_asynq_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -330,10 +334,7 @@ func (x *ServerInfo) GetActiveWorkerCount() int32 {
|
||||
|
||||
// WorkerInfo holds information about a running worker.
|
||||
type WorkerInfo struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Host matchine this worker is running on.
|
||||
Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
|
||||
// PID of the process in which this worker is running.
|
||||
@@ -352,16 +353,16 @@ type WorkerInfo struct {
|
||||
StartTime *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"`
|
||||
// Deadline by which the worker needs to complete processing
|
||||
// the task. If worker exceeds the deadline, the task will fail.
|
||||
Deadline *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=deadline,proto3" json:"deadline,omitempty"`
|
||||
Deadline *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=deadline,proto3" json:"deadline,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *WorkerInfo) Reset() {
|
||||
*x = WorkerInfo{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_asynq_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_asynq_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *WorkerInfo) String() string {
|
||||
@@ -372,7 +373,7 @@ func (*WorkerInfo) ProtoMessage() {}
|
||||
|
||||
func (x *WorkerInfo) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_asynq_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -453,10 +454,7 @@ func (x *WorkerInfo) GetDeadline() *timestamppb.Timestamp {
|
||||
// SchedulerEntry holds information about a periodic task registered
|
||||
// with a scheduler.
|
||||
type SchedulerEntry struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Identifier of the scheduler entry.
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
// Periodic schedule spec of the entry.
|
||||
@@ -472,15 +470,15 @@ type SchedulerEntry struct {
|
||||
// Last time the task was enqueued.
|
||||
// Zero time if task was never enqueued.
|
||||
PrevEnqueueTime *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=prev_enqueue_time,json=prevEnqueueTime,proto3" json:"prev_enqueue_time,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SchedulerEntry) Reset() {
|
||||
*x = SchedulerEntry{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_asynq_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_asynq_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SchedulerEntry) String() string {
|
||||
@@ -491,7 +489,7 @@ func (*SchedulerEntry) ProtoMessage() {}
|
||||
|
||||
func (x *SchedulerEntry) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_asynq_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -558,23 +556,20 @@ func (x *SchedulerEntry) GetPrevEnqueueTime() *timestamppb.Timestamp {
|
||||
// SchedulerEnqueueEvent holds information about an enqueue event
|
||||
// by a scheduler.
|
||||
type SchedulerEnqueueEvent struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// ID of the task that was enqueued.
|
||||
TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"`
|
||||
// Time the task was enqueued.
|
||||
EnqueueTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=enqueue_time,json=enqueueTime,proto3" json:"enqueue_time,omitempty"`
|
||||
EnqueueTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=enqueue_time,json=enqueueTime,proto3" json:"enqueue_time,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SchedulerEnqueueEvent) Reset() {
|
||||
*x = SchedulerEnqueueEvent{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_asynq_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_asynq_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SchedulerEnqueueEvent) String() string {
|
||||
@@ -585,7 +580,7 @@ func (*SchedulerEnqueueEvent) ProtoMessage() {}
|
||||
|
||||
func (x *SchedulerEnqueueEvent) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_asynq_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -616,146 +611,106 @@ func (x *SchedulerEnqueueEvent) GetEnqueueTime() *timestamppb.Timestamp {
|
||||
|
||||
var File_asynq_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_asynq_proto_rawDesc = []byte{
|
||||
0x0a, 0x0b, 0x61, 0x73, 0x79, 0x6e, 0x71, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61,
|
||||
0x73, 0x79, 0x6e, 0x71, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x87, 0x03, 0x0a, 0x0b, 0x54, 0x61, 0x73, 0x6b, 0x4d, 0x65,
|
||||
0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79,
|
||||
0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c,
|
||||
0x6f, 0x61, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x74,
|
||||
0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x72, 0x65, 0x74, 0x72, 0x79, 0x12,
|
||||
0x18, 0x0a, 0x07, 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05,
|
||||
0x52, 0x07, 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x72, 0x72,
|
||||
0x6f, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x72,
|
||||
0x72, 0x6f, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x66,
|
||||
0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c,
|
||||
0x6c, 0x61, 0x73, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x41, 0x74, 0x12, 0x18, 0x0a, 0x07,
|
||||
0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74,
|
||||
0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69,
|
||||
0x6e, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69,
|
||||
0x6e, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x6b, 0x65, 0x79,
|
||||
0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x4b, 0x65,
|
||||
0x79, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0e,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x1c,
|
||||
0x0a, 0x09, 0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28,
|
||||
0x03, 0x52, 0x09, 0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c,
|
||||
0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0d, 0x20, 0x01,
|
||||
0x28, 0x03, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22,
|
||||
0x8f, 0x03, 0x0a, 0x0a, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12,
|
||||
0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f,
|
||||
0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52,
|
||||
0x03, 0x70, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x49,
|
||||
0x64, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79,
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65,
|
||||
0x6e, 0x63, 0x79, 0x12, 0x35, 0x0a, 0x06, 0x71, 0x75, 0x65, 0x75, 0x65, 0x73, 0x18, 0x05, 0x20,
|
||||
0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x73, 0x79, 0x6e, 0x71, 0x2e, 0x53, 0x65, 0x72, 0x76,
|
||||
0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x51, 0x75, 0x65, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74,
|
||||
0x72, 0x79, 0x52, 0x06, 0x71, 0x75, 0x65, 0x75, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x74,
|
||||
0x72, 0x69, 0x63, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20,
|
||||
0x01, 0x28, 0x08, 0x52, 0x0e, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x50, 0x72, 0x69, 0x6f, 0x72,
|
||||
0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x73,
|
||||
0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x74, 0x61,
|
||||
0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65,
|
||||
0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20,
|
||||
0x01, 0x28, 0x05, 0x52, 0x11, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65,
|
||||
0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0x39, 0x0a, 0x0b, 0x51, 0x75, 0x65, 0x75, 0x65, 0x73,
|
||||
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
|
||||
0x01, 0x22, 0xb1, 0x02, 0x0a, 0x0a, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f,
|
||||
0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
|
||||
0x68, 0x6f, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x05, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x65,
|
||||
0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x04,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09,
|
||||
0x74, 0x61, 0x73, 0x6b, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x08, 0x74, 0x61, 0x73, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x61, 0x73,
|
||||
0x6b, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x0b, 0x74, 0x61, 0x73, 0x6b, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x14, 0x0a, 0x05,
|
||||
0x71, 0x75, 0x65, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65,
|
||||
0x75, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65,
|
||||
0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
||||
0x6d, 0x70, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x36, 0x0a,
|
||||
0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x64, 0x65, 0x61,
|
||||
0x64, 0x6c, 0x69, 0x6e, 0x65, 0x22, 0xad, 0x02, 0x0a, 0x0e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75,
|
||||
0x6c, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x1b, 0x0a, 0x09,
|
||||
0x74, 0x61, 0x73, 0x6b, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x08, 0x74, 0x61, 0x73, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x61, 0x73,
|
||||
0x6b, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x0b, 0x74, 0x61, 0x73, 0x6b, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x27, 0x0a, 0x0f,
|
||||
0x65, 0x6e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18,
|
||||
0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x6e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x4f, 0x70,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x46, 0x0a, 0x11, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x65, 0x6e,
|
||||
0x71, 0x75, 0x65, 0x75, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x6e, 0x65,
|
||||
0x78, 0x74, 0x45, 0x6e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x46, 0x0a,
|
||||
0x11, 0x70, 0x72, 0x65, 0x76, 0x5f, 0x65, 0x6e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x5f, 0x74, 0x69,
|
||||
0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
|
||||
0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x70, 0x72, 0x65, 0x76, 0x45, 0x6e, 0x71, 0x75, 0x65, 0x75,
|
||||
0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x6f, 0x0a, 0x15, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c,
|
||||
0x65, 0x72, 0x45, 0x6e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x17,
|
||||
0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x3d, 0x0a, 0x0c, 0x65, 0x6e, 0x71, 0x75, 0x65,
|
||||
0x75, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x65, 0x6e, 0x71, 0x75, 0x65,
|
||||
0x75, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
|
||||
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x69, 0x62, 0x69, 0x6b, 0x65, 0x6e, 0x2f, 0x61, 0x73, 0x79,
|
||||
0x6e, 0x71, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
const file_asynq_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\vasynq.proto\x12\x05asynq\x1a\x1fgoogle/protobuf/timestamp.proto\"\xfe\x03\n" +
|
||||
"\vTaskMessage\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x18\n" +
|
||||
"\apayload\x18\x02 \x01(\fR\apayload\x129\n" +
|
||||
"\aheaders\x18\x0f \x03(\v2\x1f.asynq.TaskMessage.HeadersEntryR\aheaders\x12\x0e\n" +
|
||||
"\x02id\x18\x03 \x01(\tR\x02id\x12\x14\n" +
|
||||
"\x05queue\x18\x04 \x01(\tR\x05queue\x12\x14\n" +
|
||||
"\x05retry\x18\x05 \x01(\x05R\x05retry\x12\x18\n" +
|
||||
"\aretried\x18\x06 \x01(\x05R\aretried\x12\x1b\n" +
|
||||
"\terror_msg\x18\a \x01(\tR\berrorMsg\x12$\n" +
|
||||
"\x0elast_failed_at\x18\v \x01(\x03R\flastFailedAt\x12\x18\n" +
|
||||
"\atimeout\x18\b \x01(\x03R\atimeout\x12\x1a\n" +
|
||||
"\bdeadline\x18\t \x01(\x03R\bdeadline\x12\x1d\n" +
|
||||
"\n" +
|
||||
"unique_key\x18\n" +
|
||||
" \x01(\tR\tuniqueKey\x12\x1b\n" +
|
||||
"\tgroup_key\x18\x0e \x01(\tR\bgroupKey\x12\x1c\n" +
|
||||
"\tretention\x18\f \x01(\x03R\tretention\x12!\n" +
|
||||
"\fcompleted_at\x18\r \x01(\x03R\vcompletedAt\x1a:\n" +
|
||||
"\fHeadersEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x8f\x03\n" +
|
||||
"\n" +
|
||||
"ServerInfo\x12\x12\n" +
|
||||
"\x04host\x18\x01 \x01(\tR\x04host\x12\x10\n" +
|
||||
"\x03pid\x18\x02 \x01(\x05R\x03pid\x12\x1b\n" +
|
||||
"\tserver_id\x18\x03 \x01(\tR\bserverId\x12 \n" +
|
||||
"\vconcurrency\x18\x04 \x01(\x05R\vconcurrency\x125\n" +
|
||||
"\x06queues\x18\x05 \x03(\v2\x1d.asynq.ServerInfo.QueuesEntryR\x06queues\x12'\n" +
|
||||
"\x0fstrict_priority\x18\x06 \x01(\bR\x0estrictPriority\x12\x16\n" +
|
||||
"\x06status\x18\a \x01(\tR\x06status\x129\n" +
|
||||
"\n" +
|
||||
"start_time\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\tstartTime\x12.\n" +
|
||||
"\x13active_worker_count\x18\t \x01(\x05R\x11activeWorkerCount\x1a9\n" +
|
||||
"\vQueuesEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\x05R\x05value:\x028\x01\"\xb1\x02\n" +
|
||||
"\n" +
|
||||
"WorkerInfo\x12\x12\n" +
|
||||
"\x04host\x18\x01 \x01(\tR\x04host\x12\x10\n" +
|
||||
"\x03pid\x18\x02 \x01(\x05R\x03pid\x12\x1b\n" +
|
||||
"\tserver_id\x18\x03 \x01(\tR\bserverId\x12\x17\n" +
|
||||
"\atask_id\x18\x04 \x01(\tR\x06taskId\x12\x1b\n" +
|
||||
"\ttask_type\x18\x05 \x01(\tR\btaskType\x12!\n" +
|
||||
"\ftask_payload\x18\x06 \x01(\fR\vtaskPayload\x12\x14\n" +
|
||||
"\x05queue\x18\a \x01(\tR\x05queue\x129\n" +
|
||||
"\n" +
|
||||
"start_time\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\tstartTime\x126\n" +
|
||||
"\bdeadline\x18\t \x01(\v2\x1a.google.protobuf.TimestampR\bdeadline\"\xad\x02\n" +
|
||||
"\x0eSchedulerEntry\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
|
||||
"\x04spec\x18\x02 \x01(\tR\x04spec\x12\x1b\n" +
|
||||
"\ttask_type\x18\x03 \x01(\tR\btaskType\x12!\n" +
|
||||
"\ftask_payload\x18\x04 \x01(\fR\vtaskPayload\x12'\n" +
|
||||
"\x0fenqueue_options\x18\x05 \x03(\tR\x0eenqueueOptions\x12F\n" +
|
||||
"\x11next_enqueue_time\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\x0fnextEnqueueTime\x12F\n" +
|
||||
"\x11prev_enqueue_time\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\x0fprevEnqueueTime\"o\n" +
|
||||
"\x15SchedulerEnqueueEvent\x12\x17\n" +
|
||||
"\atask_id\x18\x01 \x01(\tR\x06taskId\x12=\n" +
|
||||
"\fenqueue_time\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\venqueueTimeB)Z'github.com/hibiken/asynq/internal/protob\x06proto3"
|
||||
|
||||
var (
|
||||
file_asynq_proto_rawDescOnce sync.Once
|
||||
file_asynq_proto_rawDescData = file_asynq_proto_rawDesc
|
||||
file_asynq_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_asynq_proto_rawDescGZIP() []byte {
|
||||
file_asynq_proto_rawDescOnce.Do(func() {
|
||||
file_asynq_proto_rawDescData = protoimpl.X.CompressGZIP(file_asynq_proto_rawDescData)
|
||||
file_asynq_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_asynq_proto_rawDesc), len(file_asynq_proto_rawDesc)))
|
||||
})
|
||||
return file_asynq_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_asynq_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
||||
var file_asynq_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||
var file_asynq_proto_goTypes = []any{
|
||||
(*TaskMessage)(nil), // 0: asynq.TaskMessage
|
||||
(*ServerInfo)(nil), // 1: asynq.ServerInfo
|
||||
(*WorkerInfo)(nil), // 2: asynq.WorkerInfo
|
||||
(*SchedulerEntry)(nil), // 3: asynq.SchedulerEntry
|
||||
(*SchedulerEnqueueEvent)(nil), // 4: asynq.SchedulerEnqueueEvent
|
||||
nil, // 5: asynq.ServerInfo.QueuesEntry
|
||||
(*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp
|
||||
nil, // 5: asynq.TaskMessage.HeadersEntry
|
||||
nil, // 6: asynq.ServerInfo.QueuesEntry
|
||||
(*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp
|
||||
}
|
||||
var file_asynq_proto_depIdxs = []int32{
|
||||
5, // 0: asynq.ServerInfo.queues:type_name -> asynq.ServerInfo.QueuesEntry
|
||||
6, // 1: asynq.ServerInfo.start_time:type_name -> google.protobuf.Timestamp
|
||||
6, // 2: asynq.WorkerInfo.start_time:type_name -> google.protobuf.Timestamp
|
||||
6, // 3: asynq.WorkerInfo.deadline:type_name -> google.protobuf.Timestamp
|
||||
6, // 4: asynq.SchedulerEntry.next_enqueue_time:type_name -> google.protobuf.Timestamp
|
||||
6, // 5: asynq.SchedulerEntry.prev_enqueue_time:type_name -> google.protobuf.Timestamp
|
||||
6, // 6: asynq.SchedulerEnqueueEvent.enqueue_time:type_name -> google.protobuf.Timestamp
|
||||
7, // [7:7] is the sub-list for method output_type
|
||||
7, // [7:7] is the sub-list for method input_type
|
||||
7, // [7:7] is the sub-list for extension type_name
|
||||
7, // [7:7] is the sub-list for extension extendee
|
||||
0, // [0:7] is the sub-list for field type_name
|
||||
5, // 0: asynq.TaskMessage.headers:type_name -> asynq.TaskMessage.HeadersEntry
|
||||
6, // 1: asynq.ServerInfo.queues:type_name -> asynq.ServerInfo.QueuesEntry
|
||||
7, // 2: asynq.ServerInfo.start_time:type_name -> google.protobuf.Timestamp
|
||||
7, // 3: asynq.WorkerInfo.start_time:type_name -> google.protobuf.Timestamp
|
||||
7, // 4: asynq.WorkerInfo.deadline:type_name -> google.protobuf.Timestamp
|
||||
7, // 5: asynq.SchedulerEntry.next_enqueue_time:type_name -> google.protobuf.Timestamp
|
||||
7, // 6: asynq.SchedulerEntry.prev_enqueue_time:type_name -> google.protobuf.Timestamp
|
||||
7, // 7: asynq.SchedulerEnqueueEvent.enqueue_time:type_name -> google.protobuf.Timestamp
|
||||
8, // [8:8] is the sub-list for method output_type
|
||||
8, // [8:8] is the sub-list for method input_type
|
||||
8, // [8:8] is the sub-list for extension type_name
|
||||
8, // [8:8] is the sub-list for extension extendee
|
||||
0, // [0:8] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_asynq_proto_init() }
|
||||
@@ -763,75 +718,13 @@ func file_asynq_proto_init() {
|
||||
if File_asynq_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_asynq_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*TaskMessage); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_asynq_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*ServerInfo); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_asynq_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*WorkerInfo); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_asynq_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*SchedulerEntry); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_asynq_proto_msgTypes[4].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*SchedulerEnqueueEvent); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_asynq_proto_rawDesc,
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_asynq_proto_rawDesc), len(file_asynq_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 6,
|
||||
NumMessages: 7,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
@@ -840,7 +733,6 @@ func file_asynq_proto_init() {
|
||||
MessageInfos: file_asynq_proto_msgTypes,
|
||||
}.Build()
|
||||
File_asynq_proto = out.File
|
||||
file_asynq_proto_rawDesc = nil
|
||||
file_asynq_proto_goTypes = nil
|
||||
file_asynq_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ option go_package = "github.com/hibiken/asynq/internal/proto";
|
||||
|
||||
// TaskMessage is the internal representation of a task with additional
|
||||
// metadata fields.
|
||||
// Next ID: 15
|
||||
// Next ID: 16
|
||||
message TaskMessage {
|
||||
// Type indicates the kind of the task to be performed.
|
||||
string type = 1;
|
||||
@@ -19,6 +19,9 @@ message TaskMessage {
|
||||
// Payload holds data needed to process the task.
|
||||
bytes payload = 2;
|
||||
|
||||
// Headers holds additional metadata for the task.
|
||||
map<string, string> headers = 15;
|
||||
|
||||
// Unique identifier for the task.
|
||||
string id = 3;
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/hibiken/asynq/internal/base"
|
||||
"github.com/hibiken/asynq/internal/errors"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
@@ -264,7 +264,9 @@ for i=1,2 do
|
||||
if (table.getn(ids) > 0) then
|
||||
for _, id in ipairs(ids) do
|
||||
local bytes = redis.call("MEMORY", "USAGE", ARGV[1] .. id)
|
||||
sample_total = sample_total + bytes
|
||||
if bytes then
|
||||
sample_total = sample_total + bytes
|
||||
end
|
||||
end
|
||||
local n = redis.call("LLEN", KEYS[i])
|
||||
local avg = sample_total / table.getn(ids)
|
||||
@@ -281,7 +283,9 @@ for i=3,6 do
|
||||
if (table.getn(ids) > 0) then
|
||||
for _, id in ipairs(ids) do
|
||||
local bytes = redis.call("MEMORY", "USAGE", ARGV[1] .. id)
|
||||
sample_total = sample_total + bytes
|
||||
if bytes then
|
||||
sample_total = sample_total + bytes
|
||||
end
|
||||
end
|
||||
local n = redis.call("ZCARD", KEYS[i])
|
||||
local avg = sample_total / table.getn(ids)
|
||||
@@ -304,13 +308,17 @@ if table.getn(groups) > 0 then
|
||||
local ids = redis.call("ZRANGE", group_key, 0, sample_size - 1)
|
||||
for _, id in ipairs(ids) do
|
||||
local bytes = redis.call("MEMORY", "USAGE", ARGV[1] .. id)
|
||||
agg_task_sample_total = agg_task_sample_total + bytes
|
||||
agg_task_sample_size = agg_task_sample_size + 1
|
||||
if bytes then
|
||||
agg_task_sample_total = agg_task_sample_total + bytes
|
||||
agg_task_sample_size = agg_task_sample_size + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local avg = agg_task_sample_total / agg_task_sample_size
|
||||
memusg = memusg + (avg * agg_task_count)
|
||||
if agg_task_sample_size > 0 then
|
||||
local avg = agg_task_sample_total / agg_task_sample_size
|
||||
memusg = memusg + (avg * agg_task_count)
|
||||
end
|
||||
end
|
||||
return memusg
|
||||
`)
|
||||
@@ -1412,6 +1420,93 @@ func (r *RDB) archiveAll(src, dst, qname string) (int64, error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Input:
|
||||
// KEYS[1] -> asynq:{<qname>}:t:<task_id>
|
||||
// --
|
||||
// ARGV[1] -> task message data
|
||||
//
|
||||
// Output:
|
||||
// Numeric code indicating the status:
|
||||
// Returns 1 if task is successfully updated.
|
||||
// Returns 0 if task is not found.
|
||||
// Returns -1 if task is not in scheduled state.
|
||||
var updateTaskPayloadCmd = redis.NewScript(`
|
||||
-- Check if given taks exists
|
||||
if redis.call("EXISTS", KEYS[1]) == 0 then
|
||||
return 0
|
||||
end
|
||||
local state, pending_since, group, unique_key = unpack(redis.call("HMGET", KEYS[1], "state", "pending_since", "group", "unique_key"))
|
||||
if state ~= "scheduled" then
|
||||
return -1
|
||||
end
|
||||
local redis_call_args = {"state", state}
|
||||
|
||||
if pending_since then
|
||||
table.insert(redis_call_args, "pending_since")
|
||||
table.insert(redis_call_args, pending_since)
|
||||
end
|
||||
if group then
|
||||
table.insert(redis_call_args, "group")
|
||||
table.insert(redis_call_args, group)
|
||||
end
|
||||
if unique_key then
|
||||
table.insert(redis_call_args, "unique_key")
|
||||
table.insert(redis_call_args, unique_key)
|
||||
end
|
||||
redis.call("HSET", KEYS[1], "msg", ARGV[1], unpack(redis_call_args))
|
||||
return 1
|
||||
`)
|
||||
|
||||
// UpdateTaskPayload finds a task that matches the id from the given queue and updates it's payload.
|
||||
// It returns nil if it successfully updated the task payload.
|
||||
//
|
||||
// If a queue with the given name doesn't exist, it returns QueueNotFoundError.
|
||||
// If a task with the given id doesn't exist in the queue, it returns TaskNotFoundError
|
||||
// If a task is in active state it returns non-nil error with Code FailedPrecondition.
|
||||
func (r *RDB) UpdateTaskPayload(qname, id string, payload []byte) error {
|
||||
var op errors.Op = "rdb.UpdateTask"
|
||||
if err := r.checkQueueExists(qname); err != nil {
|
||||
return errors.E(op, errors.CanonicalCode(err), err)
|
||||
}
|
||||
|
||||
taskInfo, err := r.GetTaskInfo(qname, id)
|
||||
if err != nil {
|
||||
return errors.E(op, errors.Unknown, err)
|
||||
}
|
||||
|
||||
taskInfo.Message.Payload = payload
|
||||
|
||||
encoded, err := base.EncodeMessage(taskInfo.Message)
|
||||
if err != nil {
|
||||
return errors.E(op, errors.Unknown, fmt.Sprintf("cannot encode message: %v", err))
|
||||
}
|
||||
keys := []string{
|
||||
base.TaskKey(qname, id),
|
||||
}
|
||||
argv := []interface{}{
|
||||
encoded,
|
||||
}
|
||||
|
||||
res, err := updateTaskPayloadCmd.Run(context.Background(), r.client, keys, argv...).Result()
|
||||
if err != nil {
|
||||
return errors.E(op, errors.Unknown, err)
|
||||
}
|
||||
n, ok := res.(int64)
|
||||
if !ok {
|
||||
return errors.E(op, errors.Internal, fmt.Sprintf("cast error: updateTaskCmd script returned unexported value %v", res))
|
||||
}
|
||||
switch n {
|
||||
case 1:
|
||||
return nil
|
||||
case 0:
|
||||
return errors.E(op, errors.NotFound, &errors.TaskNotFoundError{Queue: qname, ID: id})
|
||||
case -1:
|
||||
return errors.E(op, errors.FailedPrecondition, "cannot update task that is not in scheduled state.")
|
||||
default:
|
||||
return errors.E(op, errors.Internal, fmt.Sprintf("unexpected return value from updateTaskCmd script: %d", n))
|
||||
}
|
||||
}
|
||||
|
||||
// Input:
|
||||
// KEYS[1] -> asynq:{<qname>}:t:<task_id>
|
||||
// KEYS[2] -> asynq:{<qname>}:groups
|
||||
@@ -1832,6 +1927,7 @@ func (r *RDB) RemoveQueue(qname string, force bool) error {
|
||||
if err := r.client.SRem(context.Background(), base.AllQueues, qname).Err(); err != nil {
|
||||
return errors.E(op, errors.Unknown, err)
|
||||
}
|
||||
r.queuesPublished.Delete(qname)
|
||||
return nil
|
||||
case -1:
|
||||
return errors.E(op, errors.NotFound, &errors.QueueNotEmptyError{Queue: qname})
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -26,8 +27,9 @@ const LeaseDuration = 30 * time.Second
|
||||
|
||||
// RDB is a client interface to query and mutate task queues.
|
||||
type RDB struct {
|
||||
client redis.UniversalClient
|
||||
clock timeutil.Clock
|
||||
client redis.UniversalClient
|
||||
clock timeutil.Clock
|
||||
queuesPublished sync.Map
|
||||
}
|
||||
|
||||
// NewRDB returns a new instance of RDB.
|
||||
@@ -112,8 +114,11 @@ func (r *RDB) Enqueue(ctx context.Context, msg *base.TaskMessage) error {
|
||||
if err != nil {
|
||||
return errors.E(op, errors.Unknown, fmt.Sprintf("cannot encode message: %v", err))
|
||||
}
|
||||
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
|
||||
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
|
||||
if _, found := r.queuesPublished.Load(msg.Queue); !found {
|
||||
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
|
||||
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
|
||||
}
|
||||
r.queuesPublished.Store(msg.Queue, true)
|
||||
}
|
||||
keys := []string{
|
||||
base.TaskKey(msg.Queue, msg.ID),
|
||||
@@ -134,6 +139,118 @@ func (r *RDB) Enqueue(ctx context.Context, msg *base.TaskMessage) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// BatchEnqueue adds all given tasks to Redis using a single pipeline round-trip.
|
||||
// Each item is either enqueued immediately (ProcessAt is zero) or added to the
|
||||
// scheduled sorted set.
|
||||
//
|
||||
// WARNING: tasks whose IDs already exist in Redis are silently skipped.
|
||||
//
|
||||
// The pipeline executes independent Lua scripts per task — there is no
|
||||
// MULTI/EXEC wrapping the batch, so individual tasks may succeed or fail
|
||||
// independently. The returned int is the number of tasks actually written;
|
||||
// skipped duplicates do not count. The returned error is non-nil only when the
|
||||
// pipeline call itself fails (network error, context cancellation, etc.), in
|
||||
// which case no individual result should be trusted.
|
||||
//
|
||||
// Message encoding errors cause an immediate return before any Redis I/O.
|
||||
func (r *RDB) BatchEnqueue(ctx context.Context, items []base.BatchEnqueueItem) (int, error) {
|
||||
var op errors.Op = "rdb.BatchEnqueue"
|
||||
if len(items) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Preload Lua scripts so that EVALSHA inside the pipeline does not fail with
|
||||
// NOSCRIPT. Script.Run on a pipeline only sends EVALSHA (unlike non-pipeline
|
||||
// Run which retries with EVAL on NOSCRIPT).
|
||||
needsEnqueue, needsSchedule := false, false
|
||||
for _, item := range items {
|
||||
if item.ProcessAt.IsZero() {
|
||||
needsEnqueue = true
|
||||
} else {
|
||||
needsSchedule = true
|
||||
}
|
||||
if needsEnqueue && needsSchedule {
|
||||
break
|
||||
}
|
||||
}
|
||||
if needsEnqueue {
|
||||
if err := enqueueCmd.Load(ctx, r.client).Err(); err != nil {
|
||||
return 0, errors.E(op, errors.Unknown, fmt.Sprintf("failed to load enqueue script: %v", err))
|
||||
}
|
||||
}
|
||||
if needsSchedule {
|
||||
if err := scheduleCmd.Load(ctx, r.client).Err(); err != nil {
|
||||
return 0, errors.E(op, errors.Unknown, fmt.Sprintf("failed to load schedule script: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
pipe := r.client.Pipeline()
|
||||
|
||||
// Track which pipeline slot holds each item's script result.
|
||||
scriptIdxs := make([]int, 0, len(items))
|
||||
pipeLen := 0
|
||||
|
||||
// Track queues we add to AllQueues in this pipeline so we can roll back the
|
||||
// in-memory cache on failure.
|
||||
var newQueues []string
|
||||
|
||||
now := r.clock.Now().UnixNano()
|
||||
|
||||
for _, item := range items {
|
||||
encoded, err := base.EncodeMessage(item.Msg)
|
||||
if err != nil {
|
||||
return 0, errors.E(op, errors.Unknown, fmt.Sprintf("cannot encode message: %v", err))
|
||||
}
|
||||
if _, found := r.queuesPublished.Load(item.Msg.Queue); !found {
|
||||
pipe.SAdd(ctx, base.AllQueues, item.Msg.Queue)
|
||||
r.queuesPublished.Store(item.Msg.Queue, true)
|
||||
newQueues = append(newQueues, item.Msg.Queue)
|
||||
pipeLen++
|
||||
}
|
||||
|
||||
if item.ProcessAt.IsZero() {
|
||||
keys := []string{
|
||||
base.TaskKey(item.Msg.Queue, item.Msg.ID),
|
||||
base.PendingKey(item.Msg.Queue),
|
||||
}
|
||||
argv := []interface{}{encoded, item.Msg.ID, now}
|
||||
enqueueCmd.Run(ctx, pipe, keys, argv...)
|
||||
} else {
|
||||
keys := []string{
|
||||
base.TaskKey(item.Msg.Queue, item.Msg.ID),
|
||||
base.ScheduledKey(item.Msg.Queue),
|
||||
}
|
||||
argv := []interface{}{encoded, item.ProcessAt.Unix(), item.Msg.ID}
|
||||
scheduleCmd.Run(ctx, pipe, keys, argv...)
|
||||
}
|
||||
scriptIdxs = append(scriptIdxs, pipeLen)
|
||||
pipeLen++
|
||||
}
|
||||
|
||||
cmds, err := pipe.Exec(ctx)
|
||||
if err != nil && err != redis.Nil {
|
||||
for _, q := range newQueues {
|
||||
r.queuesPublished.Delete(q)
|
||||
}
|
||||
return 0, errors.E(op, errors.Unknown, fmt.Sprintf("redis pipeline error: %v", err))
|
||||
}
|
||||
|
||||
enqueued := 0
|
||||
for _, idx := range scriptIdxs {
|
||||
if idx >= len(cmds) {
|
||||
continue
|
||||
}
|
||||
res, err := cmds[idx].(*redis.Cmd).Result()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if n, ok := res.(int64); ok && n == 1 {
|
||||
enqueued++
|
||||
}
|
||||
}
|
||||
return enqueued, nil
|
||||
}
|
||||
|
||||
// enqueueUniqueCmd enqueues the task message if the task is unique.
|
||||
//
|
||||
// KEYS[1] -> unique key
|
||||
@@ -174,8 +291,11 @@ func (r *RDB) EnqueueUnique(ctx context.Context, msg *base.TaskMessage, ttl time
|
||||
if err != nil {
|
||||
return errors.E(op, errors.Internal, "cannot encode task message: %v", err)
|
||||
}
|
||||
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
|
||||
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
|
||||
if _, found := r.queuesPublished.Load(msg.Queue); !found {
|
||||
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
|
||||
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
|
||||
}
|
||||
r.queuesPublished.Store(msg.Queue, true)
|
||||
}
|
||||
keys := []string{
|
||||
msg.UniqueKey,
|
||||
@@ -529,8 +649,11 @@ func (r *RDB) AddToGroup(ctx context.Context, msg *base.TaskMessage, groupKey st
|
||||
if err != nil {
|
||||
return errors.E(op, errors.Unknown, fmt.Sprintf("cannot encode message: %v", err))
|
||||
}
|
||||
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
|
||||
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
|
||||
if _, found := r.queuesPublished.Load(msg.Queue); !found {
|
||||
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
|
||||
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
|
||||
}
|
||||
r.queuesPublished.Store(msg.Queue, true)
|
||||
}
|
||||
keys := []string{
|
||||
base.TaskKey(msg.Queue, msg.ID),
|
||||
@@ -591,8 +714,11 @@ func (r *RDB) AddToGroupUnique(ctx context.Context, msg *base.TaskMessage, group
|
||||
if err != nil {
|
||||
return errors.E(op, errors.Unknown, fmt.Sprintf("cannot encode message: %v", err))
|
||||
}
|
||||
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
|
||||
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
|
||||
if _, found := r.queuesPublished.Load(msg.Queue); !found {
|
||||
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
|
||||
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
|
||||
}
|
||||
r.queuesPublished.Store(msg.Queue, true)
|
||||
}
|
||||
keys := []string{
|
||||
base.TaskKey(msg.Queue, msg.ID),
|
||||
@@ -648,8 +774,11 @@ func (r *RDB) Schedule(ctx context.Context, msg *base.TaskMessage, processAt tim
|
||||
if err != nil {
|
||||
return errors.E(op, errors.Unknown, fmt.Sprintf("cannot encode message: %v", err))
|
||||
}
|
||||
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
|
||||
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
|
||||
if _, found := r.queuesPublished.Load(msg.Queue); !found {
|
||||
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
|
||||
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
|
||||
}
|
||||
r.queuesPublished.Store(msg.Queue, true)
|
||||
}
|
||||
keys := []string{
|
||||
base.TaskKey(msg.Queue, msg.ID),
|
||||
@@ -707,8 +836,11 @@ func (r *RDB) ScheduleUnique(ctx context.Context, msg *base.TaskMessage, process
|
||||
if err != nil {
|
||||
return errors.E(op, errors.Internal, fmt.Sprintf("cannot encode task message: %v", err))
|
||||
}
|
||||
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
|
||||
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
|
||||
if _, found := r.queuesPublished.Load(msg.Queue); !found {
|
||||
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
|
||||
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
|
||||
}
|
||||
r.queuesPublished.Store(msg.Queue, true)
|
||||
}
|
||||
keys := []string{
|
||||
msg.UniqueKey,
|
||||
@@ -1417,7 +1549,7 @@ func (r *RDB) ClearServerState(host string, pid int, serverID string) error {
|
||||
|
||||
// KEYS[1] -> asynq:schedulers:{<schedulerID>}
|
||||
// ARGV[1] -> TTL in seconds
|
||||
// ARGV[2:] -> schedler entries
|
||||
// ARGV[2:] -> scheduler entries
|
||||
var writeSchedulerEntriesCmd = redis.NewScript(`
|
||||
redis.call("DEL", KEYS[1])
|
||||
for i = 2, #ARGV do
|
||||
@@ -1448,10 +1580,10 @@ func (r *RDB) WriteSchedulerEntries(schedulerID string, entries []*base.Schedule
|
||||
}
|
||||
|
||||
// ClearSchedulerEntries deletes scheduler entries data from redis.
|
||||
func (r *RDB) ClearSchedulerEntries(scheduelrID string) error {
|
||||
func (r *RDB) ClearSchedulerEntries(schedulerID string) error {
|
||||
var op errors.Op = "rdb.ClearSchedulerEntries"
|
||||
ctx := context.Background()
|
||||
key := base.SchedulerEntriesKey(scheduelrID)
|
||||
key := base.SchedulerEntriesKey(schedulerID)
|
||||
if err := r.client.ZRem(ctx, base.AllSchedulers, key).Err(); err != nil {
|
||||
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "zrem", Err: err})
|
||||
}
|
||||
@@ -1468,6 +1600,7 @@ func (r *RDB) CancelationPubSub() (*redis.PubSub, error) {
|
||||
pubsub := r.client.Subscribe(ctx, base.CancelChannel)
|
||||
_, err := pubsub.Receive(ctx)
|
||||
if err != nil {
|
||||
pubsub.Close()
|
||||
return nil, errors.E(op, errors.Unknown, fmt.Sprintf("redis pubsub receive error: %v", err))
|
||||
}
|
||||
return pubsub, nil
|
||||
|
||||
@@ -160,6 +160,209 @@ func TestEnqueueTaskIdConflictError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchEnqueue(t *testing.T) {
|
||||
r := setup(t)
|
||||
defer r.Close()
|
||||
|
||||
t1 := h.NewTaskMessage("send_email", h.JSON(map[string]interface{}{"to": "user@example.com"}))
|
||||
t2 := h.NewTaskMessageWithQueue("generate_csv", h.JSON(map[string]interface{}{}), "csv")
|
||||
t3 := h.NewTaskMessageWithQueue("sync", nil, "low")
|
||||
|
||||
enqueueTime := time.Now()
|
||||
r.SetClock(timeutil.NewSimulatedClock(enqueueTime))
|
||||
|
||||
t.Run("enqueue multiple tasks", func(t *testing.T) {
|
||||
h.FlushDB(t, r.client)
|
||||
items := []base.BatchEnqueueItem{
|
||||
{Msg: t1},
|
||||
{Msg: t2},
|
||||
{Msg: t3},
|
||||
}
|
||||
|
||||
n, err := r.BatchEnqueue(context.Background(), items)
|
||||
if err != nil {
|
||||
t.Fatalf("BatchEnqueue returned error: %v", err)
|
||||
}
|
||||
if n != 3 {
|
||||
t.Errorf("BatchEnqueue returned %d, want 3", n)
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
msg := item.Msg
|
||||
pendingKey := base.PendingKey(msg.Queue)
|
||||
pendingIDs := r.client.LRange(context.Background(), pendingKey, 0, -1).Val()
|
||||
found := false
|
||||
for _, id := range pendingIDs {
|
||||
if id == msg.ID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("task %s not found in pending list %s", msg.ID, pendingKey)
|
||||
}
|
||||
|
||||
taskKey := base.TaskKey(msg.Queue, msg.ID)
|
||||
state := r.client.HGet(context.Background(), taskKey, "state").Val()
|
||||
if state != "pending" {
|
||||
t.Errorf("state for task %s = %q, want %q", msg.ID, state, "pending")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty batch", func(t *testing.T) {
|
||||
h.FlushDB(t, r.client)
|
||||
|
||||
n, err := r.BatchEnqueue(context.Background(), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("BatchEnqueue(nil) returned error: %v", err)
|
||||
}
|
||||
if n != 0 {
|
||||
t.Errorf("BatchEnqueue(nil) returned %d, want 0", n)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("duplicate IDs skipped", func(t *testing.T) {
|
||||
h.FlushDB(t, r.client)
|
||||
|
||||
if err := r.Enqueue(context.Background(), t1); err != nil {
|
||||
t.Fatalf("pre-enqueue failed: %v", err)
|
||||
}
|
||||
|
||||
dup := *t1
|
||||
newMsg := h.NewTaskMessage("new_task", nil)
|
||||
|
||||
items := []base.BatchEnqueueItem{
|
||||
{Msg: &dup},
|
||||
{Msg: newMsg},
|
||||
}
|
||||
n, err := r.BatchEnqueue(context.Background(), items)
|
||||
if err != nil {
|
||||
t.Fatalf("BatchEnqueue returned error: %v", err)
|
||||
}
|
||||
if n != 1 {
|
||||
t.Errorf("BatchEnqueue returned %d, want 1 (duplicate should be skipped)", n)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("scheduled tasks", func(t *testing.T) {
|
||||
h.FlushDB(t, r.client)
|
||||
|
||||
future := time.Now().Add(1 * time.Hour)
|
||||
s1 := h.NewTaskMessage("deferred_email", nil)
|
||||
items := []base.BatchEnqueueItem{
|
||||
{Msg: t1},
|
||||
{Msg: s1, ProcessAt: future},
|
||||
}
|
||||
|
||||
n, err := r.BatchEnqueue(context.Background(), items)
|
||||
if err != nil {
|
||||
t.Fatalf("BatchEnqueue returned error: %v", err)
|
||||
}
|
||||
if n != 2 {
|
||||
t.Errorf("BatchEnqueue returned %d, want 2", n)
|
||||
}
|
||||
|
||||
// Immediate task should be in pending.
|
||||
pendingIDs := r.client.LRange(context.Background(), base.PendingKey(t1.Queue), 0, -1).Val()
|
||||
foundPending := false
|
||||
for _, id := range pendingIDs {
|
||||
if id == t1.ID {
|
||||
foundPending = true
|
||||
}
|
||||
}
|
||||
if !foundPending {
|
||||
t.Errorf("immediate task %s not found in pending list", t1.ID)
|
||||
}
|
||||
|
||||
// Scheduled task should be in scheduled set.
|
||||
scheduledIDs := r.client.ZRange(context.Background(), base.ScheduledKey(s1.Queue), 0, -1).Val()
|
||||
foundScheduled := false
|
||||
for _, id := range scheduledIDs {
|
||||
if id == s1.ID {
|
||||
foundScheduled = true
|
||||
}
|
||||
}
|
||||
if !foundScheduled {
|
||||
t.Errorf("scheduled task %s not found in scheduled set", s1.ID)
|
||||
}
|
||||
|
||||
taskKey := base.TaskKey(s1.Queue, s1.ID)
|
||||
state := r.client.HGet(context.Background(), taskKey, "state").Val()
|
||||
if state != "scheduled" {
|
||||
t.Errorf("state for scheduled task %s = %q, want %q", s1.ID, state, "scheduled")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("pipeline error from cancelled context", func(t *testing.T) {
|
||||
h.FlushDB(t, r.client)
|
||||
|
||||
msg := h.NewTaskMessage("pipeline_error_task", nil)
|
||||
items := []base.BatchEnqueueItem{{Msg: msg}}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
_, err := r.BatchEnqueue(ctx, items)
|
||||
if err == nil {
|
||||
t.Error("BatchEnqueue with cancelled context returned nil error, want non-nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnqueueQueueCache(t *testing.T) {
|
||||
r := setup(t)
|
||||
defer r.Close()
|
||||
t1 := h.NewTaskMessageWithQueue("sync1", nil, "low")
|
||||
|
||||
enqueueTime := time.Now()
|
||||
clock := timeutil.NewSimulatedClock(enqueueTime)
|
||||
r.SetClock(clock)
|
||||
|
||||
err := r.Enqueue(context.Background(), t1)
|
||||
if err != nil {
|
||||
t.Fatalf("(*RDB).Enqueue(msg) = %v, want nil", err)
|
||||
}
|
||||
|
||||
// Check queue is in the AllQueues set.
|
||||
if !r.client.SIsMember(context.Background(), base.AllQueues, t1.Queue).Val() {
|
||||
t.Fatalf("%q is not a member of SET %q", t1.Queue, base.AllQueues)
|
||||
}
|
||||
|
||||
if _, ok := r.queuesPublished.Load(t1.Queue); !ok {
|
||||
t.Fatalf("%q is not cached in queuesPublished", t1.Queue)
|
||||
}
|
||||
|
||||
t.Run("remove-queue", func(t *testing.T) {
|
||||
err := r.RemoveQueue(t1.Queue, true)
|
||||
if err != nil {
|
||||
t.Errorf("(*RDB).RemoveQueue(%q, %t) = %v, want nil", t1.Queue, true, err)
|
||||
}
|
||||
|
||||
if _, ok := r.queuesPublished.Load(t1.Queue); ok {
|
||||
t.Fatalf("%q is still cached in queuesPublished", t1.Queue)
|
||||
}
|
||||
|
||||
if r.client.SIsMember(context.Background(), base.AllQueues, t1.Queue).Val() {
|
||||
t.Fatalf("%q is a member of SET %q", t1.Queue, base.AllQueues)
|
||||
}
|
||||
|
||||
err = r.Enqueue(context.Background(), t1)
|
||||
if err != nil {
|
||||
t.Fatalf("(*RDB).Enqueue(msg) = %v, want nil", err)
|
||||
}
|
||||
|
||||
// Check queue is in the AllQueues set.
|
||||
if !r.client.SIsMember(context.Background(), base.AllQueues, t1.Queue).Val() {
|
||||
t.Fatalf("%q is not a member of SET %q", t1.Queue, base.AllQueues)
|
||||
}
|
||||
|
||||
if _, ok := r.queuesPublished.Load(t1.Queue); !ok {
|
||||
t.Fatalf("%q is not cached in queuesPublished", t1.Queue)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnqueueUnique(t *testing.T) {
|
||||
r := setup(t)
|
||||
defer r.Close()
|
||||
@@ -3221,6 +3424,29 @@ func TestCancelationPubSub(t *testing.T) {
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
func TestCancelationPubSubReceiveError(t *testing.T) {
|
||||
// Use a client connected to a non-existent Redis server to trigger
|
||||
// a Receive() error. This verifies that the pubsub connection is
|
||||
// closed on error, preventing connection leaks.
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:0", // invalid port — connection will fail
|
||||
})
|
||||
r := NewRDB(client)
|
||||
defer r.Close()
|
||||
|
||||
pubsub, err := r.CancelationPubSub()
|
||||
if err == nil {
|
||||
// If no error, we must clean up the pubsub.
|
||||
if pubsub != nil {
|
||||
pubsub.Close()
|
||||
}
|
||||
t.Fatal("(*RDB).CancelationPubSub() expected to return an error when redis is unreachable")
|
||||
}
|
||||
if pubsub != nil {
|
||||
t.Error("(*RDB).CancelationPubSub() expected nil pubsub on error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteResult(t *testing.T) {
|
||||
r := setup(t)
|
||||
defer r.Close()
|
||||
|
||||
@@ -64,6 +64,15 @@ func (tb *TestBroker) EnqueueUnique(ctx context.Context, msg *base.TaskMessage,
|
||||
return tb.real.EnqueueUnique(ctx, msg, ttl)
|
||||
}
|
||||
|
||||
func (tb *TestBroker) BatchEnqueue(ctx context.Context, items []base.BatchEnqueueItem) (int, error) {
|
||||
tb.mu.Lock()
|
||||
defer tb.mu.Unlock()
|
||||
if tb.sleeping {
|
||||
return 0, errRedisDown
|
||||
}
|
||||
return tb.real.BatchEnqueue(ctx, items)
|
||||
}
|
||||
|
||||
func (tb *TestBroker) Dequeue(qnames ...string) (*base.TaskMessage, time.Time, error) {
|
||||
tb.mu.Lock()
|
||||
defer tb.mu.Unlock()
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// PeriodicTaskManager manages scheduling of periodic tasks.
|
||||
@@ -27,9 +29,12 @@ type PeriodicTaskManagerOpts struct {
|
||||
// Required: must be non nil
|
||||
PeriodicTaskConfigProvider PeriodicTaskConfigProvider
|
||||
|
||||
// Required: must be non nil
|
||||
// Optional: if RedisUniversalClient is nil must be non nil
|
||||
RedisConnOpt RedisConnOpt
|
||||
|
||||
// Optional: if RedisUniversalClient is non nil, RedisConnOpt is ignored.
|
||||
RedisUniversalClient redis.UniversalClient
|
||||
|
||||
// Optional: scheduler options
|
||||
*SchedulerOpts
|
||||
|
||||
@@ -45,10 +50,16 @@ func NewPeriodicTaskManager(opts PeriodicTaskManagerOpts) (*PeriodicTaskManager,
|
||||
if opts.PeriodicTaskConfigProvider == nil {
|
||||
return nil, fmt.Errorf("PeriodicTaskConfigProvider cannot be nil")
|
||||
}
|
||||
if opts.RedisConnOpt == nil {
|
||||
return nil, fmt.Errorf("RedisConnOpt cannot be nil")
|
||||
if opts.RedisConnOpt == nil && opts.RedisUniversalClient == nil {
|
||||
return nil, fmt.Errorf("RedisConnOpt/RedisUniversalClient cannot be nil")
|
||||
}
|
||||
scheduler := NewScheduler(opts.RedisConnOpt, opts.SchedulerOpts)
|
||||
var scheduler *Scheduler
|
||||
if opts.RedisUniversalClient != nil {
|
||||
scheduler = NewSchedulerFromRedisClient(opts.RedisUniversalClient, opts.SchedulerOpts)
|
||||
} else {
|
||||
scheduler = NewScheduler(opts.RedisConnOpt, opts.SchedulerOpts)
|
||||
}
|
||||
|
||||
syncInterval := opts.SyncInterval
|
||||
if syncInterval == 0 {
|
||||
syncInterval = defaultSyncInterval
|
||||
@@ -111,10 +122,10 @@ func (mgr *PeriodicTaskManager) Start() error {
|
||||
panic("asynq: cannot start uninitialized PeriodicTaskManager; use NewPeriodicTaskManager to initialize")
|
||||
}
|
||||
if err := mgr.initialSync(); err != nil {
|
||||
return fmt.Errorf("asynq: %v", err)
|
||||
return fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
if err := mgr.s.Start(); err != nil {
|
||||
return fmt.Errorf("asynq: %v", err)
|
||||
return fmt.Errorf("asynq: %w", err)
|
||||
}
|
||||
mgr.wg.Add(1)
|
||||
go func() {
|
||||
@@ -157,11 +168,11 @@ func (mgr *PeriodicTaskManager) Run() error {
|
||||
func (mgr *PeriodicTaskManager) initialSync() error {
|
||||
configs, err := mgr.p.GetConfigs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("initial call to GetConfigs failed: %v", err)
|
||||
return fmt.Errorf("initial call to GetConfigs failed: %w", err)
|
||||
}
|
||||
for _, c := range configs {
|
||||
if err := validatePeriodicTaskConfig(c); err != nil {
|
||||
return fmt.Errorf("initial call to GetConfigs contained an invalid config: %v", err)
|
||||
return fmt.Errorf("initial call to GetConfigs contained an invalid config: %w", err)
|
||||
}
|
||||
}
|
||||
mgr.add(configs)
|
||||
@@ -172,8 +183,8 @@ func (mgr *PeriodicTaskManager) add(configs []*PeriodicTaskConfig) {
|
||||
for _, c := range configs {
|
||||
entryID, err := mgr.s.Register(c.Cronspec, c.Task, c.Opts...)
|
||||
if err != nil {
|
||||
mgr.s.logger.Errorf("Failed to register periodic task: cronspec=%q task=%q",
|
||||
c.Cronspec, c.Task.Type())
|
||||
mgr.s.logger.Errorf("Failed to register periodic task: cronspec=%q task=%q err=%v",
|
||||
c.Cronspec, c.Task.Type(), err)
|
||||
continue
|
||||
}
|
||||
mgr.m[c.hash()] = entryID
|
||||
|
||||
@@ -230,6 +230,7 @@ func (p *processor) exec() {
|
||||
ctx: ctx,
|
||||
},
|
||||
)
|
||||
task.headers = msg.Headers
|
||||
resCh <- p.perform(ctx, task)
|
||||
}()
|
||||
|
||||
@@ -333,7 +334,7 @@ var RevokeTask = errors.New("revoke task")
|
||||
|
||||
func (p *processor) handleFailedMessage(ctx context.Context, l *base.Lease, msg *base.TaskMessage, err error) {
|
||||
if p.errHandler != nil {
|
||||
p.errHandler.HandleError(ctx, NewTask(msg.Type, msg.Payload), err)
|
||||
p.errHandler.HandleError(ctx, NewTaskWithHeaders(msg.Type, msg.Payload, msg.Headers), err)
|
||||
}
|
||||
switch {
|
||||
case errors.Is(err, RevokeTask):
|
||||
@@ -354,7 +355,7 @@ func (p *processor) retry(l *base.Lease, msg *base.TaskMessage, e error, isFailu
|
||||
}
|
||||
ctx, cancel := context.WithDeadline(context.Background(), l.Deadline())
|
||||
defer cancel()
|
||||
d := p.retryDelayFunc(msg.Retried, e, NewTask(msg.Type, msg.Payload))
|
||||
d := p.retryDelayFunc(msg.Retried, e, NewTaskWithHeaders(msg.Type, msg.Payload, msg.Headers))
|
||||
retryAt := time.Now().Add(d)
|
||||
err := p.broker.Retry(ctx, msg, retryAt, e.Error(), isFailure)
|
||||
if err != nil {
|
||||
|
||||
@@ -112,7 +112,7 @@ func (r *recoverer) recoverStaleAggregationSets() {
|
||||
}
|
||||
|
||||
func (r *recoverer) retry(msg *base.TaskMessage, err error) {
|
||||
delay := r.retryDelayFunc(msg.Retried, err, NewTask(msg.Type, msg.Payload))
|
||||
delay := r.retryDelayFunc(msg.Retried, err, NewTaskWithHeaders(msg.Type, msg.Payload, msg.Headers))
|
||||
retryAt := time.Now().Add(delay)
|
||||
if err := r.broker.Retry(context.Background(), msg, retryAt, err.Error(), r.isFailureFunc(err)); err != nil {
|
||||
r.logger.Warnf("recoverer: could not retry lease expired task: %v", err)
|
||||
|
||||
90
scheduler.go
90
scheduler.go
@@ -26,16 +26,17 @@ type Scheduler struct {
|
||||
|
||||
state *serverState
|
||||
|
||||
logger *log.Logger
|
||||
client *Client
|
||||
rdb *rdb.RDB
|
||||
cron *cron.Cron
|
||||
location *time.Location
|
||||
done chan struct{}
|
||||
wg sync.WaitGroup
|
||||
preEnqueueFunc func(task *Task, opts []Option)
|
||||
postEnqueueFunc func(info *TaskInfo, err error)
|
||||
errHandler func(task *Task, opts []Option, err error)
|
||||
heartbeatInterval time.Duration
|
||||
logger *log.Logger
|
||||
client *Client
|
||||
rdb *rdb.RDB
|
||||
cron *cron.Cron
|
||||
location *time.Location
|
||||
done chan struct{}
|
||||
wg sync.WaitGroup
|
||||
preEnqueueFunc func(task *Task, opts []Option)
|
||||
postEnqueueFunc func(info *TaskInfo, err error)
|
||||
errHandler func(task *Task, opts []Option, err error)
|
||||
|
||||
// guards idmap
|
||||
mu sync.Mutex
|
||||
@@ -43,20 +44,25 @@ type Scheduler struct {
|
||||
// to avoid using cron.EntryID as the public API of
|
||||
// the Scheduler.
|
||||
idmap map[string]cron.EntryID
|
||||
// When a Scheduler has been created with an existing Redis connection, we do
|
||||
// not want to close it.
|
||||
sharedConnection bool
|
||||
}
|
||||
|
||||
const defaultHeartbeatInterval = 10 * time.Second
|
||||
|
||||
// NewScheduler returns a new Scheduler instance given the redis connection option.
|
||||
// The parameter opts is optional, defaults will be used if opts is set to nil
|
||||
func NewScheduler(r RedisConnOpt, opts *SchedulerOpts) *Scheduler {
|
||||
scheduler := newScheduler(opts)
|
||||
|
||||
redisClient, ok := r.MakeRedisClient().(redis.UniversalClient)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("asynq: unsupported RedisConnOpt type %T", r))
|
||||
}
|
||||
scheduler := NewSchedulerFromRedisClient(redisClient, opts)
|
||||
scheduler.sharedConnection = false
|
||||
|
||||
rdb := rdb.NewRDB(redisClient)
|
||||
|
||||
scheduler.rdb = rdb
|
||||
scheduler.client = &Client{broker: rdb, sharedConnection: false}
|
||||
|
||||
return scheduler
|
||||
}
|
||||
|
||||
@@ -64,10 +70,24 @@ func NewScheduler(r RedisConnOpt, opts *SchedulerOpts) *Scheduler {
|
||||
// The parameter opts is optional, defaults will be used if opts is set to nil.
|
||||
// Warning: The underlying redis connection pool will not be closed by Asynq, you are responsible for closing it.
|
||||
func NewSchedulerFromRedisClient(c redis.UniversalClient, opts *SchedulerOpts) *Scheduler {
|
||||
scheduler := newScheduler(opts)
|
||||
|
||||
scheduler.rdb = rdb.NewRDB(c)
|
||||
scheduler.client = NewClientFromRedisClient(c)
|
||||
|
||||
return scheduler
|
||||
}
|
||||
|
||||
func newScheduler(opts *SchedulerOpts) *Scheduler {
|
||||
if opts == nil {
|
||||
opts = &SchedulerOpts{}
|
||||
}
|
||||
|
||||
heartbeatInterval := opts.HeartbeatInterval
|
||||
if heartbeatInterval <= 0 {
|
||||
heartbeatInterval = defaultHeartbeatInterval
|
||||
}
|
||||
|
||||
logger := log.NewLogger(opts.Logger)
|
||||
loglevel := opts.LogLevel
|
||||
if loglevel == level_unspecified {
|
||||
@@ -81,18 +101,17 @@ func NewSchedulerFromRedisClient(c redis.UniversalClient, opts *SchedulerOpts) *
|
||||
}
|
||||
|
||||
return &Scheduler{
|
||||
id: generateSchedulerID(),
|
||||
state: &serverState{value: srvStateNew},
|
||||
logger: logger,
|
||||
client: NewClientFromRedisClient(c),
|
||||
rdb: rdb.NewRDB(c),
|
||||
cron: cron.New(cron.WithLocation(loc)),
|
||||
location: loc,
|
||||
done: make(chan struct{}),
|
||||
preEnqueueFunc: opts.PreEnqueueFunc,
|
||||
postEnqueueFunc: opts.PostEnqueueFunc,
|
||||
errHandler: opts.EnqueueErrorHandler,
|
||||
idmap: make(map[string]cron.EntryID),
|
||||
id: generateSchedulerID(),
|
||||
state: &serverState{value: srvStateNew},
|
||||
heartbeatInterval: heartbeatInterval,
|
||||
logger: logger,
|
||||
cron: cron.New(cron.WithLocation(loc)),
|
||||
location: loc,
|
||||
done: make(chan struct{}),
|
||||
preEnqueueFunc: opts.PreEnqueueFunc,
|
||||
postEnqueueFunc: opts.PostEnqueueFunc,
|
||||
errHandler: opts.EnqueueErrorHandler,
|
||||
idmap: make(map[string]cron.EntryID),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +125,15 @@ func generateSchedulerID() string {
|
||||
|
||||
// SchedulerOpts specifies scheduler options.
|
||||
type SchedulerOpts struct {
|
||||
// HeartbeatInterval specifies the interval between scheduler heartbeats.
|
||||
//
|
||||
// If unset, zero or a negative value, the interval is set to 10 second.
|
||||
//
|
||||
// Note: Setting this value too low may add significant load to redis.
|
||||
//
|
||||
// By default, HeartbeatInterval is set to 10 seconds.
|
||||
HeartbeatInterval time.Duration
|
||||
|
||||
// Logger specifies the logger used by the scheduler instance.
|
||||
//
|
||||
// If unset, the default logger is used.
|
||||
@@ -276,15 +304,12 @@ func (s *Scheduler) Shutdown() {
|
||||
if err := s.client.Close(); err != nil {
|
||||
s.logger.Errorf("Failed to close redis client connection: %v", err)
|
||||
}
|
||||
if !s.sharedConnection {
|
||||
s.rdb.Close()
|
||||
}
|
||||
s.logger.Info("Scheduler stopped")
|
||||
}
|
||||
|
||||
func (s *Scheduler) runHeartbeater() {
|
||||
defer s.wg.Done()
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
ticker := time.NewTicker(s.heartbeatInterval)
|
||||
for {
|
||||
select {
|
||||
case <-s.done:
|
||||
@@ -316,8 +341,7 @@ func (s *Scheduler) beat() {
|
||||
}
|
||||
entries = append(entries, e)
|
||||
}
|
||||
s.logger.Debugf("Writing entries %v", entries)
|
||||
if err := s.rdb.WriteSchedulerEntries(s.id, entries, 5*time.Second); err != nil {
|
||||
if err := s.rdb.WriteSchedulerEntries(s.id, entries, s.heartbeatInterval*2); err != nil {
|
||||
s.logger.Warnf("Scheduler could not write heartbeat data: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,16 @@ package asynq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ErrHandlerNotFound indicates that no task handler was found for a given pattern.
|
||||
var ErrHandlerNotFound = errors.New("handler not found for task")
|
||||
|
||||
// ServeMux is a multiplexer for asynchronous tasks.
|
||||
// It matches the type of each task against a list of registered patterns
|
||||
// and calls the handler for the pattern that most closely matches the
|
||||
@@ -149,8 +153,8 @@ func (mux *ServeMux) Use(mws ...MiddlewareFunc) {
|
||||
|
||||
// NotFound returns an error indicating that the handler was not found for the given task.
|
||||
func NotFound(ctx context.Context, task *Task) error {
|
||||
return fmt.Errorf("handler not found for task %q", task.Type())
|
||||
return fmt.Errorf("%w %q", ErrHandlerNotFound, task.Type())
|
||||
}
|
||||
|
||||
// NotFoundHandler returns a simple task handler that returns a ``not found`` error.
|
||||
// NotFoundHandler returns a simple task handler that returns a “not found“ error.
|
||||
func NotFoundHandler() Handler { return HandlerFunc(NotFound) }
|
||||
|
||||
@@ -24,7 +24,7 @@ func makeFakeHandler(identity string) Handler {
|
||||
}
|
||||
|
||||
// makeFakeMiddleware returns a middleware function that appends the given identity
|
||||
//to the global invoked slice.
|
||||
// to the global invoked slice.
|
||||
func makeFakeMiddleware(identity string) MiddlewareFunc {
|
||||
return func(next Handler) Handler {
|
||||
return HandlerFunc(func(ctx context.Context, t *Task) error {
|
||||
|
||||
@@ -174,16 +174,15 @@ type Config struct {
|
||||
// })
|
||||
//
|
||||
// ErrorHandler: asynq.ErrorHandlerFunc(reportError)
|
||||
|
||||
//
|
||||
// we can also handle panic error like:
|
||||
// func reportError(ctx context, task *asynq.Task, err error) {
|
||||
// if asynq.IsPanic(err) {
|
||||
// if asynq.IsPanicError(err) {
|
||||
// errorReportingService.Notify(err)
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// ErrorHandler: asynq.ErrorHandlerFunc(reportError)
|
||||
|
||||
ErrorHandler ErrorHandler
|
||||
|
||||
// Logger specifies the logger used by the server instance.
|
||||
|
||||
@@ -24,8 +24,10 @@ func (srv *Server) waitForSignals() {
|
||||
if sig == unix.SIGTSTP {
|
||||
srv.Stop()
|
||||
continue
|
||||
} else {
|
||||
srv.Stop()
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/hibiken/asynq/internal/base"
|
||||
"github.com/hibiken/asynq/internal/log"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type subscriber struct {
|
||||
|
||||
@@ -39,6 +39,7 @@ By default, CLI will try to connect to a redis server running at `localhost:6379
|
||||
--config string config file to set flag defaut values (default is $HOME/.asynq.yaml)
|
||||
-n, --db int redis database number (default is 0)
|
||||
-h, --help help for asynq
|
||||
-U, --username string username to use when connecting to redis server
|
||||
-p, --password string password to use when connecting to redis server
|
||||
-u, --uri string redis server URI (default "127.0.0.1:6379")
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
@@ -36,14 +35,14 @@ var cronListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Short: "List cron entries",
|
||||
Run: cronList,
|
||||
RunE: cronList,
|
||||
}
|
||||
|
||||
var cronHistoryCmd = &cobra.Command{
|
||||
Use: "history <entry_id> [<entry_id>...]",
|
||||
Short: "Show history of each cron tasks",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: cronHistory,
|
||||
RunE: cronHistory,
|
||||
Example: heredoc.Doc(`
|
||||
$ asynq cron history 7837f142-6337-4217-9276-8f27281b67d1
|
||||
$ asynq cron history 7837f142-6337-4217-9276-8f27281b67d1 bf6a8594-cd03-4968-b36a-8572c5e160dd
|
||||
@@ -51,17 +50,16 @@ var cronHistoryCmd = &cobra.Command{
|
||||
$ asynq cron history 7837f142-6337-4217-9276-8f27281b67d1 --page=2`),
|
||||
}
|
||||
|
||||
func cronList(cmd *cobra.Command, args []string) {
|
||||
func cronList(cmd *cobra.Command, args []string) error {
|
||||
inspector := createInspector()
|
||||
|
||||
entries, err := inspector.SchedulerEntries()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("could not fetch scheduler entries: %v", err)
|
||||
}
|
||||
if len(entries) == 0 {
|
||||
fmt.Println("No scheduler entries")
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sort entries by spec.
|
||||
@@ -78,6 +76,7 @@ func cronList(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
printTable(cols, printRows)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns a string describing when the next enqueue will happen.
|
||||
@@ -97,16 +96,14 @@ func prevEnqueue(prevEnqueuedAt time.Time) string {
|
||||
return fmt.Sprintf("%v ago", time.Since(prevEnqueuedAt).Round(time.Second))
|
||||
}
|
||||
|
||||
func cronHistory(cmd *cobra.Command, args []string) {
|
||||
func cronHistory(cmd *cobra.Command, args []string) error {
|
||||
pageNum, err := cmd.Flags().GetInt("page")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
pageSize, err := cmd.Flags().GetInt("size")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
inspector := createInspector()
|
||||
for i, entryID := range args {
|
||||
@@ -136,4 +133,5 @@ func cronHistory(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
printTable(cols, printRows)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc/v2"
|
||||
@@ -32,14 +31,14 @@ var dashCmd = &cobra.Command{
|
||||
Example: heredoc.Doc(`
|
||||
$ asynq dash
|
||||
$ asynq dash --refresh=3s`),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if flagPollInterval < 1*time.Second {
|
||||
fmt.Println("error: --refresh cannot be less than 1s")
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("--refresh cannot be less than 1s")
|
||||
}
|
||||
dash.Run(dash.Options{
|
||||
PollInterval: flagPollInterval,
|
||||
RedisConnOpt: getRedisConnOpt(),
|
||||
})
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -16,11 +16,12 @@ import (
|
||||
// ScreenDrawer is used to draw contents on screen.
|
||||
//
|
||||
// Usage example:
|
||||
// d := NewScreenDrawer(s)
|
||||
// d.Println("Hello world", mystyle)
|
||||
// d.NL() // adds newline
|
||||
// d.Print("foo", mystyle.Bold(true))
|
||||
// d.Print("bar", mystyle.Italic(true))
|
||||
//
|
||||
// d := NewScreenDrawer(s)
|
||||
// d.Println("Hello world", mystyle)
|
||||
// d.NL() // adds newline
|
||||
// d.Print("foo", mystyle.Bold(true))
|
||||
// d.Print("bar", mystyle.Italic(true))
|
||||
type ScreenDrawer struct {
|
||||
l *LineDrawer
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/MakeNowJust/heredoc/v2"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -31,22 +30,25 @@ var groupListCmd = &cobra.Command{
|
||||
Aliases: []string{"ls"},
|
||||
Short: "List groups",
|
||||
Args: cobra.NoArgs,
|
||||
Run: groupLists,
|
||||
RunE: groupLists,
|
||||
}
|
||||
|
||||
func groupLists(cmd *cobra.Command, args []string) {
|
||||
func groupLists(cmd *cobra.Command, args []string) error {
|
||||
qname, err := cmd.Flags().GetString("queue")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
inspector := createInspector()
|
||||
groups, err := inspector.Groups(qname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not fetch groups: %v", err)
|
||||
}
|
||||
if len(groups) == 0 {
|
||||
fmt.Printf("No groups found in queue %q\n", qname)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
for _, g := range groups {
|
||||
fmt.Println(g.Group)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/MakeNowJust/heredoc/v2"
|
||||
"github.com/fatih/color"
|
||||
@@ -44,16 +43,14 @@ var queueListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List queues",
|
||||
Aliases: []string{"ls"},
|
||||
// TODO: Use RunE instead?
|
||||
Run: queueList,
|
||||
RunE: queueList,
|
||||
}
|
||||
|
||||
var queueInspectCmd = &cobra.Command{
|
||||
Use: "inspect <queue> [<queue>...]",
|
||||
Short: "Display detailed information on one or more queues",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
// TODO: Use RunE instead?
|
||||
Run: queueInspect,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: queueInspect,
|
||||
Example: heredoc.Doc(`
|
||||
$ asynq queue inspect myqueue
|
||||
$ asynq queue inspect queue1 queue2 queue3`),
|
||||
@@ -62,8 +59,8 @@ var queueInspectCmd = &cobra.Command{
|
||||
var queueHistoryCmd = &cobra.Command{
|
||||
Use: "history <queue> [<queue>...]",
|
||||
Short: "Display historical aggregate data from one or more queues",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: queueHistory,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: queueHistory,
|
||||
Example: heredoc.Doc(`
|
||||
$ asynq queue history myqueue
|
||||
$ asynq queue history queue1 queue2 queue3
|
||||
@@ -74,7 +71,7 @@ var queuePauseCmd = &cobra.Command{
|
||||
Use: "pause <queue> [<queue>...]",
|
||||
Short: "Pause one or more queues",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: queuePause,
|
||||
RunE: queuePause,
|
||||
Example: heredoc.Doc(`
|
||||
$ asynq queue pause myqueue
|
||||
$ asynq queue pause queue1 queue2 queue3`),
|
||||
@@ -85,7 +82,7 @@ var queueUnpauseCmd = &cobra.Command{
|
||||
Short: "Resume (unpause) one or more queues",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Aliases: []string{"unpause"},
|
||||
Run: queueUnpause,
|
||||
RunE: queueUnpause,
|
||||
Example: heredoc.Doc(`
|
||||
$ asynq queue resume myqueue
|
||||
$ asynq queue resume queue1 queue2 queue3`),
|
||||
@@ -96,14 +93,14 @@ var queueRemoveCmd = &cobra.Command{
|
||||
Short: "Remove one or more queues",
|
||||
Aliases: []string{"rm", "delete"},
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: queueRemove,
|
||||
RunE: queueRemove,
|
||||
Example: heredoc.Doc(`
|
||||
$ asynq queue rm myqueue
|
||||
$ asynq queue rm queue1 queue2 queue3
|
||||
$ asynq queue rm myqueue --force`),
|
||||
}
|
||||
|
||||
func queueList(cmd *cobra.Command, args []string) {
|
||||
func queueList(cmd *cobra.Command, args []string) error {
|
||||
type queueInfo struct {
|
||||
name string
|
||||
keyslot int64
|
||||
@@ -112,8 +109,7 @@ func queueList(cmd *cobra.Command, args []string) {
|
||||
inspector := createInspector()
|
||||
queues, err := inspector.Queues()
|
||||
if err != nil {
|
||||
fmt.Printf("error: Could not fetch list of queues: %v\n", err)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("could not fetch list of queues: %v", err)
|
||||
}
|
||||
var qs []*queueInfo
|
||||
for _, qname := range queues {
|
||||
@@ -121,13 +117,13 @@ func queueList(cmd *cobra.Command, args []string) {
|
||||
if useRedisCluster {
|
||||
keyslot, err := inspector.ClusterKeySlot(qname)
|
||||
if err != nil {
|
||||
fmt.Errorf("error: Could not get cluster keyslot for %q\n", qname)
|
||||
fmt.Printf("error: could not get cluster keyslot for %q\n", qname)
|
||||
continue
|
||||
}
|
||||
q.keyslot = keyslot
|
||||
nodes, err := inspector.ClusterNodes(qname)
|
||||
if err != nil {
|
||||
fmt.Errorf("error: Could not get cluster nodes for %q\n", qname)
|
||||
fmt.Printf("error: could not get cluster nodes for %q\n", qname)
|
||||
continue
|
||||
}
|
||||
q.nodes = nodes
|
||||
@@ -148,9 +144,10 @@ func queueList(cmd *cobra.Command, args []string) {
|
||||
fmt.Println(q.name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func queueInspect(cmd *cobra.Command, args []string) {
|
||||
func queueInspect(cmd *cobra.Command, args []string) error {
|
||||
inspector := createInspector()
|
||||
for i, qname := range args {
|
||||
if i > 0 {
|
||||
@@ -163,6 +160,7 @@ func queueInspect(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
printQueueInfo(info)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printQueueInfo(info *asynq.QueueInfo) {
|
||||
@@ -195,11 +193,10 @@ func printQueueInfo(info *asynq.QueueInfo) {
|
||||
)
|
||||
}
|
||||
|
||||
func queueHistory(cmd *cobra.Command, args []string) {
|
||||
func queueHistory(cmd *cobra.Command, args []string) error {
|
||||
days, err := cmd.Flags().GetInt("days")
|
||||
if err != nil {
|
||||
fmt.Printf("error: Internal error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
inspector := createInspector()
|
||||
for i, qname := range args {
|
||||
@@ -214,6 +211,7 @@ func queueHistory(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
printDailyStats(stats)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printDailyStats(stats []*asynq.DailyStats) {
|
||||
@@ -233,49 +231,63 @@ func printDailyStats(stats []*asynq.DailyStats) {
|
||||
)
|
||||
}
|
||||
|
||||
func queuePause(cmd *cobra.Command, args []string) {
|
||||
func queuePause(cmd *cobra.Command, args []string) error {
|
||||
inspector := createInspector()
|
||||
var firstErr error
|
||||
for _, qname := range args {
|
||||
err := inspector.PauseQueue(qname)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
continue
|
||||
}
|
||||
fmt.Printf("Successfully paused queue %q\n", qname)
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
|
||||
func queueUnpause(cmd *cobra.Command, args []string) {
|
||||
func queueUnpause(cmd *cobra.Command, args []string) error {
|
||||
inspector := createInspector()
|
||||
var firstErr error
|
||||
for _, qname := range args {
|
||||
err := inspector.UnpauseQueue(qname)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
continue
|
||||
}
|
||||
fmt.Printf("Successfully unpaused queue %q\n", qname)
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
|
||||
func queueRemove(cmd *cobra.Command, args []string) {
|
||||
func queueRemove(cmd *cobra.Command, args []string) error {
|
||||
// TODO: Use inspector once RemoveQueue become public API.
|
||||
force, err := cmd.Flags().GetBool("force")
|
||||
if err != nil {
|
||||
fmt.Printf("error: Internal error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
|
||||
r := createRDB()
|
||||
var firstErr error
|
||||
for _, qname := range args {
|
||||
err = r.RemoveQueue(qname, force)
|
||||
if err != nil {
|
||||
if errors.IsQueueNotEmpty(err) {
|
||||
fmt.Printf("error: %v\nIf you are sure you want to delete it, run 'asynq queue rm --force %s'\n", err, qname)
|
||||
continue
|
||||
} else {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
}
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
fmt.Printf("error: %v\n", err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("Successfully removed queue %q\n", qname)
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//
|
||||
// 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.
|
||||
@@ -36,10 +37,13 @@ var (
|
||||
uri string
|
||||
db int
|
||||
password string
|
||||
username string
|
||||
|
||||
useRedisCluster bool
|
||||
clusterAddrs string
|
||||
tlsServerName string
|
||||
insecure bool
|
||||
useTLS bool
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
@@ -257,7 +261,6 @@ func adjustPadding(lines ...*displayLine) {
|
||||
func rpad(s string, padding int) string {
|
||||
tmpl := fmt.Sprintf("%%-%ds ", padding)
|
||||
return fmt.Sprintf(tmpl, s)
|
||||
|
||||
}
|
||||
|
||||
// lpad adds padding to the left of a string.
|
||||
@@ -308,19 +311,26 @@ func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&uri, "uri", "u", "127.0.0.1:6379", "Redis server URI")
|
||||
rootCmd.PersistentFlags().IntVarP(&db, "db", "n", 0, "Redis database number (default is 0)")
|
||||
rootCmd.PersistentFlags().StringVarP(&password, "password", "p", "", "Password to use when connecting to redis server")
|
||||
rootCmd.PersistentFlags().StringVarP(&username, "username", "U", "", "Username to use when connecting to Redis (ACL username)")
|
||||
rootCmd.PersistentFlags().BoolVar(&useRedisCluster, "cluster", false, "Connect to redis cluster")
|
||||
rootCmd.PersistentFlags().StringVar(&clusterAddrs, "cluster_addrs",
|
||||
"127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003,127.0.0.1:7004,127.0.0.1:7005",
|
||||
"List of comma-separated redis server addresses")
|
||||
rootCmd.PersistentFlags().BoolVar(&useTLS, "tls", false, "Enable TLS connection")
|
||||
rootCmd.PersistentFlags().StringVar(&tlsServerName, "tls_server",
|
||||
"", "Server name for TLS validation")
|
||||
rootCmd.PersistentFlags().BoolVar(&insecure, "insecure",
|
||||
false, "Allow insecure TLS connection by skipping cert validation")
|
||||
// Bind flags with config.
|
||||
viper.BindPFlag("uri", rootCmd.PersistentFlags().Lookup("uri"))
|
||||
viper.BindPFlag("db", rootCmd.PersistentFlags().Lookup("db"))
|
||||
viper.BindPFlag("password", rootCmd.PersistentFlags().Lookup("password"))
|
||||
viper.BindPFlag("username", rootCmd.PersistentFlags().Lookup("username"))
|
||||
viper.BindPFlag("cluster", rootCmd.PersistentFlags().Lookup("cluster"))
|
||||
viper.BindPFlag("cluster_addrs", rootCmd.PersistentFlags().Lookup("cluster_addrs"))
|
||||
viper.BindPFlag("tls", rootCmd.PersistentFlags().Lookup("tls"))
|
||||
viper.BindPFlag("tls_server", rootCmd.PersistentFlags().Lookup("tls_server"))
|
||||
viper.BindPFlag("insecure", rootCmd.PersistentFlags().Lookup("insecure"))
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
@@ -357,6 +367,7 @@ func createRDB() *rdb.RDB {
|
||||
c = redis.NewClusterClient(&redis.ClusterOptions{
|
||||
Addrs: addrs,
|
||||
Password: viper.GetString("password"),
|
||||
Username: viper.GetString("username"),
|
||||
TLSConfig: getTLSConfig(),
|
||||
})
|
||||
} else {
|
||||
@@ -364,6 +375,7 @@ func createRDB() *rdb.RDB {
|
||||
Addr: viper.GetString("uri"),
|
||||
DB: viper.GetInt("db"),
|
||||
Password: viper.GetString("password"),
|
||||
Username: viper.GetString("username"),
|
||||
TLSConfig: getTLSConfig(),
|
||||
})
|
||||
}
|
||||
@@ -386,6 +398,7 @@ func getRedisConnOpt() asynq.RedisConnOpt {
|
||||
return asynq.RedisClusterClientOpt{
|
||||
Addrs: addrs,
|
||||
Password: viper.GetString("password"),
|
||||
Username: viper.GetString("username"),
|
||||
TLSConfig: getTLSConfig(),
|
||||
}
|
||||
}
|
||||
@@ -393,16 +406,22 @@ func getRedisConnOpt() asynq.RedisConnOpt {
|
||||
Addr: viper.GetString("uri"),
|
||||
DB: viper.GetInt("db"),
|
||||
Password: viper.GetString("password"),
|
||||
Username: viper.GetString("username"),
|
||||
TLSConfig: getTLSConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
func getTLSConfig() *tls.Config {
|
||||
tlsServer := viper.GetString("tls_server")
|
||||
if tlsServer == "" {
|
||||
return nil
|
||||
if tlsServer != "" {
|
||||
return &tls.Config{ServerName: tlsServer, InsecureSkipVerify: viper.GetBool("insecure")}
|
||||
}
|
||||
return &tls.Config{ServerName: tlsServer}
|
||||
|
||||
if viper.GetBool("tls") {
|
||||
return &tls.Config{InsecureSkipVerify: viper.GetBool("insecure")}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// printTable is a helper function to print data in table format.
|
||||
@@ -410,18 +429,22 @@ func getTLSConfig() *tls.Config {
|
||||
// cols is a list of headers and printRow specifies how to print rows.
|
||||
//
|
||||
// Example:
|
||||
// type User struct {
|
||||
// Name string
|
||||
// Addr string
|
||||
// Age int
|
||||
// }
|
||||
//
|
||||
// type User struct {
|
||||
// Name string
|
||||
// Addr string
|
||||
// Age int
|
||||
// }
|
||||
//
|
||||
// data := []*User{{"user1", "addr1", 24}, {"user2", "addr2", 42}, ...}
|
||||
// cols := []string{"Name", "Addr", "Age"}
|
||||
// printRows := func(w io.Writer, tmpl string) {
|
||||
// for _, u := range data {
|
||||
// fmt.Fprintf(w, tmpl, u.Name, u.Addr, u.Age)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// printRows := func(w io.Writer, tmpl string) {
|
||||
// for _, u := range data {
|
||||
// fmt.Fprintf(w, tmpl, u.Name, u.Addr, u.Age)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// printTable(cols, printRows)
|
||||
func printTable(cols []string, printRows func(w io.Writer, tmpl string)) {
|
||||
format := strings.Repeat("%v\t", len(cols)) + "\n"
|
||||
@@ -464,35 +487,31 @@ func isPrintable(data []byte) bool {
|
||||
}
|
||||
|
||||
// Helper to turn a command line flag into a duration
|
||||
func getDuration(cmd *cobra.Command, arg string) time.Duration {
|
||||
func getDuration(cmd *cobra.Command, arg string) (time.Duration, error) {
|
||||
durationStr, err := cmd.Flags().GetString(arg)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
duration, err := time.ParseDuration(durationStr)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return duration
|
||||
return duration, nil
|
||||
}
|
||||
|
||||
// Helper to turn a command line flag into a time
|
||||
func getTime(cmd *cobra.Command, arg string) time.Time {
|
||||
func getTime(cmd *cobra.Command, arg string) (time.Time, error) {
|
||||
timeStr, err := cmd.Flags().GetString(arg)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
timeVal, err := time.Parse(time.RFC3339, timeStr)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
return timeVal
|
||||
return timeVal, nil
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -44,20 +43,19 @@ The command shows the following for each server:
|
||||
|
||||
A "active" server is pulling tasks from queues and processing them.
|
||||
A "stopped" server is no longer pulling new tasks from queues`,
|
||||
Run: serverList,
|
||||
RunE: serverList,
|
||||
}
|
||||
|
||||
func serverList(cmd *cobra.Command, args []string) {
|
||||
func serverList(cmd *cobra.Command, args []string) error {
|
||||
r := createRDB()
|
||||
|
||||
servers, err := r.ListServers()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("could not fetch list of servers: %v", err)
|
||||
}
|
||||
if len(servers) == 0 {
|
||||
fmt.Println("No running servers")
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// sort by hostname and pid
|
||||
@@ -80,6 +78,7 @@ func serverList(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
printTable(cols, printRows)
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatQueues(qmap map[string]int) string {
|
||||
|
||||
@@ -35,7 +35,7 @@ var statsCmd = &cobra.Command{
|
||||
* Aggregate data for the current day
|
||||
* Basic information about the running redis instance`),
|
||||
Args: cobra.NoArgs,
|
||||
Run: stats,
|
||||
RunE: stats,
|
||||
}
|
||||
|
||||
var jsonFlag bool
|
||||
@@ -74,13 +74,12 @@ type FullStats struct {
|
||||
RedisInfo map[string]string `json:"redis"`
|
||||
}
|
||||
|
||||
func stats(cmd *cobra.Command, args []string) {
|
||||
func stats(cmd *cobra.Command, args []string) error {
|
||||
r := createRDB()
|
||||
|
||||
queues, err := r.AllQueues()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("could not fetch queues: %v", err)
|
||||
}
|
||||
|
||||
var aggStats AggregateStats
|
||||
@@ -88,8 +87,7 @@ func stats(cmd *cobra.Command, args []string) {
|
||||
for _, qname := range queues {
|
||||
s, err := r.CurrentStats(qname)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("could not fetch stats for queue %q: %v", qname, err)
|
||||
}
|
||||
aggStats.Active += s.Active
|
||||
aggStats.Pending += s.Pending
|
||||
@@ -110,8 +108,7 @@ func stats(cmd *cobra.Command, args []string) {
|
||||
info, err = r.RedisInfo()
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("could not fetch redis info: %v", err)
|
||||
}
|
||||
|
||||
if jsonFlag {
|
||||
@@ -122,12 +119,11 @@ func stats(cmd *cobra.Command, args []string) {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("could not marshal stats to JSON: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println(string(statsJSON))
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
bold := color.New(color.Bold)
|
||||
@@ -151,6 +147,7 @@ func stats(cmd *cobra.Command, args []string) {
|
||||
printInfo(info)
|
||||
}
|
||||
fmt.Println()
|
||||
return nil
|
||||
}
|
||||
|
||||
func printStatsByState(s *AggregateStats) {
|
||||
|
||||
@@ -7,7 +7,6 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc/v2"
|
||||
@@ -120,14 +119,14 @@ var taskListCmd = &cobra.Command{
|
||||
$ asynq task list --queue=myqueue --state=pending
|
||||
$ asynq task list --queue=myqueue --state=aggregating --group=mygroup
|
||||
$ asynq task list --queue=myqueue --state=scheduled --page=2`),
|
||||
Run: taskList,
|
||||
RunE: taskList,
|
||||
}
|
||||
|
||||
var taskInspectCmd = &cobra.Command{
|
||||
Use: "inspect --queue=<queue> --id=<task_id>",
|
||||
Short: "Display detailed information on the specified task",
|
||||
Args: cobra.NoArgs,
|
||||
Run: taskInspect,
|
||||
RunE: taskInspect,
|
||||
Example: heredoc.Doc(`
|
||||
$ asynq task inspect --queue=myqueue --id=f1720682-f5a6-4db1-8953-4f48ae541d0f`),
|
||||
}
|
||||
@@ -136,7 +135,7 @@ var taskCancelCmd = &cobra.Command{
|
||||
Use: "cancel <task_id> [<task_id>...]",
|
||||
Short: "Cancel one or more active tasks",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: taskCancel,
|
||||
RunE: taskCancel,
|
||||
Example: heredoc.Doc(`
|
||||
$ asynq task cancel f1720682-f5a6-4db1-8953-4f48ae541d0f`),
|
||||
}
|
||||
@@ -145,7 +144,7 @@ var taskArchiveCmd = &cobra.Command{
|
||||
Use: "archive --queue=<queue> --id=<task_id>",
|
||||
Short: "Archive a task with the given id",
|
||||
Args: cobra.NoArgs,
|
||||
Run: taskArchive,
|
||||
RunE: taskArchive,
|
||||
Example: heredoc.Doc(`
|
||||
$ asynq task archive --queue=myqueue --id=f1720682-f5a6-4db1-8953-4f48ae541d0f`),
|
||||
}
|
||||
@@ -155,7 +154,7 @@ var taskDeleteCmd = &cobra.Command{
|
||||
Aliases: []string{"remove", "rm"},
|
||||
Short: "Delete a task with the given id",
|
||||
Args: cobra.NoArgs,
|
||||
Run: taskDelete,
|
||||
RunE: taskDelete,
|
||||
Example: heredoc.Doc(`
|
||||
$ asynq task delete --queue=myqueue --id=f1720682-f5a6-4db1-8953-4f48ae541d0f`),
|
||||
}
|
||||
@@ -164,7 +163,7 @@ var taskRunCmd = &cobra.Command{
|
||||
Use: "run --queue=<queue> --id=<task_id>",
|
||||
Short: "Run a task with the given id",
|
||||
Args: cobra.NoArgs,
|
||||
Run: taskRun,
|
||||
RunE: taskRun,
|
||||
Example: heredoc.Doc(`
|
||||
$ asynq task run --queue=myqueue --id=f1720682-f5a6-4db1-8953-4f48ae541d0f`),
|
||||
}
|
||||
@@ -173,7 +172,7 @@ var taskEnqueueCmd = &cobra.Command{
|
||||
Use: "enqueue --type_name=footype --payload=barpayload",
|
||||
Short: "Enqueue a task",
|
||||
Args: cobra.NoArgs,
|
||||
Run: taskEnqueue,
|
||||
RunE: taskEnqueue,
|
||||
Example: heredoc.Doc(`
|
||||
$ asynq task enqueue -t footype -l barpayload
|
||||
$ asynq task enqueue -t footask -l barpayload --retry 3 --id f1720682-f5a6-4db1-8953-4f48ae541d0f --queue bazqueue --timeout 100s --deadline 2024-12-14T01:23:45Z --unique 100s --process_at 2024-12-14T01:22:05Z --process_in 100s --retention 5h --group baygroup`),
|
||||
@@ -183,7 +182,7 @@ var taskArchiveAllCmd = &cobra.Command{
|
||||
Use: "archiveall --queue=<queue> --state=<state>",
|
||||
Short: "Archive all tasks in the given state",
|
||||
Args: cobra.NoArgs,
|
||||
Run: taskArchiveAll,
|
||||
RunE: taskArchiveAll,
|
||||
Example: heredoc.Doc(`
|
||||
$ asynq task archiveall --queue=myqueue --state=retry
|
||||
$ asynq task archiveall --queue=myqueue --state=aggregating --group=mygroup`),
|
||||
@@ -193,7 +192,7 @@ var taskDeleteAllCmd = &cobra.Command{
|
||||
Use: "deleteall --queue=<queue> --state=<state>",
|
||||
Short: "Delete all tasks in the given state",
|
||||
Args: cobra.NoArgs,
|
||||
Run: taskDeleteAll,
|
||||
RunE: taskDeleteAll,
|
||||
Example: heredoc.Doc(`
|
||||
$ asynq task deleteall --queue=myqueue --state=archived
|
||||
$ asynq task deleteall --queue=myqueue --state=aggregating --group=mygroup`),
|
||||
@@ -203,74 +202,66 @@ var taskRunAllCmd = &cobra.Command{
|
||||
Use: "runall --queue=<queue> --state=<state>",
|
||||
Short: "Run all tasks in the given state",
|
||||
Args: cobra.NoArgs,
|
||||
Run: taskRunAll,
|
||||
RunE: taskRunAll,
|
||||
Example: heredoc.Doc(`
|
||||
$ asynq task runall --queue=myqueue --state=retry
|
||||
$ asynq task runall --queue=myqueue --state=aggregating --group=mygroup`),
|
||||
}
|
||||
|
||||
func taskList(cmd *cobra.Command, args []string) {
|
||||
func taskList(cmd *cobra.Command, args []string) error {
|
||||
qname, err := cmd.Flags().GetString("queue")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
state, err := cmd.Flags().GetString("state")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
pageNum, err := cmd.Flags().GetInt("page")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
pageSize, err := cmd.Flags().GetInt("size")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
|
||||
switch state {
|
||||
case "active":
|
||||
listActiveTasks(qname, pageNum, pageSize)
|
||||
return listActiveTasks(qname, pageNum, pageSize)
|
||||
case "pending":
|
||||
listPendingTasks(qname, pageNum, pageSize)
|
||||
return listPendingTasks(qname, pageNum, pageSize)
|
||||
case "scheduled":
|
||||
listScheduledTasks(qname, pageNum, pageSize)
|
||||
return listScheduledTasks(qname, pageNum, pageSize)
|
||||
case "retry":
|
||||
listRetryTasks(qname, pageNum, pageSize)
|
||||
return listRetryTasks(qname, pageNum, pageSize)
|
||||
case "archived":
|
||||
listArchivedTasks(qname, pageNum, pageSize)
|
||||
return listArchivedTasks(qname, pageNum, pageSize)
|
||||
case "completed":
|
||||
listCompletedTasks(qname, pageNum, pageSize)
|
||||
return listCompletedTasks(qname, pageNum, pageSize)
|
||||
case "aggregating":
|
||||
group, err := cmd.Flags().GetString("group")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
if group == "" {
|
||||
fmt.Println("Flag --group is required for listing aggregating tasks")
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("flag --group is required for listing aggregating tasks")
|
||||
}
|
||||
listAggregatingTasks(qname, group, pageNum, pageSize)
|
||||
return listAggregatingTasks(qname, group, pageNum, pageSize)
|
||||
default:
|
||||
fmt.Printf("error: state=%q is not supported\n", state)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("state=%q is not supported", state)
|
||||
}
|
||||
}
|
||||
|
||||
func listActiveTasks(qname string, pageNum, pageSize int) {
|
||||
func listActiveTasks(qname string, pageNum, pageSize int) error {
|
||||
i := createInspector()
|
||||
tasks, err := i.ListActiveTasks(qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
if len(tasks) == 0 {
|
||||
fmt.Printf("No active tasks in %q queue\n", qname)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
printTable(
|
||||
[]string{"ID", "Type", "Payload"},
|
||||
@@ -280,18 +271,18 @@ func listActiveTasks(qname string, pageNum, pageSize int) {
|
||||
}
|
||||
},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func listPendingTasks(qname string, pageNum, pageSize int) {
|
||||
func listPendingTasks(qname string, pageNum, pageSize int) error {
|
||||
i := createInspector()
|
||||
tasks, err := i.ListPendingTasks(qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
if len(tasks) == 0 {
|
||||
fmt.Printf("No pending tasks in %q queue\n", qname)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
printTable(
|
||||
[]string{"ID", "Type", "Payload"},
|
||||
@@ -301,18 +292,18 @@ func listPendingTasks(qname string, pageNum, pageSize int) {
|
||||
}
|
||||
},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func listScheduledTasks(qname string, pageNum, pageSize int) {
|
||||
func listScheduledTasks(qname string, pageNum, pageSize int) error {
|
||||
i := createInspector()
|
||||
tasks, err := i.ListScheduledTasks(qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
if len(tasks) == 0 {
|
||||
fmt.Printf("No scheduled tasks in %q queue\n", qname)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
printTable(
|
||||
[]string{"ID", "Type", "Payload", "Process In"},
|
||||
@@ -322,6 +313,7 @@ func listScheduledTasks(qname string, pageNum, pageSize int) {
|
||||
}
|
||||
},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// formatProcessAt formats next process at time to human friendly string.
|
||||
@@ -335,16 +327,15 @@ func formatProcessAt(processAt time.Time) string {
|
||||
return fmt.Sprintf("in %v", d.Round(time.Second))
|
||||
}
|
||||
|
||||
func listRetryTasks(qname string, pageNum, pageSize int) {
|
||||
func listRetryTasks(qname string, pageNum, pageSize int) error {
|
||||
i := createInspector()
|
||||
tasks, err := i.ListRetryTasks(qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
if len(tasks) == 0 {
|
||||
fmt.Printf("No retry tasks in %q queue\n", qname)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
printTable(
|
||||
[]string{"ID", "Type", "Payload", "Next Retry", "Last Error", "Last Failed", "Retried", "Max Retry"},
|
||||
@@ -355,18 +346,18 @@ func listRetryTasks(qname string, pageNum, pageSize int) {
|
||||
}
|
||||
},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func listArchivedTasks(qname string, pageNum, pageSize int) {
|
||||
func listArchivedTasks(qname string, pageNum, pageSize int) error {
|
||||
i := createInspector()
|
||||
tasks, err := i.ListArchivedTasks(qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
if len(tasks) == 0 {
|
||||
fmt.Printf("No archived tasks in %q queue\n", qname)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
printTable(
|
||||
[]string{"ID", "Type", "Payload", "Last Failed", "Last Error"},
|
||||
@@ -375,18 +366,18 @@ func listArchivedTasks(qname string, pageNum, pageSize int) {
|
||||
fmt.Fprintf(w, tmpl, t.ID, t.Type, sprintBytes(t.Payload), formatPastTime(t.LastFailedAt), t.LastErr)
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func listCompletedTasks(qname string, pageNum, pageSize int) {
|
||||
func listCompletedTasks(qname string, pageNum, pageSize int) error {
|
||||
i := createInspector()
|
||||
tasks, err := i.ListCompletedTasks(qname, asynq.PageSize(pageSize), asynq.Page(pageNum))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
if len(tasks) == 0 {
|
||||
fmt.Printf("No completed tasks in %q queue\n", qname)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
printTable(
|
||||
[]string{"ID", "Type", "Payload", "CompletedAt", "Result"},
|
||||
@@ -395,18 +386,18 @@ func listCompletedTasks(qname string, pageNum, pageSize int) {
|
||||
fmt.Fprintf(w, tmpl, t.ID, t.Type, sprintBytes(t.Payload), formatPastTime(t.CompletedAt), sprintBytes(t.Result))
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func listAggregatingTasks(qname, group string, pageNum, pageSize int) {
|
||||
func listAggregatingTasks(qname, group string, pageNum, pageSize int) error {
|
||||
i := createInspector()
|
||||
tasks, err := i.ListAggregatingTasks(qname, group, asynq.PageSize(pageSize), asynq.Page(pageNum))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
if len(tasks) == 0 {
|
||||
fmt.Printf("No aggregating tasks in group %q \n", group)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
printTable(
|
||||
[]string{"ID", "Type", "Payload", "Group"},
|
||||
@@ -416,38 +407,42 @@ func listAggregatingTasks(qname, group string, pageNum, pageSize int) {
|
||||
}
|
||||
},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func taskCancel(cmd *cobra.Command, args []string) {
|
||||
func taskCancel(cmd *cobra.Command, args []string) error {
|
||||
i := createInspector()
|
||||
var firstErr error
|
||||
for _, id := range args {
|
||||
if err := i.CancelProcessing(id); err != nil {
|
||||
fmt.Printf("error: could not send cancelation signal: %v\n", err)
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
continue
|
||||
}
|
||||
fmt.Printf("Sent cancelation signal for task %s\n", id)
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
|
||||
func taskInspect(cmd *cobra.Command, args []string) {
|
||||
func taskInspect(cmd *cobra.Command, args []string) error {
|
||||
qname, err := cmd.Flags().GetString("queue")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
id, err := cmd.Flags().GetString("id")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
|
||||
i := createInspector()
|
||||
info, err := i.GetTaskInfo(qname, id)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("could not get task info: %v", err)
|
||||
}
|
||||
printTaskInfo(info)
|
||||
return nil
|
||||
}
|
||||
|
||||
func printTaskInfo(info *asynq.TaskInfo) {
|
||||
@@ -486,80 +481,72 @@ func formatPastTime(t time.Time) string {
|
||||
return t.Format(time.UnixDate)
|
||||
}
|
||||
|
||||
func taskArchive(cmd *cobra.Command, args []string) {
|
||||
func taskArchive(cmd *cobra.Command, args []string) error {
|
||||
qname, err := cmd.Flags().GetString("queue")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
id, err := cmd.Flags().GetString("id")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
|
||||
i := createInspector()
|
||||
err = i.ArchiveTask(qname, id)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("could not archive task: %v", err)
|
||||
}
|
||||
fmt.Println("task archived")
|
||||
return nil
|
||||
}
|
||||
|
||||
func taskDelete(cmd *cobra.Command, args []string) {
|
||||
func taskDelete(cmd *cobra.Command, args []string) error {
|
||||
qname, err := cmd.Flags().GetString("queue")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
id, err := cmd.Flags().GetString("id")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
|
||||
i := createInspector()
|
||||
err = i.DeleteTask(qname, id)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("could not delete task: %v", err)
|
||||
}
|
||||
fmt.Println("task deleted")
|
||||
return nil
|
||||
}
|
||||
|
||||
func taskRun(cmd *cobra.Command, args []string) {
|
||||
func taskRun(cmd *cobra.Command, args []string) error {
|
||||
qname, err := cmd.Flags().GetString("queue")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
id, err := cmd.Flags().GetString("id")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
|
||||
i := createInspector()
|
||||
err = i.RunTask(qname, id)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("could not run task: %v", err)
|
||||
}
|
||||
fmt.Println("task is now pending")
|
||||
return nil
|
||||
}
|
||||
|
||||
func taskEnqueue(cmd *cobra.Command, args []string) {
|
||||
func taskEnqueue(cmd *cobra.Command, args []string) error {
|
||||
typeName, err := cmd.Flags().GetString("type_name")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
|
||||
payload, err := cmd.Flags().GetString("payload")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
|
||||
// For all of the optional flags, we need to explicitly check whether they were set or
|
||||
@@ -569,8 +556,7 @@ func taskEnqueue(cmd *cobra.Command, args []string) {
|
||||
if cmd.Flags().Changed("retry") {
|
||||
retry, err := cmd.Flags().GetInt("retry")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
opts = append(opts, asynq.MaxRetry(retry))
|
||||
}
|
||||
@@ -578,8 +564,7 @@ func taskEnqueue(cmd *cobra.Command, args []string) {
|
||||
if cmd.Flags().Changed("queue") {
|
||||
queue, err := cmd.Flags().GetString("queue")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
opts = append(opts, asynq.Queue(queue))
|
||||
}
|
||||
@@ -587,41 +572,63 @@ func taskEnqueue(cmd *cobra.Command, args []string) {
|
||||
if cmd.Flags().Changed("id") {
|
||||
id, err := cmd.Flags().GetString("id")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
opts = append(opts, asynq.TaskID(id))
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("timeout") {
|
||||
opts = append(opts, asynq.Timeout(getDuration(cmd, "timeout")))
|
||||
d, err := getDuration(cmd, "timeout")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts = append(opts, asynq.Timeout(d))
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("deadline") {
|
||||
opts = append(opts, asynq.Deadline(getTime(cmd, "deadline")))
|
||||
t, err := getTime(cmd, "deadline")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts = append(opts, asynq.Deadline(t))
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("unique") {
|
||||
opts = append(opts, asynq.Unique(getDuration(cmd, "unique")))
|
||||
d, err := getDuration(cmd, "unique")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts = append(opts, asynq.Unique(d))
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("process_at") {
|
||||
opts = append(opts, asynq.ProcessAt(getTime(cmd, "process_at")))
|
||||
t, err := getTime(cmd, "process_at")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts = append(opts, asynq.ProcessAt(t))
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("process_in") {
|
||||
opts = append(opts, asynq.ProcessIn(getDuration(cmd, "process_in")))
|
||||
d, err := getDuration(cmd, "process_in")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts = append(opts, asynq.ProcessIn(d))
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("retention") {
|
||||
opts = append(opts, asynq.Retention(getDuration(cmd, "retention")))
|
||||
d, err := getDuration(cmd, "retention")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts = append(opts, asynq.Retention(d))
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("group") {
|
||||
group, err := cmd.Flags().GetString("group")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
opts = append(opts, asynq.Group(group))
|
||||
}
|
||||
@@ -631,23 +638,21 @@ func taskEnqueue(cmd *cobra.Command, args []string) {
|
||||
|
||||
taskInfo, err := c.Enqueue(task)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("could not enqueue task: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Enqueued task %s to queue %s\n", taskInfo.ID, taskInfo.Queue)
|
||||
return nil
|
||||
}
|
||||
|
||||
func taskArchiveAll(cmd *cobra.Command, args []string) {
|
||||
func taskArchiveAll(cmd *cobra.Command, args []string) error {
|
||||
qname, err := cmd.Flags().GetString("queue")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
state, err := cmd.Flags().GetString("state")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
|
||||
i := createInspector()
|
||||
@@ -662,35 +667,35 @@ func taskArchiveAll(cmd *cobra.Command, args []string) {
|
||||
case "aggregating":
|
||||
group, err := cmd.Flags().GetString("group")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
if group == "" {
|
||||
fmt.Println("error: Flag --group is required for aggregating tasks")
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("flag --group is required for aggregating tasks")
|
||||
}
|
||||
n, err = i.ArchiveAllAggregatingTasks(qname, group)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%d tasks archived\n", n)
|
||||
return nil
|
||||
default:
|
||||
fmt.Printf("error: unsupported state %q\n", state)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("unsupported state %q", state)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%d tasks archived\n", n)
|
||||
return nil
|
||||
}
|
||||
|
||||
func taskDeleteAll(cmd *cobra.Command, args []string) {
|
||||
func taskDeleteAll(cmd *cobra.Command, args []string) error {
|
||||
qname, err := cmd.Flags().GetString("queue")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
state, err := cmd.Flags().GetString("state")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
|
||||
i := createInspector()
|
||||
@@ -709,35 +714,35 @@ func taskDeleteAll(cmd *cobra.Command, args []string) {
|
||||
case "aggregating":
|
||||
group, err := cmd.Flags().GetString("group")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
if group == "" {
|
||||
fmt.Println("error: Flag --group is required for aggregating tasks")
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("flag --group is required for aggregating tasks")
|
||||
}
|
||||
n, err = i.DeleteAllAggregatingTasks(qname, group)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%d tasks deleted\n", n)
|
||||
return nil
|
||||
default:
|
||||
fmt.Printf("error: unsupported state %q\n", state)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("unsupported state %q", state)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%d tasks deleted\n", n)
|
||||
return nil
|
||||
}
|
||||
|
||||
func taskRunAll(cmd *cobra.Command, args []string) {
|
||||
func taskRunAll(cmd *cobra.Command, args []string) error {
|
||||
qname, err := cmd.Flags().GetString("queue")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
state, err := cmd.Flags().GetString("state")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
|
||||
i := createInspector()
|
||||
@@ -752,22 +757,23 @@ func taskRunAll(cmd *cobra.Command, args []string) {
|
||||
case "aggregating":
|
||||
group, err := cmd.Flags().GetString("group")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
if group == "" {
|
||||
fmt.Println("error: Flag --group is required for aggregating tasks")
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("flag --group is required for aggregating tasks")
|
||||
}
|
||||
n, err = i.RunAllAggregatingTasks(qname, group)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%d tasks are now pending\n", n)
|
||||
return nil
|
||||
default:
|
||||
fmt.Printf("error: unsupported state %q\n", state)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("unsupported state %q", state)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%d tasks are now pending\n", n)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
18
tools/go.mod
18
tools/go.mod
@@ -6,13 +6,13 @@ require (
|
||||
github.com/MakeNowJust/heredoc/v2 v2.0.1
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/gdamore/tcell/v2 v2.5.1
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/hibiken/asynq v0.24.1
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/hibiken/asynq v0.25.0
|
||||
github.com/hibiken/asynq/x v0.0.0-20220131170841-349f4c50fb1d
|
||||
github.com/mattn/go-runewidth v0.0.13
|
||||
github.com/mattn/go-runewidth v0.0.16
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/prometheus/client_golang v1.11.1
|
||||
github.com/redis/go-redis/v9 v9.0.5
|
||||
github.com/redis/go-redis/v9 v9.7.0
|
||||
github.com/spf13/cobra v1.1.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.7.0
|
||||
@@ -26,7 +26,7 @@ require (
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
@@ -42,14 +42,14 @@ require (
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/spf13/afero v1.1.2 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.0.0 // indirect
|
||||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
|
||||
golang.org/x/text v0.3.8 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
google.golang.org/protobuf v1.35.1 // indirect
|
||||
gopkg.in/ini.v1 v1.51.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
62
tools/go.sum
62
tools/go.sum
@@ -31,10 +31,10 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
|
||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
@@ -60,8 +60,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
@@ -113,16 +113,17 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
@@ -153,8 +154,8 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hibiken/asynq v0.19.0/go.mod h1:tyc63ojaW8SJ5SBm8mvI4DDONsguP5HE85EEl4Qr5Ig=
|
||||
github.com/hibiken/asynq v0.24.1 h1:+5iIEAyA9K/lcSPvx3qoPtsKJeKI5u9aOIvUmSsazEw=
|
||||
github.com/hibiken/asynq v0.24.1/go.mod h1:u5qVeSbrnfT+vtG5Mq8ZPzQu/BmCKMHvTGb91uy9Tts=
|
||||
github.com/hibiken/asynq v0.25.0 h1:VCPyRRrrjFChsTSI8x5OCPu51MlEz6Rk+1p0kHKnZug=
|
||||
github.com/hibiken/asynq v0.25.0/go.mod h1:DYQ1etBEl2Y+uSkqFElGYbk3M0ujLVwCfWE+TlvxtEk=
|
||||
github.com/hibiken/asynq/x v0.0.0-20220131170841-349f4c50fb1d h1:Er+U+9PmnyRHRDQjSjRQ24HoWvOY7w9Pk7bUPYM3Ags=
|
||||
github.com/hibiken/asynq/x v0.0.0-20220131170841-349f4c50fb1d/go.mod h1:VmxwMfMKyb6gyv8xG0oOBMXIhquWKPx+zPtbVBd2Q1s=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
@@ -193,8 +194,9 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
@@ -259,9 +261,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
|
||||
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
||||
github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o=
|
||||
github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
||||
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
@@ -287,8 +288,8 @@ github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
|
||||
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||
@@ -304,22 +305,20 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI=
|
||||
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
||||
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -349,7 +348,6 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -370,7 +368,6 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -383,7 +380,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -410,16 +406,13 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@@ -433,8 +426,8 @@ golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -455,7 +448,6 @@ golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -495,8 +487,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
27
x/go.mod
27
x/go.mod
@@ -3,24 +3,23 @@ module github.com/hibiken/asynq/x
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/hibiken/asynq v0.24.1
|
||||
github.com/prometheus/client_golang v1.11.1
|
||||
github.com/redis/go-redis/v9 v9.3.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hibiken/asynq v0.25.0
|
||||
github.com/prometheus/client_golang v1.20.5
|
||||
github.com/redis/go-redis/v9 v9.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.26.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
google.golang.org/protobuf v1.35.1 // indirect
|
||||
)
|
||||
|
||||
222
x/go.sum
222
x/go.sum
@@ -1,204 +1,50 @@
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hibiken/asynq v0.24.1 h1:+5iIEAyA9K/lcSPvx3qoPtsKJeKI5u9aOIvUmSsazEw=
|
||||
github.com/hibiken/asynq v0.24.1/go.mod h1:u5qVeSbrnfT+vtG5Mq8ZPzQu/BmCKMHvTGb91uy9Tts=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hibiken/asynq v0.25.0 h1:VCPyRRrrjFChsTSI8x5OCPu51MlEz6Rk+1p0kHKnZug=
|
||||
github.com/hibiken/asynq v0.25.0/go.mod h1:DYQ1etBEl2Y+uSkqFElGYbk3M0ujLVwCfWE+TlvxtEk=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s=
|
||||
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
||||
github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0=
|
||||
github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
||||
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
|
||||
@@ -25,13 +25,13 @@ type QueueMetricsCollector struct {
|
||||
func (qmc *QueueMetricsCollector) collectQueueInfo() ([]*asynq.QueueInfo, error) {
|
||||
qnames, err := qmc.inspector.Queues()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get queue names: %v", err)
|
||||
return nil, fmt.Errorf("failed to get queue names: %w", err)
|
||||
}
|
||||
infos := make([]*asynq.QueueInfo, len(qnames))
|
||||
for i, qname := range qnames {
|
||||
qinfo, err := qmc.inspector.GetQueueInfo(qname)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get queue info: %v", err)
|
||||
return nil, fmt.Errorf("failed to get queue info: %w", err)
|
||||
}
|
||||
infos[i] = qinfo
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ func (s *Semaphore) Release(ctx context.Context) error {
|
||||
|
||||
n, err := s.rc.ZRem(ctx, semaphoreKey(s.scope), taskID).Result()
|
||||
if err != nil {
|
||||
return fmt.Errorf("redis command failed: %v", err)
|
||||
return fmt.Errorf("redis command failed: %w", err)
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
@@ -110,5 +110,5 @@ func (s *Semaphore) Close() error {
|
||||
}
|
||||
|
||||
func semaphoreKey(scope string) string {
|
||||
return fmt.Sprintf("asynq:sema:%s", scope)
|
||||
return "asynq:sema:" + scope
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user