2
0
mirror of https://github.com/hibiken/asynq.git synced 2024-09-20 02:55:54 +08:00

Update processor to query queues based on priority

This commit is contained in:
Ken Hibino 2020-01-07 07:03:39 -08:00
parent 53d0902808
commit 8d9a2d1313
2 changed files with 74 additions and 8 deletions

View File

@ -7,6 +7,7 @@ package asynq
import (
"fmt"
"log"
"math/rand"
"sync"
"time"
@ -109,12 +110,7 @@ func (p *processor) start() {
// exec pulls a task out of the queue and starts a worker goroutine to
// process the task.
func (p *processor) exec() {
// TODO(hibiken): Randomize the order to avoid starving low priority queues
var qnames []string
for q := range p.queueConfig {
qnames = append(qnames, q)
}
qnames := p.queues()
msg, err := p.rdb.Dequeue(qnames...)
if err == rdb.ErrNoProcessableTask {
// queues are empty, this is a normal behavior.
@ -209,6 +205,21 @@ func (p *processor) kill(msg *base.TaskMessage, e error) {
}
}
// queues returns a list of queues to query. Order of the list
// is based roughly on the priority of each queue, but randomizes
// it to avoid starving low priority queues.
func (p *processor) queues() []string {
var names []string
for qname, priority := range p.queueConfig {
for i := 0; i < int(priority); i++ {
names = append(names, qname)
}
}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
r.Shuffle(len(names), func(i, j int) { names[i], names[j] = names[j], names[i] })
return uniq(names, len(p.queueConfig))
}
// perform calls the handler with the given task.
// If the call returns without panic, it simply returns the value,
// otherwise, it recovers from panic and returns an error.
@ -220,3 +231,20 @@ func perform(h Handler, task *Task) (err error) {
}()
return h.ProcessTask(task)
}
// uniq dedupes elements and returns a slice of unique names of length l.
// Order of the output slice is based on the input list.
func uniq(names []string, l int) []string {
var res []string
seen := make(map[string]struct{})
for _, s := range names {
if _, ok := seen[s]; !ok {
seen[s] = struct{}{}
res = append(res, s)
}
if len(res) == l {
break
}
}
return res
}

View File

@ -6,6 +6,7 @@ package asynq
import (
"fmt"
"sort"
"sync"
"testing"
"time"
@ -51,7 +52,7 @@ func TestProcessorSuccess(t *testing.T) {
}
for _, tc := range tests {
h.FlushDB(t, r) // clean up db before each test case.
h.FlushDB(t, r) // clean up db before each test case.
h.SeedEnqueuedQueue(t, r, tc.enqueued) // initialize default queue.
// instantiate a new processor
@ -137,7 +138,7 @@ func TestProcessorRetry(t *testing.T) {
}
for _, tc := range tests {
h.FlushDB(t, r) // clean up db before each test case.
h.FlushDB(t, r) // clean up db before each test case.
h.SeedEnqueuedQueue(t, r, tc.enqueued) // initialize default queue.
// instantiate a new processor
@ -178,6 +179,43 @@ func TestProcessorRetry(t *testing.T) {
}
}
func TestProcessorQueues(t *testing.T) {
sortOpt := cmp.Transformer("SortStrings", func(in []string) []string {
out := append([]string(nil), in...) // Copy input to avoid mutating it
sort.Strings(out)
return out
})
tests := []struct {
queueCfg map[string]uint
want []string
}{
{
queueCfg: map[string]uint{
"high": 6,
"default": 3,
"low": 1,
},
want: []string{"high", "default", "low"},
},
{
queueCfg: map[string]uint{
"default": 1,
},
want: []string{"default"},
},
}
for _, tc := range tests {
p := newProcessor(nil, 10, tc.queueCfg, defaultDelayFunc)
got := p.queues()
if diff := cmp.Diff(tc.want, got, sortOpt); diff != "" {
t.Errorf("with queue config: %v\n(*processor).queues() = %v, want %v\n(-want,+got):\n%s",
tc.queueCfg, got, tc.want, diff)
}
}
}
func TestPerform(t *testing.T) {
tests := []struct {
desc string