mirror of
https://github.com/hibiken/asynq.git
synced 2025-10-24 22:46:11 +08:00
Add GetTaskInfo method to RDB
This commit is contained in:
@@ -6,6 +6,7 @@ package rdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -287,6 +288,65 @@ func reverse(x []string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Parses val as base10 64-bit integer if val contains a value.
|
||||
// Uses default value if val is nil.
|
||||
//
|
||||
// Assumes val contains either string value or nil.
|
||||
func parseIntOrDefault(val interface{}, defaultVal int64) (int64, error) {
|
||||
if val == nil {
|
||||
return defaultVal, nil
|
||||
}
|
||||
return strconv.ParseInt(val.(string), 10, 64)
|
||||
}
|
||||
|
||||
// GetTaskInfo finds a task with the given id from the given queue.
|
||||
// Returns TaskInfo of the task if a task is found, otherwise returns ErrTaskNotFound.
|
||||
func (r *RDB) GetTaskInfo(qname, id string) (*base.TaskInfo, error) {
|
||||
key := base.TaskKey(qname, id)
|
||||
exists, err := r.client.Exists(key).Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exists == 0 {
|
||||
return nil, ErrTaskNotFound
|
||||
}
|
||||
// The "msg", "state" fields are non-nil;
|
||||
// whereas the "process_at", "last_failed_at" fields can be nil.
|
||||
res, err := r.client.HMGet(key, "msg", "state", "process_at", "last_failed_at").Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(res) != 4 {
|
||||
return nil, fmt.Errorf("asynq internal error: HMGET command returned %d elements", len(res))
|
||||
}
|
||||
encoded := res[0]
|
||||
if encoded == nil {
|
||||
return nil, fmt.Errorf("asynq internal error: HMGET field 'msg' was nil")
|
||||
}
|
||||
msg, err := base.DecodeMessage([]byte(encoded.(string)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state := res[1]
|
||||
if state == nil {
|
||||
return nil, fmt.Errorf("asynq internal error: HMGET field 'state' was nil")
|
||||
}
|
||||
processAt, err := parseIntOrDefault(res[2], 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lastFailedAt, err := parseIntOrDefault(res[3], 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &base.TaskInfo{
|
||||
TaskMessage: msg,
|
||||
State: strings.ToLower(state.(string)),
|
||||
NextProcessAt: processAt,
|
||||
LastFailedAt: lastFailedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Pagination specifies the page size and page number
|
||||
// for the list operation.
|
||||
type Pagination struct {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package rdb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -308,6 +309,162 @@ func TestRedisInfo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTaskInfo(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")
|
||||
m4 := h.NewTaskMessageWithQueue("task4", nil, "custom")
|
||||
|
||||
now := time.Now()
|
||||
oneHourFromNow := now.Add(1 * time.Hour)
|
||||
oneHourAgo := now.Add(-1 * time.Hour)
|
||||
|
||||
// state of the queues
|
||||
queueState := struct {
|
||||
active map[string][]*base.TaskMessage
|
||||
pending map[string][]*base.TaskMessage
|
||||
scheduled map[string][]base.Z
|
||||
retry map[string][]base.Z
|
||||
archived map[string][]base.Z
|
||||
}{
|
||||
|
||||
active: map[string][]*base.TaskMessage{
|
||||
"default": {m1},
|
||||
"custom": {},
|
||||
},
|
||||
pending: map[string][]*base.TaskMessage{
|
||||
"default": {},
|
||||
"custom": {m3},
|
||||
},
|
||||
scheduled: map[string][]base.Z{
|
||||
"default": {{Message: m2, Score: oneHourFromNow.Unix()}},
|
||||
"custom": {},
|
||||
},
|
||||
retry: map[string][]base.Z{
|
||||
"default": {},
|
||||
"custom": {},
|
||||
},
|
||||
archived: map[string][]base.Z{
|
||||
"default": {},
|
||||
"custom": {{Message: m4, Score: oneHourAgo.Unix()}},
|
||||
},
|
||||
}
|
||||
// seed redis with fixtures.
|
||||
h.FlushDB(t, r.client)
|
||||
h.SeedAllActiveQueues(t, r.client, queueState.active)
|
||||
h.SeedAllPendingQueues(t, r.client, queueState.pending)
|
||||
h.SeedAllScheduledQueues(t, r.client, queueState.scheduled)
|
||||
h.SeedAllRetryQueues(t, r.client, queueState.retry)
|
||||
h.SeedAllArchivedQueues(t, r.client, queueState.archived)
|
||||
|
||||
tests := []struct {
|
||||
qname string
|
||||
id uuid.UUID
|
||||
want *base.TaskInfo
|
||||
}{
|
||||
{
|
||||
qname: "default",
|
||||
id: m1.ID,
|
||||
want: &base.TaskInfo{
|
||||
TaskMessage: m1,
|
||||
State: "active",
|
||||
NextProcessAt: 0,
|
||||
LastFailedAt: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
qname: "default",
|
||||
id: m2.ID,
|
||||
want: &base.TaskInfo{
|
||||
TaskMessage: m2,
|
||||
State: "scheduled",
|
||||
NextProcessAt: oneHourFromNow.Unix(),
|
||||
LastFailedAt: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
qname: "custom",
|
||||
id: m3.ID,
|
||||
want: &base.TaskInfo{
|
||||
TaskMessage: m3,
|
||||
State: "pending",
|
||||
NextProcessAt: now.Unix(),
|
||||
LastFailedAt: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
qname: "custom",
|
||||
id: m4.ID,
|
||||
want: &base.TaskInfo{
|
||||
TaskMessage: m4,
|
||||
State: "archived",
|
||||
NextProcessAt: 0,
|
||||
LastFailedAt: oneHourAgo.Unix(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
|
||||
got, err := r.GetTaskInfo(tc.qname, tc.id.String())
|
||||
if err != nil {
|
||||
t.Errorf("GetTaskInfo(%q, %q) failed: %v",
|
||||
tc.qname, tc.id.String(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("GetTaskInfo(%q, %q) = %v, want %v; (-want,+got)\n%s",
|
||||
tc.qname, tc.id.String(), got, tc.want, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTaskInfoError(t *testing.T) {
|
||||
r := setup(t)
|
||||
defer r.Close()
|
||||
|
||||
m := h.NewTaskMessageWithQueue("test1", nil, "custom")
|
||||
h.SeedPendingQueue(t, r.client, []*base.TaskMessage{m}, "custom")
|
||||
|
||||
tests := []struct {
|
||||
desc string
|
||||
qname string
|
||||
id uuid.UUID
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
desc: "searching for task in a wrong queue",
|
||||
qname: "default",
|
||||
id: m.ID,
|
||||
wantErr: ErrTaskNotFound,
|
||||
},
|
||||
{
|
||||
desc: "searching with non-existent task ID",
|
||||
qname: "custom",
|
||||
id: uuid.New(),
|
||||
wantErr: ErrTaskNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
_, err := r.GetTaskInfo(tc.qname, tc.id.String())
|
||||
if err == nil {
|
||||
t.Errorf("%s; GetTaskInfo(%q, %q) returned nil error, want %v",
|
||||
tc.desc, tc.qname, tc.id.String(), tc.wantErr)
|
||||
continue
|
||||
}
|
||||
|
||||
if !errors.Is(err, tc.wantErr) {
|
||||
t.Errorf("%s; GetTaskInfo(%q, %q) returned %v error, want %v",
|
||||
tc.desc, tc.qname, tc.id.String(), err, tc.wantErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPending(t *testing.T) {
|
||||
r := setup(t)
|
||||
defer r.Close()
|
||||
|
||||
@@ -195,7 +195,7 @@ if redis.call("EXISTS", KEYS[2]) == 0 then
|
||||
else
|
||||
return redis.error_reply("asynq internal error: both timeout and deadline are not set")
|
||||
end
|
||||
redis.call("HSET", key, "state", "ACTIVE")
|
||||
redis.call("HSET", key, "state", "ACTIVE", "process_at", 0)
|
||||
redis.call("ZADD", KEYS[4], score, id)
|
||||
return {msg, score}
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user