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-01-31 22:48:58 +08:00
|
|
|
"fmt"
|
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"
|
|
|
|
|
|
|
|
"github.com/rs/xid"
|
|
|
|
)
|
2019-12-22 23:15:45 +08:00
|
|
|
|
2020-01-06 22:53:40 +08:00
|
|
|
// DefaultQueueName is the queue name used if none are specified by user.
|
|
|
|
const DefaultQueueName = "default"
|
|
|
|
|
2019-12-22 23:15:45 +08:00
|
|
|
// Redis keys
|
|
|
|
const (
|
2020-02-02 14:22:48 +08:00
|
|
|
AllProcesses = "asynq:ps" // ZSET
|
2020-02-20 22:34:09 +08:00
|
|
|
psPrefix = "asynq:ps:" // STRING - asynq:ps:<host>:<pid>
|
2020-02-21 23:18:22 +08:00
|
|
|
AllWorkers = "asynq:workers" // ZSET
|
2020-02-20 22:34:09 +08:00
|
|
|
workersPrefix = "asynq:workers:" // HASH - asynq:workers:<host:<pid>
|
2020-01-06 22:53:40 +08:00
|
|
|
processedPrefix = "asynq:processed:" // STRING - asynq:processed:<yyyy-mm-dd>
|
|
|
|
failurePrefix = "asynq:failure:" // STRING - asynq:failure:<yyyy-mm-dd>
|
2020-01-07 13:27:51 +08:00
|
|
|
QueuePrefix = "asynq:queues:" // LIST - asynq:queues:<qname>
|
2020-01-09 22:21:43 +08:00
|
|
|
AllQueues = "asynq:queues" // SET
|
2020-01-07 13:27:51 +08:00
|
|
|
DefaultQueue = QueuePrefix + DefaultQueueName // LIST
|
2020-01-06 22:53:40 +08:00
|
|
|
ScheduledQueue = "asynq:scheduled" // ZSET
|
|
|
|
RetryQueue = "asynq:retry" // ZSET
|
|
|
|
DeadQueue = "asynq:dead" // ZSET
|
|
|
|
InProgressQueue = "asynq:in_progress" // LIST
|
2020-02-13 09:12:09 +08:00
|
|
|
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-01-11 13:32:15 +08:00
|
|
|
return QueuePrefix + strings.ToLower(qname)
|
2020-01-06 22:53:40 +08:00
|
|
|
}
|
|
|
|
|
2020-02-20 22:34:09 +08:00
|
|
|
// ProcessedKey returns a redis key for processed count for the given day.
|
2019-12-23 21:33:48 +08:00
|
|
|
func ProcessedKey(t time.Time) string {
|
|
|
|
return processedPrefix + t.UTC().Format("2006-01-02")
|
|
|
|
}
|
|
|
|
|
2020-02-20 22:34:09 +08:00
|
|
|
// FailureKey returns a redis key for failure count for the given day.
|
2019-12-23 21:33:48 +08:00
|
|
|
func FailureKey(t time.Time) string {
|
|
|
|
return failurePrefix + t.UTC().Format("2006-01-02")
|
|
|
|
}
|
|
|
|
|
2020-02-20 22:34:09 +08:00
|
|
|
// ProcessInfoKey returns a redis key for process info.
|
2020-02-02 14:22:48 +08:00
|
|
|
func ProcessInfoKey(hostname string, pid int) string {
|
2020-01-31 22:48:58 +08:00
|
|
|
return fmt.Sprintf("%s%s:%d", psPrefix, hostname, pid)
|
|
|
|
}
|
|
|
|
|
2020-02-20 22:34:09 +08:00
|
|
|
// WorkersKey returns a redis key for the workers given hostname and pid.
|
|
|
|
func WorkersKey(hostname string, pid int) string {
|
|
|
|
return fmt.Sprintf("%s%s:%d", workersPrefix, hostname, pid)
|
|
|
|
}
|
|
|
|
|
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.
|
2019-12-22 23:15:45 +08:00
|
|
|
ID xid.ID
|
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
|
|
|
|
|
|
|
// Timeout specifies how long a task may run.
|
|
|
|
// The string value should be compatible with time.Duration.ParseDuration.
|
|
|
|
//
|
|
|
|
// Zero means no limit.
|
|
|
|
Timeout string
|
2020-03-08 12:24:03 +08:00
|
|
|
|
|
|
|
// Deadline specifies the deadline for the task.
|
|
|
|
// Task won't be processed if it exceeded its deadline.
|
|
|
|
// The string shoulbe be in RFC3339 format.
|
|
|
|
//
|
|
|
|
// time.Time's zero value means no deadline.
|
|
|
|
Deadline string
|
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-02-18 22:57:39 +08:00
|
|
|
// ProcessState holds process level information.
|
|
|
|
//
|
|
|
|
// ProcessStates are safe for concurrent use by multiple goroutines.
|
|
|
|
type ProcessState struct {
|
2020-02-20 23:44:13 +08:00
|
|
|
mu sync.Mutex // guards all data fields
|
|
|
|
concurrency int
|
|
|
|
queues map[string]int
|
|
|
|
strictPriority bool
|
|
|
|
pid int
|
|
|
|
host string
|
|
|
|
status PStatus
|
|
|
|
started time.Time
|
|
|
|
workers map[string]*workerStats
|
2020-02-18 22:57:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// PStatus represents status of a process.
|
|
|
|
type PStatus int
|
|
|
|
|
|
|
|
const (
|
|
|
|
// StatusIdle indicates process is in idle state.
|
|
|
|
StatusIdle PStatus = iota
|
|
|
|
|
|
|
|
// StatusRunning indicates process is up and processing tasks.
|
|
|
|
StatusRunning
|
|
|
|
|
|
|
|
// StatusStopped indicates process is up but not processing new tasks.
|
|
|
|
StatusStopped
|
|
|
|
)
|
|
|
|
|
|
|
|
var statuses = []string{
|
|
|
|
"idle",
|
|
|
|
"running",
|
|
|
|
"stopped",
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s PStatus) String() string {
|
|
|
|
if StatusIdle <= s && s <= StatusStopped {
|
|
|
|
return statuses[s]
|
|
|
|
}
|
|
|
|
return "unknown status"
|
|
|
|
}
|
|
|
|
|
2020-02-20 23:44:13 +08:00
|
|
|
type workerStats struct {
|
|
|
|
msg *TaskMessage
|
|
|
|
started time.Time
|
|
|
|
}
|
|
|
|
|
2020-02-18 22:57:39 +08:00
|
|
|
// NewProcessState returns a new instance of ProcessState.
|
|
|
|
func NewProcessState(host string, pid, concurrency int, queues map[string]int, strict bool) *ProcessState {
|
|
|
|
return &ProcessState{
|
|
|
|
host: host,
|
|
|
|
pid: pid,
|
|
|
|
concurrency: concurrency,
|
|
|
|
queues: cloneQueueConfig(queues),
|
|
|
|
strictPriority: strict,
|
|
|
|
status: StatusIdle,
|
2020-02-20 23:44:13 +08:00
|
|
|
workers: make(map[string]*workerStats),
|
2020-02-18 22:57:39 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetStatus updates the state of process.
|
|
|
|
func (ps *ProcessState) SetStatus(status PStatus) {
|
|
|
|
ps.mu.Lock()
|
|
|
|
defer ps.mu.Unlock()
|
|
|
|
ps.status = status
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetStarted records when the process started processing.
|
|
|
|
func (ps *ProcessState) SetStarted(t time.Time) {
|
|
|
|
ps.mu.Lock()
|
|
|
|
defer ps.mu.Unlock()
|
|
|
|
ps.started = t
|
|
|
|
}
|
|
|
|
|
2020-02-20 23:44:13 +08:00
|
|
|
// AddWorkerStats records when a worker started and which task it's processing.
|
|
|
|
func (ps *ProcessState) AddWorkerStats(msg *TaskMessage, started time.Time) {
|
|
|
|
ps.mu.Lock()
|
|
|
|
defer ps.mu.Unlock()
|
|
|
|
ps.workers[msg.ID.String()] = &workerStats{msg, started}
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteWorkerStats removes a worker's entry from the process state.
|
|
|
|
func (ps *ProcessState) DeleteWorkerStats(msg *TaskMessage) {
|
2020-02-18 22:57:39 +08:00
|
|
|
ps.mu.Lock()
|
|
|
|
defer ps.mu.Unlock()
|
2020-02-20 23:44:13 +08:00
|
|
|
delete(ps.workers, msg.ID.String())
|
2020-02-18 22:57:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get returns current state of process as a ProcessInfo.
|
|
|
|
func (ps *ProcessState) Get() *ProcessInfo {
|
|
|
|
ps.mu.Lock()
|
|
|
|
defer ps.mu.Unlock()
|
|
|
|
return &ProcessInfo{
|
|
|
|
Host: ps.host,
|
|
|
|
PID: ps.pid,
|
|
|
|
Concurrency: ps.concurrency,
|
|
|
|
Queues: cloneQueueConfig(ps.queues),
|
|
|
|
StrictPriority: ps.strictPriority,
|
|
|
|
Status: ps.status.String(),
|
|
|
|
Started: ps.started,
|
2020-02-20 23:44:13 +08:00
|
|
|
ActiveWorkerCount: len(ps.workers),
|
2020-02-18 22:57:39 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-20 23:44:13 +08:00
|
|
|
// GetWorkers returns a list of currently running workers' info.
|
|
|
|
func (ps *ProcessState) GetWorkers() []*WorkerInfo {
|
|
|
|
ps.mu.Lock()
|
|
|
|
defer ps.mu.Unlock()
|
|
|
|
var res []*WorkerInfo
|
|
|
|
for _, w := range ps.workers {
|
|
|
|
res = append(res, &WorkerInfo{
|
|
|
|
Host: ps.host,
|
|
|
|
PID: ps.pid,
|
|
|
|
ID: w.msg.ID,
|
|
|
|
Type: w.msg.Type,
|
|
|
|
Queue: w.msg.Queue,
|
|
|
|
Payload: clonePayload(w.msg.Payload),
|
|
|
|
Started: w.started,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2020-02-18 22:57:39 +08:00
|
|
|
func cloneQueueConfig(qcfg map[string]int) map[string]int {
|
|
|
|
res := make(map[string]int)
|
|
|
|
for qname, n := range qcfg {
|
|
|
|
res[qname] = n
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2020-02-20 23:44:13 +08:00
|
|
|
func clonePayload(payload map[string]interface{}) map[string]interface{} {
|
|
|
|
res := make(map[string]interface{})
|
|
|
|
for k, v := range payload {
|
|
|
|
res[k] = v
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
// ProcessInfo holds information about a running background worker process.
|
2020-02-02 14:22:48 +08:00
|
|
|
type ProcessInfo struct {
|
2020-02-20 23:44:13 +08:00
|
|
|
Host string
|
|
|
|
PID int
|
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
|
|
|
|
ID xid.ID
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
// GetAll returns all cancel funcs.
|
|
|
|
func (c *Cancelations) GetAll() []context.CancelFunc {
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
var res []context.CancelFunc
|
|
|
|
for _, fn := range c.cancelFuncs {
|
|
|
|
res = append(res, fn)
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|