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-22 23:15:45 +08:00
|
|
|
// Package base defines foundational types and constants used in asynq package.
|
|
|
|
package base
|
|
|
|
|
2019-12-23 21:33:48 +08:00
|
|
|
import (
|
2020-02-13 09:12:09 +08:00
|
|
|
"context"
|
2020-06-12 11:58:27 +08:00
|
|
|
"encoding/json"
|
2020-01-31 22:48:58 +08:00
|
|
|
"fmt"
|
2020-08-07 20:36:54 +08:00
|
|
|
"sort"
|
2020-01-11 13:32:15 +08:00
|
|
|
"strings"
|
2020-02-02 14:22:48 +08:00
|
|
|
"sync"
|
2019-12-23 21:33:48 +08:00
|
|
|
"time"
|
|
|
|
|
2020-04-18 22:55:10 +08:00
|
|
|
"github.com/go-redis/redis/v7"
|
2020-07-02 21:21:20 +08:00
|
|
|
"github.com/google/uuid"
|
2019-12-23 21:33:48 +08:00
|
|
|
)
|
2019-12-22 23:15:45 +08:00
|
|
|
|
2020-06-30 04:56:53 +08:00
|
|
|
// Version of asynq library and CLI.
|
|
|
|
const Version = "0.10.0"
|
|
|
|
|
2020-01-06 22:53:40 +08:00
|
|
|
// DefaultQueueName is the queue name used if none are specified by user.
|
|
|
|
const DefaultQueueName = "default"
|
|
|
|
|
2020-08-06 22:34:00 +08:00
|
|
|
// DefaultQueue is the redis key for the default queue.
|
|
|
|
var DefaultQueue = QueueKey(DefaultQueueName)
|
|
|
|
|
|
|
|
// Global Redis keys.
|
2019-12-22 23:15:45 +08:00
|
|
|
const (
|
2020-08-06 22:34:00 +08:00
|
|
|
AllServers = "asynq:servers" // ZSET
|
|
|
|
AllWorkers = "asynq:workers" // ZSET
|
|
|
|
AllQueues = "asynq:queues" // SET
|
|
|
|
CancelChannel = "asynq:cancel" // PubSub channel
|
2019-12-22 23:15:45 +08:00
|
|
|
)
|
|
|
|
|
2020-02-20 22:34:09 +08:00
|
|
|
// QueueKey returns a redis key for the given queue name.
|
2020-01-06 22:53:40 +08:00
|
|
|
func QueueKey(qname string) string {
|
2020-08-06 22:34:00 +08:00
|
|
|
return fmt.Sprintf("asynq:{%s}", qname)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Should we rename this to "active"?
|
|
|
|
// InProgressKey returns a redis key for the in-progress tasks.
|
|
|
|
func InProgressKey(qname string) string {
|
|
|
|
return fmt.Sprintf("asynq:{%s}:in_progress", qname)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ScheduledKey returns a redis key for the scheduled tasks.
|
|
|
|
func ScheduledKey(qname string) string {
|
|
|
|
return fmt.Sprintf("asynq:{%s}:scheduled", qname)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RetryKey returns a redis key for the retry tasks.
|
|
|
|
func RetryKey(qname string) string {
|
|
|
|
return fmt.Sprintf("asynq:{%s}:retry", qname)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeadKey returns a redis key for the dead tasks.
|
|
|
|
func DeadKey(qname string) string {
|
|
|
|
return fmt.Sprintf("asynq:{%s}:dead", qname)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeadlinesKey returns a redis key for the deadlines.
|
|
|
|
func DeadlinesKey(qname string) string {
|
|
|
|
return fmt.Sprintf("asynq:{%s}:deadlines", qname)
|
|
|
|
}
|
|
|
|
|
|
|
|
// PausedKey returns a redis key to indicate that the given queue is paused.
|
|
|
|
func PausedKey(qname string) string {
|
|
|
|
return fmt.Sprintf("asynq:{%s}:paused", qname)
|
2020-01-06 22:53:40 +08:00
|
|
|
}
|
|
|
|
|
2020-08-06 22:34:00 +08:00
|
|
|
// 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("asynq:{%s}:processed:%s", qname, t.UTC().Format("2006-01-02"))
|
2019-12-23 21:33:48 +08:00
|
|
|
}
|
|
|
|
|
2020-08-06 22:34:00 +08:00
|
|
|
// 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("asynq:{%s}:failed:%s", qname, t.UTC().Format("2006-01-02"))
|
2019-12-23 21:33:48 +08:00
|
|
|
}
|
|
|
|
|
2020-04-13 07:42:11 +08:00
|
|
|
// ServerInfoKey returns a redis key for process info.
|
|
|
|
func ServerInfoKey(hostname string, pid int, sid string) string {
|
2020-08-06 22:34:00 +08:00
|
|
|
return fmt.Sprintf("asynq:servers:{%s:%d:%s}", hostname, pid, sid)
|
2020-01-31 22:48:58 +08:00
|
|
|
}
|
|
|
|
|
2020-04-13 07:42:11 +08:00
|
|
|
// WorkersKey returns a redis key for the workers given hostname, pid, and server ID.
|
|
|
|
func WorkersKey(hostname string, pid int, sid string) string {
|
2020-08-06 22:34:00 +08:00
|
|
|
return fmt.Sprintf("asynq:workers:{%s:%d:%s}", hostname, pid, sid)
|
2020-02-20 22:34:09 +08:00
|
|
|
}
|
|
|
|
|
2020-08-07 20:36:54 +08:00
|
|
|
// UniqueKey returns a redis key with the given type, payload, and queue name.
|
|
|
|
func UniqueKey(qname, tasktype string, payload map[string]interface{}) string {
|
|
|
|
return fmt.Sprintf("asynq:{%s}:unique:%s:%s", qname, tasktype, serializePayload(payload))
|
|
|
|
}
|
|
|
|
|
|
|
|
func serializePayload(payload map[string]interface{}) string {
|
|
|
|
if payload == nil {
|
|
|
|
return "nil"
|
|
|
|
}
|
|
|
|
type entry struct {
|
|
|
|
k string
|
|
|
|
v interface{}
|
|
|
|
}
|
|
|
|
var es []entry
|
|
|
|
for k, v := range payload {
|
|
|
|
es = append(es, entry{k, v})
|
|
|
|
}
|
|
|
|
// sort entries by key
|
|
|
|
sort.Slice(es, func(i, j int) bool { return es[i].k < es[j].k })
|
|
|
|
var b strings.Builder
|
|
|
|
for _, e := range es {
|
|
|
|
if b.Len() > 0 {
|
|
|
|
b.WriteString(",")
|
|
|
|
}
|
|
|
|
b.WriteString(fmt.Sprintf("%s=%v", e.k, e.v))
|
|
|
|
}
|
|
|
|
return b.String()
|
|
|
|
}
|
|
|
|
|
2019-12-22 23:15:45 +08:00
|
|
|
// TaskMessage is the internal representation of a task with additional metadata fields.
|
2019-12-28 12:37:15 +08:00
|
|
|
// Serialized data of this type gets written to redis.
|
2019-12-22 23:15:45 +08:00
|
|
|
type TaskMessage struct {
|
2019-12-28 12:37:15 +08:00
|
|
|
// Type indicates the kind of the task to be performed.
|
2019-12-22 23:15:45 +08:00
|
|
|
Type string
|
2019-12-28 12:37:15 +08:00
|
|
|
|
2019-12-22 23:15:45 +08:00
|
|
|
// Payload holds data needed to process the task.
|
|
|
|
Payload map[string]interface{}
|
|
|
|
|
2019-12-28 12:37:15 +08:00
|
|
|
// ID is a unique identifier for each task.
|
2020-07-02 21:21:20 +08:00
|
|
|
ID uuid.UUID
|
2019-12-28 12:37:15 +08:00
|
|
|
|
|
|
|
// Queue is a name this message should be enqueued to.
|
2019-12-22 23:15:45 +08:00
|
|
|
Queue string
|
2019-12-28 12:37:15 +08:00
|
|
|
|
2019-12-22 23:15:45 +08:00
|
|
|
// Retry is the max number of retry for this task.
|
|
|
|
Retry int
|
2019-12-28 12:37:15 +08:00
|
|
|
|
|
|
|
// Retried is the number of times we've retried this task so far.
|
2019-12-22 23:15:45 +08:00
|
|
|
Retried int
|
2019-12-28 12:37:15 +08:00
|
|
|
|
|
|
|
// ErrorMsg holds the error message from the last failure.
|
2019-12-22 23:15:45 +08:00
|
|
|
ErrorMsg string
|
2020-02-12 13:53:59 +08:00
|
|
|
|
2020-06-17 12:11:54 +08:00
|
|
|
// Timeout specifies timeout in seconds.
|
|
|
|
// If task processing doesn't complete within the timeout, the task will be retried
|
|
|
|
// if retry count is remaining. Otherwise it will be moved to the dead queue.
|
2020-02-12 13:53:59 +08:00
|
|
|
//
|
2020-06-17 12:11:54 +08:00
|
|
|
// Use zero to indicate no timeout.
|
2020-06-22 23:33:58 +08:00
|
|
|
Timeout int64
|
2020-03-08 12:24:03 +08:00
|
|
|
|
2020-06-17 12:11:54 +08:00
|
|
|
// Deadline specifies the deadline for the task in Unix time,
|
|
|
|
// the number of seconds elapsed since January 1, 1970 UTC.
|
|
|
|
// If task processing doesn't complete before the deadline, the task will be retried
|
|
|
|
// if retry count is remaining. Otherwise it will be moved to the dead queue.
|
2020-03-08 12:24:03 +08:00
|
|
|
//
|
2020-06-17 12:11:54 +08:00
|
|
|
// Use zero to indicate no deadline.
|
2020-06-22 23:33:58 +08:00
|
|
|
Deadline int64
|
2020-03-18 21:49:39 +08:00
|
|
|
|
|
|
|
// UniqueKey holds the redis key used for uniqueness lock for this task.
|
|
|
|
//
|
|
|
|
// Empty string indicates that no uniqueness lock was used.
|
|
|
|
UniqueKey string
|
2019-12-22 23:15:45 +08:00
|
|
|
}
|
2020-01-31 22:48:58 +08:00
|
|
|
|
2020-06-12 11:58:27 +08:00
|
|
|
// EncodeMessage marshals the given task message in JSON and returns an encoded string.
|
|
|
|
func EncodeMessage(msg *TaskMessage) (string, error) {
|
|
|
|
b, err := json.Marshal(msg)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return string(b), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DecodeMessage unmarshals the given encoded string and returns a decoded task message.
|
|
|
|
func DecodeMessage(s string) (*TaskMessage, error) {
|
|
|
|
d := json.NewDecoder(strings.NewReader(s))
|
|
|
|
d.UseNumber()
|
|
|
|
var msg TaskMessage
|
|
|
|
if err := d.Decode(&msg); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &msg, nil
|
|
|
|
}
|
|
|
|
|
2020-07-13 21:29:41 +08:00
|
|
|
// Z represents sorted set member.
|
|
|
|
type Z struct {
|
|
|
|
Message *TaskMessage
|
|
|
|
Score int64
|
|
|
|
}
|
|
|
|
|
2020-05-19 11:47:35 +08:00
|
|
|
// ServerStatus represents status of a server.
|
|
|
|
// ServerStatus methods are concurrency safe.
|
|
|
|
type ServerStatus struct {
|
|
|
|
mu sync.Mutex
|
|
|
|
val ServerStatusValue
|
2020-02-18 22:57:39 +08:00
|
|
|
}
|
|
|
|
|
2020-05-19 11:47:35 +08:00
|
|
|
// NewServerStatus returns a new status instance given an initial value.
|
|
|
|
func NewServerStatus(v ServerStatusValue) *ServerStatus {
|
|
|
|
return &ServerStatus{val: v}
|
|
|
|
}
|
|
|
|
|
|
|
|
type ServerStatusValue int
|
2020-02-18 22:57:39 +08:00
|
|
|
|
|
|
|
const (
|
2020-04-13 02:41:50 +08:00
|
|
|
// StatusIdle indicates the server is in idle state.
|
2020-05-19 11:47:35 +08:00
|
|
|
StatusIdle ServerStatusValue = iota
|
2020-02-18 22:57:39 +08:00
|
|
|
|
2020-04-13 02:41:50 +08:00
|
|
|
// StatusRunning indicates the servier is up and processing tasks.
|
2020-02-18 22:57:39 +08:00
|
|
|
StatusRunning
|
|
|
|
|
2020-04-13 02:41:50 +08:00
|
|
|
// StatusQuiet indicates the server is up but not processing new tasks.
|
|
|
|
StatusQuiet
|
|
|
|
|
|
|
|
// StatusStopped indicates the server server has been stopped.
|
2020-02-18 22:57:39 +08:00
|
|
|
StatusStopped
|
|
|
|
)
|
|
|
|
|
|
|
|
var statuses = []string{
|
|
|
|
"idle",
|
|
|
|
"running",
|
2020-04-13 02:41:50 +08:00
|
|
|
"quiet",
|
2020-02-18 22:57:39 +08:00
|
|
|
"stopped",
|
|
|
|
}
|
|
|
|
|
2020-05-19 11:47:35 +08:00
|
|
|
func (s *ServerStatus) String() string {
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
if StatusIdle <= s.val && s.val <= StatusStopped {
|
|
|
|
return statuses[s.val]
|
2020-02-18 22:57:39 +08:00
|
|
|
}
|
|
|
|
return "unknown status"
|
|
|
|
}
|
|
|
|
|
2020-05-19 11:47:35 +08:00
|
|
|
// Get returns the status value.
|
|
|
|
func (s *ServerStatus) Get() ServerStatusValue {
|
|
|
|
s.mu.Lock()
|
|
|
|
v := s.val
|
|
|
|
s.mu.Unlock()
|
|
|
|
return v
|
2020-02-20 23:44:13 +08:00
|
|
|
}
|
|
|
|
|
2020-05-19 11:47:35 +08:00
|
|
|
// Set sets the status value.
|
|
|
|
func (s *ServerStatus) Set(v ServerStatusValue) {
|
|
|
|
s.mu.Lock()
|
|
|
|
s.val = v
|
|
|
|
s.mu.Unlock()
|
2020-02-20 23:44:13 +08:00
|
|
|
}
|
|
|
|
|
2020-04-13 07:42:11 +08:00
|
|
|
// ServerInfo holds information about a running server.
|
|
|
|
type ServerInfo struct {
|
2020-02-20 23:44:13 +08:00
|
|
|
Host string
|
|
|
|
PID int
|
2020-04-13 07:42:11 +08:00
|
|
|
ServerID string
|
2020-02-02 14:22:48 +08:00
|
|
|
Concurrency int
|
2020-02-13 14:23:25 +08:00
|
|
|
Queues map[string]int
|
2020-02-02 14:22:48 +08:00
|
|
|
StrictPriority bool
|
2020-02-18 22:57:39 +08:00
|
|
|
Status string
|
2020-02-02 14:22:48 +08:00
|
|
|
Started time.Time
|
|
|
|
ActiveWorkerCount int
|
|
|
|
}
|
|
|
|
|
2020-02-20 23:44:13 +08:00
|
|
|
// WorkerInfo holds information about a running worker.
|
|
|
|
type WorkerInfo struct {
|
|
|
|
Host string
|
|
|
|
PID int
|
2020-05-19 11:47:35 +08:00
|
|
|
ID string
|
2020-02-20 23:44:13 +08:00
|
|
|
Type string
|
|
|
|
Queue string
|
|
|
|
Payload map[string]interface{}
|
|
|
|
Started time.Time
|
|
|
|
}
|
|
|
|
|
2020-02-13 09:33:41 +08:00
|
|
|
// Cancelations is a collection that holds cancel functions for all in-progress tasks.
|
2020-02-13 09:12:09 +08:00
|
|
|
//
|
2020-02-18 22:57:39 +08:00
|
|
|
// Cancelations are safe for concurrent use by multipel goroutines.
|
2020-02-13 09:12:09 +08:00
|
|
|
type Cancelations struct {
|
|
|
|
mu sync.Mutex
|
|
|
|
cancelFuncs map[string]context.CancelFunc
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewCancelations returns a Cancelations instance.
|
|
|
|
func NewCancelations() *Cancelations {
|
|
|
|
return &Cancelations{
|
|
|
|
cancelFuncs: make(map[string]context.CancelFunc),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-13 09:33:41 +08:00
|
|
|
// Add adds a new cancel func to the collection.
|
2020-02-13 09:12:09 +08:00
|
|
|
func (c *Cancelations) Add(id string, fn context.CancelFunc) {
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
c.cancelFuncs[id] = fn
|
|
|
|
}
|
|
|
|
|
2020-02-13 09:33:41 +08:00
|
|
|
// Delete deletes a cancel func from the collection given an id.
|
2020-02-13 09:12:09 +08:00
|
|
|
func (c *Cancelations) Delete(id string) {
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
delete(c.cancelFuncs, id)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get returns a cancel func given an id.
|
2020-02-20 23:44:13 +08:00
|
|
|
func (c *Cancelations) Get(id string) (fn context.CancelFunc, ok bool) {
|
2020-02-13 09:12:09 +08:00
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
2020-02-20 23:44:13 +08:00
|
|
|
fn, ok = c.cancelFuncs[id]
|
|
|
|
return fn, ok
|
2020-02-13 09:12:09 +08:00
|
|
|
}
|
|
|
|
|
2020-04-18 22:55:10 +08:00
|
|
|
// Broker is a message broker that supports operations to manage task queues.
|
|
|
|
//
|
|
|
|
// See rdb.RDB as a reference implementation.
|
|
|
|
type Broker interface {
|
2020-07-26 09:49:27 +08:00
|
|
|
Ping() error
|
2020-04-18 22:55:10 +08:00
|
|
|
Enqueue(msg *TaskMessage) error
|
|
|
|
EnqueueUnique(msg *TaskMessage, ttl time.Duration) error
|
2020-06-19 20:34:36 +08:00
|
|
|
Dequeue(qnames ...string) (*TaskMessage, time.Time, error)
|
2020-04-18 22:55:10 +08:00
|
|
|
Done(msg *TaskMessage) error
|
|
|
|
Requeue(msg *TaskMessage) error
|
|
|
|
Schedule(msg *TaskMessage, processAt time.Time) error
|
|
|
|
ScheduleUnique(msg *TaskMessage, processAt time.Time, ttl time.Duration) error
|
|
|
|
Retry(msg *TaskMessage, processAt time.Time, errMsg string) error
|
|
|
|
Kill(msg *TaskMessage, errMsg string) error
|
2020-06-08 04:04:27 +08:00
|
|
|
CheckAndEnqueue() error
|
2020-06-21 22:05:57 +08:00
|
|
|
ListDeadlineExceeded(deadline time.Time) ([]*TaskMessage, error)
|
2020-05-19 11:47:35 +08:00
|
|
|
WriteServerState(info *ServerInfo, workers []*WorkerInfo, ttl time.Duration) error
|
|
|
|
ClearServerState(host string, pid int, serverID string) error
|
2020-04-18 22:55:10 +08:00
|
|
|
CancelationPubSub() (*redis.PubSub, error) // TODO: Need to decouple from redis to support other brokers
|
|
|
|
PublishCancelation(id string) error
|
|
|
|
Close() error
|
|
|
|
}
|