mirror of
https://github.com/hibiken/asynq.git
synced 2026-08-21 11:24:21 +08:00
Merge pull request #1094 from fanatics-live/batch-enqueue
Add BatchEnqueue for pipelined multi-task enqueue
This commit is contained in:
@@ -160,6 +160,156 @@ 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()
|
||||
|
||||
Reference in New Issue
Block a user