2
0
mirror of https://github.com/hibiken/asynq.git synced 2024-11-10 11:31:58 +08:00
asynq/internal/rdb/inspect.go

781 lines
22 KiB
Go
Raw Normal View History

2020-01-03 10:13:16 +08:00
// Copyright 2020 Kentaro Hibino. All rights reserved.
// Use of this source code is governed by a MIT license
// that can be found in the LICENSE file.
2019-12-05 12:30:37 +08:00
package rdb
import (
"fmt"
2019-12-23 01:09:57 +08:00
"strings"
2019-12-05 12:30:37 +08:00
"time"
"github.com/go-redis/redis/v7"
"github.com/google/uuid"
2019-12-22 23:15:45 +08:00
"github.com/hibiken/asynq/internal/base"
"github.com/spf13/cast"
2019-12-05 12:30:37 +08:00
)
2020-08-11 20:35:06 +08:00
// AllQueues returns a list of all queue names.
func (r *RDB) AllQueues() ([]string, error) {
return r.client.SMembers(base.AllQueues).Result()
2019-12-05 12:30:37 +08:00
}
2020-08-11 20:35:06 +08:00
// Stats represents a state of queues at a certain time.
type Stats struct {
// Name of the queue (e.g. "default", "critical").
2020-08-12 20:30:59 +08:00
Queue string
// Paused indicates whether the queue is paused.
// If true, tasks in the queue should not be processed.
Paused bool
2020-08-11 20:35:06 +08:00
// Number of tasks in each state.
Enqueued int
InProgress int
Scheduled int
Retry int
Dead int
// Total number of tasks processed during the current date.
// The number includes both succeeded and failed tasks.
Processed int
// Total number of tasks failed during the current date.
Failed int
// Time this stats was taken.
Timestamp time.Time
}
2020-01-05 01:41:05 +08:00
// DailyStats holds aggregate data for a given day.
type DailyStats struct {
2020-08-12 20:30:59 +08:00
// Name of the queue (e.g. "default", "critical").
Queue string
// Total number of tasks processed during the given day.
// The number includes both succeeded and failed tasks.
2020-01-05 01:41:05 +08:00
Processed int
2020-08-12 20:30:59 +08:00
// Total number of tasks failed during the given day.
Failed int
// Date this stats was taken.
Time time.Time
2020-01-05 01:41:05 +08:00
}
2020-08-11 21:28:12 +08:00
// KEYS[1] -> asynq:<qname>
// KEYS[2] -> asynq:<qname>:in_progress
// KEYS[3] -> asynq:<qname>:scheduled
// KEYS[4] -> asynq:<qname>:retry
// KEYS[5] -> asynq:<qname>:dead
// KEYS[6] -> asynq:<qname>:processed:<yyyy-mm-dd>
// KEYS[7] -> asynq:<qname>:failed:<yyyy-mm-dd>
// KEYS[8] -> asynq:<qname>:paused
var currentStatsCmd = redis.NewScript(`
local res = {}
2020-08-11 21:28:12 +08:00
table.insert(res, KEYS[1])
table.insert(res, redis.call("LLEN", KEYS[1]))
table.insert(res, KEYS[2])
table.insert(res, redis.call("LLEN", KEYS[2]))
table.insert(res, KEYS[3])
table.insert(res, redis.call("ZCARD", KEYS[3]))
table.insert(res, KEYS[4])
table.insert(res, redis.call("ZCARD", KEYS[4]))
table.insert(res, KEYS[5])
table.insert(res, redis.call("ZCARD", KEYS[5]))
local pcount = 0
local p = redis.call("GET", KEYS[6])
if p then
pcount = tonumber(p)
end
2020-08-11 21:28:12 +08:00
table.insert(res, KEYS[6])
table.insert(res, pcount)
local fcount = 0
local f = redis.call("GET", KEYS[7])
if f then
fcount = tonumber(f)
end
2020-08-11 21:28:12 +08:00
table.insert(res, KEYS[7])
table.insert(res, fcount)
2020-08-11 21:28:12 +08:00
table.insert(res, KEYS[8])
table.insert(res, redis.call("EXISTS", KEYS[8]))
return res`)
2019-12-05 12:30:37 +08:00
// CurrentStats returns a current state of the queues.
2020-08-11 21:28:12 +08:00
func (r *RDB) CurrentStats(qname string) (*Stats, error) {
exists, err := r.client.SIsMember(base.AllQueues, qname).Result()
if err != nil {
return nil, err
}
if !exists {
2020-08-13 21:54:32 +08:00
return nil, &ErrQueueNotFound{qname}
2020-08-11 21:28:12 +08:00
}
now := time.Now()
res, err := currentStatsCmd.Run(r.client, []string{
2020-08-11 21:28:12 +08:00
base.QueueKey(qname),
base.InProgressKey(qname),
base.ScheduledKey(qname),
base.RetryKey(qname),
base.DeadKey(qname),
base.ProcessedKey(qname, now),
base.FailedKey(qname, now),
base.PausedKey(qname),
}).Result()
if err != nil {
return nil, err
}
data, err := cast.ToSliceE(res)
if err != nil {
return nil, err
}
stats := &Stats{
2020-08-12 20:30:59 +08:00
Queue: qname,
Timestamp: now,
}
for i := 0; i < len(data); i += 2 {
key := cast.ToString(data[i])
val := cast.ToInt(data[i+1])
2020-08-11 21:28:12 +08:00
switch key {
case base.QueueKey(qname):
stats.Enqueued = val
case base.InProgressKey(qname):
stats.InProgress = val
2020-08-11 21:28:12 +08:00
case base.ScheduledKey(qname):
stats.Scheduled = val
2020-08-13 21:54:32 +08:00
case base.RetryKey(qname):
stats.Retry = val
2020-08-11 21:28:12 +08:00
case base.DeadKey(qname):
stats.Dead = val
2020-08-11 21:28:12 +08:00
case base.ProcessedKey(qname, now):
stats.Processed = val
2020-08-11 21:28:12 +08:00
case base.FailedKey(qname, now):
stats.Failed = val
2020-08-11 21:28:12 +08:00
case base.PausedKey(qname):
if val == 0 {
stats.Paused = false
} else {
stats.Paused = true
}
}
}
return stats, nil
2019-12-05 12:30:37 +08:00
}
var historicalStatsCmd = redis.NewScript(`
local res = {}
for _, key in ipairs(KEYS) do
local n = redis.call("GET", key)
if not n then
2020-08-12 20:30:59 +08:00
n = 0
end
table.insert(res, tonumber(n))
end
return res`)
2020-08-12 20:30:59 +08:00
// HistoricalStats returns a list of stats from the last n days for the given queue.
func (r *RDB) HistoricalStats(qname string, n int) ([]*DailyStats, error) {
2020-01-05 01:41:05 +08:00
if n < 1 {
return []*DailyStats{}, nil
}
const day = 24 * time.Hour
now := time.Now().UTC()
var days []time.Time
var keys []string
for i := 0; i < n; i++ {
ts := now.Add(-time.Duration(i) * day)
days = append(days, ts)
2020-08-12 20:30:59 +08:00
keys = append(keys, base.ProcessedKey(qname, ts))
2020-08-13 21:54:32 +08:00
keys = append(keys, base.FailedKey(qname, ts))
2020-01-05 01:41:05 +08:00
}
2020-08-12 20:30:59 +08:00
res, err := historicalStatsCmd.Run(r.client, keys).Result()
2020-01-05 01:41:05 +08:00
if err != nil {
return nil, err
}
data, err := cast.ToIntSliceE(res)
if err != nil {
return nil, err
}
var stats []*DailyStats
for i := 0; i < len(data); i += 2 {
stats = append(stats, &DailyStats{
2020-08-12 20:30:59 +08:00
Queue: qname,
2020-01-05 01:41:05 +08:00
Processed: data[i],
Failed: data[i+1],
Time: days[i/2],
})
}
return stats, nil
}
2019-12-23 01:09:57 +08:00
// RedisInfo returns a map of redis info.
func (r *RDB) RedisInfo() (map[string]string, error) {
res, err := r.client.Info().Result()
if err != nil {
return nil, err
}
info := make(map[string]string)
lines := strings.Split(res, "\r\n")
for _, l := range lines {
kv := strings.Split(l, ":")
if len(kv) == 2 {
info[kv[0]] = kv[1]
}
}
return info, nil
}
func reverse(x []string) {
for i := len(x)/2 - 1; i >= 0; i-- {
opp := len(x) - 1 - i
x[i], x[opp] = x[opp], x[i]
}
}
// Pagination specifies the page size and page number
// for the list operation.
type Pagination struct {
// Number of items in the page.
Size int
// Page number starting from zero.
Page int
}
func (p Pagination) start() int64 {
return int64(p.Size * p.Page)
}
func (p Pagination) stop() int64 {
return int64(p.Size*p.Page + p.Size - 1)
}
// ListEnqueued returns enqueued tasks that are ready to be processed.
2020-07-13 21:29:41 +08:00
func (r *RDB) ListEnqueued(qname string, pgn Pagination) ([]*base.TaskMessage, error) {
2020-08-13 21:54:32 +08:00
if !r.client.SIsMember(base.AllQueues, qname).Val() {
return nil, fmt.Errorf("queue %q does not exist", qname)
}
2020-08-13 21:54:32 +08:00
return r.listMessages(base.QueueKey(qname), pgn)
2019-12-05 12:30:37 +08:00
}
2020-08-12 21:18:15 +08:00
// ListInProgress returns all tasks that are currently being processed for the given queue.
func (r *RDB) ListInProgress(qname string, pgn Pagination) ([]*base.TaskMessage, error) {
return r.listMessages(base.InProgressKey(qname), pgn)
2020-07-13 21:29:41 +08:00
}
// listMessages returns a list of TaskMessage in Redis list with the given key.
func (r *RDB) listMessages(key string, pgn Pagination) ([]*base.TaskMessage, error) {
// Note: Because we use LPUSH to redis list, we need to calculate the
// correct range and reverse the list to get the tasks with pagination.
stop := -pgn.start() - 1
start := -pgn.stop() - 1
2020-07-13 21:29:41 +08:00
data, err := r.client.LRange(key, start, stop).Result()
2019-12-05 12:30:37 +08:00
if err != nil {
return nil, err
}
reverse(data)
2020-07-13 21:29:41 +08:00
var msgs []*base.TaskMessage
2019-12-05 12:30:37 +08:00
for _, s := range data {
2020-07-13 21:29:41 +08:00
m, err := base.DecodeMessage(s)
2019-12-05 12:30:37 +08:00
if err != nil {
continue // bad data, ignore and continue
}
2020-07-13 21:29:41 +08:00
msgs = append(msgs, m)
2019-12-05 12:30:37 +08:00
}
2020-07-13 21:29:41 +08:00
return msgs, nil
2019-12-05 12:30:37 +08:00
}
2020-08-12 21:18:15 +08:00
// ListScheduled returns all tasks from the given queue that are scheduled
// to be processed in the future.
func (r *RDB) ListScheduled(qname string, pgn Pagination) ([]base.Z, error) {
return r.listZSetEntries(base.ScheduledKey(qname), pgn)
2019-12-05 12:30:37 +08:00
}
2020-08-12 21:18:15 +08:00
// ListRetry returns all tasks from the given queue that have failed before
// and willl be retried in the future.
func (r *RDB) ListRetry(qname string, pgn Pagination) ([]base.Z, error) {
return r.listZSetEntries(base.RetryKey(qname), pgn)
2019-12-05 12:30:37 +08:00
}
2020-08-12 21:18:15 +08:00
// ListDead returns all tasks from the given queue that have exhausted its retry limit.
func (r *RDB) ListDead(qname string, pgn Pagination) ([]base.Z, error) {
return r.listZSetEntries(base.DeadKey(qname), pgn)
2020-07-13 21:29:41 +08:00
}
// listZSetEntries returns a list of message and score pairs in Redis sorted-set
// with the given key.
func (r *RDB) listZSetEntries(key string, pgn Pagination) ([]base.Z, error) {
data, err := r.client.ZRangeWithScores(key, pgn.start(), pgn.stop()).Result()
2019-12-05 12:30:37 +08:00
if err != nil {
return nil, err
}
2020-07-13 21:29:41 +08:00
var res []base.Z
2019-12-05 12:30:37 +08:00
for _, z := range data {
s, ok := z.Member.(string)
if !ok {
continue // bad data, ignore and continue
}
2020-07-13 21:29:41 +08:00
msg, err := base.DecodeMessage(s)
2019-12-05 12:30:37 +08:00
if err != nil {
continue // bad data, ignore and continue
}
2020-07-13 21:29:41 +08:00
res = append(res, base.Z{msg, int64(z.Score)})
2019-12-05 12:30:37 +08:00
}
2020-07-13 21:29:41 +08:00
return res, nil
2019-12-05 12:30:37 +08:00
}
2020-08-13 21:54:32 +08:00
// EnqueueDeadTask finds a dead task that matches the given id and score from
// the given queue and enqueues it for processing.
//If a task that matches the id and score does not exist, it returns ErrTaskNotFound.
func (r *RDB) EnqueueDeadTask(qname string, id uuid.UUID, score int64) error {
n, err := r.removeAndEnqueue(base.DeadKey(qname), base.QueueKey(qname), id.String(), float64(score))
if err != nil {
return err
}
if n == 0 {
return ErrTaskNotFound
}
return nil
}
2020-08-13 21:54:32 +08:00
// EnqueueRetryTask finds a retry task that matches the given id and score from
// the given queue and enqueues it for processing.
// If a task that matches the id and score does not exist, it returns ErrTaskNotFound.
func (r *RDB) EnqueueRetryTask(qname string, id uuid.UUID, score int64) error {
n, err := r.removeAndEnqueue(base.RetryKey(qname), base.QueueKey(qname), id.String(), float64(score))
if err != nil {
return err
}
if n == 0 {
return ErrTaskNotFound
}
return nil
}
2020-08-13 21:54:32 +08:00
// EnqueueScheduledTask finds a scheduled task that matches the given id and score from
// from the given queue and enqueues it for processing.
// If a task that matches the id and score does not exist, it returns ErrTaskNotFound.
func (r *RDB) EnqueueScheduledTask(qname string, id uuid.UUID, score int64) error {
n, err := r.removeAndEnqueue(base.ScheduledKey(qname), base.QueueKey(qname), id.String(), float64(score))
if err != nil {
return err
}
if n == 0 {
return ErrTaskNotFound
}
return nil
}
2020-08-13 21:54:32 +08:00
// EnqueueAllScheduledTasks enqueues all scheduled tasks from the given queue
2019-12-11 13:38:25 +08:00
// and returns the number of tasks enqueued.
2020-08-13 21:54:32 +08:00
func (r *RDB) EnqueueAllScheduledTasks(qname string) (int64, error) {
return r.removeAndEnqueueAll(base.ScheduledKey(qname), base.QueueKey(qname))
}
2020-08-13 21:54:32 +08:00
// EnqueueAllRetryTasks enqueues all retry tasks from the given queue
2019-12-11 13:38:25 +08:00
// and returns the number of tasks enqueued.
2020-08-13 21:54:32 +08:00
func (r *RDB) EnqueueAllRetryTasks(qname string) (int64, error) {
return r.removeAndEnqueueAll(base.RetryKey(qname), base.QueueKey(qname))
}
2019-12-11 13:38:25 +08:00
// EnqueueAllDeadTasks enqueues all tasks from dead queue
// and returns the number of tasks enqueued.
2020-08-13 21:54:32 +08:00
func (r *RDB) EnqueueAllDeadTasks(qname string) (int64, error) {
return r.removeAndEnqueueAll(base.DeadKey(qname), base.QueueKey(qname))
}
var removeAndEnqueueCmd = redis.NewScript(`
local msgs = redis.call("ZRANGEBYSCORE", KEYS[1], ARGV[1], ARGV[1])
for _, msg in ipairs(msgs) do
local decoded = cjson.decode(msg)
if decoded["ID"] == ARGV[2] then
2020-08-13 21:54:32 +08:00
redis.call("LPUSH", KEYS[2], msg)
redis.call("ZREM", KEYS[1], msg)
return 1
end
end
return 0`)
2020-08-13 21:54:32 +08:00
func (r *RDB) removeAndEnqueue(zset, qkey, id string, score float64) (int64, error) {
res, err := removeAndEnqueueCmd.Run(r.client, []string{zset, qkey}, score, id).Result()
if err != nil {
return 0, err
}
n, ok := res.(int64)
if !ok {
return 0, fmt.Errorf("could not cast %v to int64", res)
}
return n, nil
}
var removeAndEnqueueAllCmd = redis.NewScript(`
local msgs = redis.call("ZRANGE", KEYS[1], 0, -1)
for _, msg in ipairs(msgs) do
2020-08-13 21:54:32 +08:00
redis.call("LPUSH", KEYS[2], msg)
redis.call("ZREM", KEYS[1], msg)
end
return table.getn(msgs)`)
2020-08-13 21:54:32 +08:00
func (r *RDB) removeAndEnqueueAll(zset, qkey string) (int64, error) {
res, err := removeAndEnqueueAllCmd.Run(r.client, []string{zset, qkey}).Result()
if err != nil {
2019-12-11 13:38:25 +08:00
return 0, err
}
2019-12-11 13:38:25 +08:00
n, ok := res.(int64)
if !ok {
return 0, fmt.Errorf("could not cast %v to int64", res)
}
return n, nil
}
2020-08-15 21:38:33 +08:00
// KillRetryTask finds a retry task that matches the given id and score from the given queue
// and kills it. If a task that maches the id and score does not exist, it returns ErrTaskNotFound.
func (r *RDB) KillRetryTask(qname string, id uuid.UUID, score int64) error {
n, err := r.removeAndKill(base.RetryKey(qname), base.DeadKey(qname), id.String(), float64(score))
if err != nil {
return err
}
if n == 0 {
return ErrTaskNotFound
}
return nil
}
2020-08-15 21:38:33 +08:00
// KillScheduledTask finds a scheduled task that matches the given id and score from the given queue
// and kills it. If a task that maches the id and score does not exist, it returns ErrTaskNotFound.
func (r *RDB) KillScheduledTask(qname string, id uuid.UUID, score int64) error {
n, err := r.removeAndKill(base.ScheduledKey(qname), base.DeadKey(qname), id.String(), float64(score))
if err != nil {
return err
}
if n == 0 {
return ErrTaskNotFound
}
return nil
}
2020-08-15 21:38:33 +08:00
// KillAllRetryTasks kills all retry tasks from the given queue and
// returns the number of tasks that were moved.
2020-08-15 21:38:33 +08:00
func (r *RDB) KillAllRetryTasks(qname string) (int64, error) {
return r.removeAndKillAll(base.RetryKey(qname), base.DeadKey(qname))
}
2020-08-15 21:38:33 +08:00
// KillAllScheduledTasks kills all scheduled tasks from the given queue and
// returns the number of tasks that were moved.
2020-08-15 21:38:33 +08:00
func (r *RDB) KillAllScheduledTasks(qname string) (int64, error) {
return r.removeAndKillAll(base.ScheduledKey(qname), base.DeadKey(qname))
}
// KEYS[1] -> ZSET to move task from (e.g., retry queue)
2020-08-15 21:38:33 +08:00
// KEYS[2] -> asynq:{<qname>}:dead
// ARGV[1] -> score of the task to kill
// ARGV[2] -> id of the task to kill
// ARGV[3] -> current timestamp
// ARGV[4] -> cutoff timestamp (e.g., 90 days ago)
// ARGV[5] -> max number of tasks in dead queue (e.g., 100)
var removeAndKillCmd = redis.NewScript(`
local msgs = redis.call("ZRANGEBYSCORE", KEYS[1], ARGV[1], ARGV[1])
for _, msg in ipairs(msgs) do
local decoded = cjson.decode(msg)
if decoded["ID"] == ARGV[2] then
redis.call("ZREM", KEYS[1], msg)
redis.call("ZADD", KEYS[2], ARGV[3], msg)
redis.call("ZREMRANGEBYSCORE", KEYS[2], "-inf", ARGV[4])
redis.call("ZREMRANGEBYRANK", KEYS[2], 0, -ARGV[5])
return 1
end
end
return 0`)
2020-08-15 21:38:33 +08:00
func (r *RDB) removeAndKill(src, dst, id string, score float64) (int64, error) {
now := time.Now()
limit := now.AddDate(0, 0, -deadExpirationInDays).Unix() // 90 days ago
res, err := removeAndKillCmd.Run(r.client,
2020-08-15 21:38:33 +08:00
[]string{src, dst},
score, id, now.Unix(), limit, maxDeadTasks).Result()
if err != nil {
return 0, err
}
n, ok := res.(int64)
if !ok {
return 0, fmt.Errorf("could not cast %v to int64", res)
}
return n, nil
}
// KEYS[1] -> ZSET to move task from (e.g., retry queue)
2020-08-15 21:38:33 +08:00
// KEYS[2] -> asynq:{<qname>}:dead
// ARGV[1] -> current timestamp
// ARGV[2] -> cutoff timestamp (e.g., 90 days ago)
// ARGV[3] -> max number of tasks in dead queue (e.g., 100)
var removeAndKillAllCmd = redis.NewScript(`
local msgs = redis.call("ZRANGE", KEYS[1], 0, -1)
for _, msg in ipairs(msgs) do
redis.call("ZADD", KEYS[2], ARGV[1], msg)
redis.call("ZREM", KEYS[1], msg)
redis.call("ZREMRANGEBYSCORE", KEYS[2], "-inf", ARGV[2])
redis.call("ZREMRANGEBYRANK", KEYS[2], 0, -ARGV[3])
end
return table.getn(msgs)`)
2020-08-15 21:38:33 +08:00
func (r *RDB) removeAndKillAll(src, dst string) (int64, error) {
now := time.Now()
limit := now.AddDate(0, 0, -deadExpirationInDays).Unix() // 90 days ago
2020-08-15 21:38:33 +08:00
res, err := removeAndKillAllCmd.Run(r.client, []string{src, dst},
now.Unix(), limit, maxDeadTasks).Result()
if err != nil {
return 0, err
}
n, ok := res.(int64)
if !ok {
return 0, fmt.Errorf("could not cast %v to int64", res)
}
return n, nil
}
2019-12-12 11:56:19 +08:00
2020-08-15 21:38:33 +08:00
/*
2019-12-12 11:56:19 +08:00
// DeleteDeadTask finds a task that matches the given id and score from dead queue
// and deletes it. If a task that matches the id and score does not exist,
// it returns ErrTaskNotFound.
func (r *RDB) DeleteDeadTask(id uuid.UUID, score int64) error {
2019-12-22 23:15:45 +08:00
return r.deleteTask(base.DeadQueue, id.String(), float64(score))
2019-12-12 11:56:19 +08:00
}
// DeleteRetryTask finds a task that matches the given id and score from retry queue
// and deletes it. If a task that matches the id and score does not exist,
// it returns ErrTaskNotFound.
func (r *RDB) DeleteRetryTask(id uuid.UUID, score int64) error {
2019-12-22 23:15:45 +08:00
return r.deleteTask(base.RetryQueue, id.String(), float64(score))
2019-12-12 11:56:19 +08:00
}
// DeleteScheduledTask finds a task that matches the given id and score from
// scheduled queue and deletes it. If a task that matches the id and score
//does not exist, it returns ErrTaskNotFound.
func (r *RDB) DeleteScheduledTask(id uuid.UUID, score int64) error {
2019-12-22 23:15:45 +08:00
return r.deleteTask(base.ScheduledQueue, id.String(), float64(score))
2019-12-12 11:56:19 +08:00
}
var deleteTaskCmd = redis.NewScript(`
local msgs = redis.call("ZRANGEBYSCORE", KEYS[1], ARGV[1], ARGV[1])
for _, msg in ipairs(msgs) do
local decoded = cjson.decode(msg)
if decoded["ID"] == ARGV[2] then
redis.call("ZREM", KEYS[1], msg)
return 1
2019-12-12 11:56:19 +08:00
end
end
return 0`)
func (r *RDB) deleteTask(zset, id string, score float64) error {
res, err := deleteTaskCmd.Run(r.client, []string{zset}, score, id).Result()
2019-12-12 11:56:19 +08:00
if err != nil {
return err
}
n, ok := res.(int64)
if !ok {
return fmt.Errorf("could not cast %v to int64", res)
}
if n == 0 {
return ErrTaskNotFound
}
return nil
}
2019-12-12 22:38:01 +08:00
2020-07-13 21:29:41 +08:00
// KEYS[1] -> queue to delete
var deleteAllCmd = redis.NewScript(`
local n = redis.call("ZCARD", KEYS[1])
redis.call("DEL", KEYS[1])
return n`)
// DeleteAllDeadTasks deletes all tasks from the dead queue
// and returns the number of tasks deleted.
func (r *RDB) DeleteAllDeadTasks() (int64, error) {
return r.deleteAll(base.DeadQueue)
}
// DeleteAllRetryTasks deletes all tasks from the dead queue
// and returns the number of tasks deleted.
func (r *RDB) DeleteAllRetryTasks() (int64, error) {
return r.deleteAll(base.RetryQueue)
2019-12-12 22:38:01 +08:00
}
2020-07-13 21:29:41 +08:00
// DeleteAllScheduledTasks deletes all tasks from the dead queue
// and returns the number of tasks deleted.
func (r *RDB) DeleteAllScheduledTasks() (int64, error) {
return r.deleteAll(base.ScheduledQueue)
2019-12-12 22:38:01 +08:00
}
2020-07-13 21:29:41 +08:00
func (r *RDB) deleteAll(key string) (int64, error) {
res, err := deleteAllCmd.Run(r.client, []string{key}).Result()
if err != nil {
return 0, err
}
n, ok := res.(int64)
if !ok {
return 0, fmt.Errorf("could not cast %v to int64", res)
}
return n, nil
2019-12-12 22:38:01 +08:00
}
2020-08-13 21:54:32 +08:00
*/
2020-01-13 22:50:03 +08:00
2020-01-13 23:03:07 +08:00
// ErrQueueNotFound indicates specified queue does not exist.
type ErrQueueNotFound struct {
qname string
}
func (e *ErrQueueNotFound) Error() string {
return fmt.Sprintf("queue %q does not exist", e.qname)
}
// ErrQueueNotEmpty indicates specified queue is not empty.
type ErrQueueNotEmpty struct {
qname string
}
func (e *ErrQueueNotEmpty) Error() string {
return fmt.Sprintf("queue %q is not empty", e.qname)
}
2020-08-13 21:54:32 +08:00
/*
// Skip checking whether queue is empty before removing.
var removeQueueForceCmd = redis.NewScript(`
local n = redis.call("SREM", KEYS[1], KEYS[2])
if n == 0 then
return redis.error_reply("LIST NOT FOUND")
end
redis.call("DEL", KEYS[2])
return redis.status_reply("OK")`)
// Checks whether queue is empty before removing.
var removeQueueCmd = redis.NewScript(`
local l = redis.call("LLEN", KEYS[2]) if l > 0 then
return redis.error_reply("LIST NOT EMPTY")
end
local n = redis.call("SREM", KEYS[1], KEYS[2])
if n == 0 then
return redis.error_reply("LIST NOT FOUND")
end
redis.call("DEL", KEYS[2])
return redis.status_reply("OK")`)
2020-01-13 23:03:07 +08:00
// RemoveQueue removes the specified queue.
//
// If force is set to true, it will remove the queue regardless
// of whether the queue is empty.
// If force is set to false, it will only remove the queue if
// it is empty.
func (r *RDB) RemoveQueue(qname string, force bool) error {
var script *redis.Script
if force {
script = removeQueueForceCmd
2020-01-13 23:03:07 +08:00
} else {
script = removeQueueCmd
2020-01-13 23:03:07 +08:00
}
err := script.Run(r.client,
[]string{base.AllQueues, base.QueueKey(qname)},
force).Err()
if err != nil {
switch err.Error() {
case "LIST NOT FOUND":
return &ErrQueueNotFound{qname}
case "LIST NOT EMPTY":
return &ErrQueueNotEmpty{qname}
default:
return err
}
}
return nil
2020-01-13 22:50:03 +08:00
}
2020-02-02 14:22:48 +08:00
// Note: Script also removes stale keys.
2020-04-13 08:09:58 +08:00
var listServersCmd = redis.NewScript(`
local res = {}
local now = tonumber(ARGV[1])
local keys = redis.call("ZRANGEBYSCORE", KEYS[1], now, "+inf")
for _, key in ipairs(keys) do
2020-04-13 08:09:58 +08:00
local s = redis.call("GET", key)
if s then
table.insert(res, s)
2020-08-13 21:54:32 +08:00
end
end
redis.call("ZREMRANGEBYSCORE", KEYS[1], "-inf", now-1)
return res`)
2020-04-13 23:14:55 +08:00
// ListServers returns the list of server info.
2020-04-13 08:09:58 +08:00
func (r *RDB) ListServers() ([]*base.ServerInfo, error) {
res, err := listServersCmd.Run(r.client,
[]string{base.AllServers}, time.Now().UTC().Unix()).Result()
2020-02-02 14:22:48 +08:00
if err != nil {
return nil, err
}
data, err := cast.ToStringSliceE(res)
if err != nil {
return nil, err
}
2020-04-13 08:09:58 +08:00
var servers []*base.ServerInfo
2020-02-02 14:22:48 +08:00
for _, s := range data {
2020-04-13 08:09:58 +08:00
var info base.ServerInfo
err := json.Unmarshal([]byte(s), &info)
2020-02-02 14:22:48 +08:00
if err != nil {
continue // skip bad data
}
2020-04-13 08:09:58 +08:00
servers = append(servers, &info)
2020-02-02 14:22:48 +08:00
}
2020-04-13 08:09:58 +08:00
return servers, nil
2020-02-02 14:22:48 +08:00
}
2020-02-23 12:42:53 +08:00
// Note: Script also removes stale keys.
var listWorkersCmd = redis.NewScript(`
local res = {}
local now = tonumber(ARGV[1])
local keys = redis.call("ZRANGEBYSCORE", KEYS[1], now, "+inf")
for _, key in ipairs(keys) do
local workers = redis.call("HVALS", key)
for _, w in ipairs(workers) do
table.insert(res, w)
2020-08-13 21:54:32 +08:00
end
2020-02-23 12:42:53 +08:00
end
redis.call("ZREMRANGEBYSCORE", KEYS[1], "-inf", now-1)
return res`)
// ListWorkers returns the list of worker stats.
func (r *RDB) ListWorkers() ([]*base.WorkerInfo, error) {
res, err := listWorkersCmd.Run(r.client, []string{base.AllWorkers}, time.Now().UTC().Unix()).Result()
if err != nil {
return nil, err
}
data, err := cast.ToStringSliceE(res)
if err != nil {
return nil, err
}
var workers []*base.WorkerInfo
for _, s := range data {
var w base.WorkerInfo
err := json.Unmarshal([]byte(s), &w)
if err != nil {
continue // skip bad data
}
workers = append(workers, &w)
}
return workers, nil
}
2020-08-13 21:54:32 +08:00
*/
2020-06-03 21:44:12 +08:00
// Pause pauses processing of tasks from the given queue.
func (r *RDB) Pause(qname string) error {
2020-08-13 21:54:32 +08:00
key := base.PausedKey(qname)
ok, err := r.client.SetNX(key, time.Now().Unix(), 0).Result()
if err != nil {
return err
}
2020-08-13 21:54:32 +08:00
if !ok {
return fmt.Errorf("queue %q is already paused", qname)
}
return nil
2020-06-03 21:44:12 +08:00
}
// Unpause resumes processing of tasks from the given queue.
func (r *RDB) Unpause(qname string) error {
2020-08-13 21:54:32 +08:00
key := base.PausedKey(qname)
deleted, err := r.client.Del(key).Result()
if err != nil {
return err
}
if deleted == 0 {
return fmt.Errorf("queue %q is not paused", qname)
}
return nil
2020-06-03 21:44:12 +08:00
}