2
0
mirror of https://github.com/hibiken/asynq.git synced 2024-11-14 19:38:49 +08:00
asynq/internal/base/base.go

592 lines
17 KiB
Go
Raw Normal View History

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
import (
"context"
2020-01-31 22:48:58 +08:00
"fmt"
2020-02-02 14:22:48 +08:00
"sync"
"time"
2020-04-18 22:55:10 +08:00
"github.com/go-redis/redis/v7"
"github.com/golang/protobuf/ptypes"
"github.com/google/uuid"
pb "github.com/hibiken/asynq/internal/proto"
"google.golang.org/protobuf/proto"
)
2019-12-22 23:15:45 +08:00
2020-06-30 04:56:53 +08:00
// Version of asynq library and CLI.
2021-06-06 21:51:30 +08:00
const Version = "0.17.2"
2020-06-30 04:56:53 +08:00
// DefaultQueueName is the queue name used if none are specified by user.
const DefaultQueueName = "default"
// DefaultQueue is the redis key for the default queue.
var DefaultQueue = PendingKey(DefaultQueueName)
// Global Redis keys.
2019-12-22 23:15:45 +08:00
const (
AllServers = "asynq:servers" // ZSET
AllWorkers = "asynq:workers" // ZSET
AllSchedulers = "asynq:schedulers" // ZSET
AllQueues = "asynq:queues" // SET
CancelChannel = "asynq:cancel" // PubSub channel
2019-12-22 23:15:45 +08:00
)
// ValidateQueueName validates a given qname to be used as a queue name.
// Returns nil if valid, otherwise returns non-nil error.
func ValidateQueueName(qname string) error {
if len(qname) == 0 {
return fmt.Errorf("queue name must contain one or more characters")
}
return nil
}
2021-04-28 22:27:35 +08:00
// QueueKeyPrefix returns a prefix for all keys in the given queue.
func QueueKeyPrefix(qname string) string {
return fmt.Sprintf("asynq:{%s}:", qname)
}
// TaskKeyPrefix returns a prefix for task key.
func TaskKeyPrefix(qname string) string {
2021-04-28 22:27:35 +08:00
return fmt.Sprintf("%st:", QueueKeyPrefix(qname))
}
// TaskKey returns a redis key for the given task message.
func TaskKey(qname, id string) string {
return fmt.Sprintf("%s%s", TaskKeyPrefix(qname), id)
}
// PendingKey returns a redis key for the given queue name.
func PendingKey(qname string) string {
2021-04-28 22:27:35 +08:00
return fmt.Sprintf("%spending", QueueKeyPrefix(qname))
}
2020-09-06 03:43:15 +08:00
// ActiveKey returns a redis key for the active tasks.
func ActiveKey(qname string) string {
2021-04-28 22:27:35 +08:00
return fmt.Sprintf("%sactive", QueueKeyPrefix(qname))
}
// ScheduledKey returns a redis key for the scheduled tasks.
func ScheduledKey(qname string) string {
2021-04-28 22:27:35 +08:00
return fmt.Sprintf("%sscheduled", QueueKeyPrefix(qname))
}
// RetryKey returns a redis key for the retry tasks.
func RetryKey(qname string) string {
2021-04-28 22:27:35 +08:00
return fmt.Sprintf("%sretry", QueueKeyPrefix(qname))
}
// ArchivedKey returns a redis key for the archived tasks.
func ArchivedKey(qname string) string {
2021-04-28 22:27:35 +08:00
return fmt.Sprintf("%sarchived", QueueKeyPrefix(qname))
}
// DeadlinesKey returns a redis key for the deadlines.
func DeadlinesKey(qname string) string {
2021-04-28 22:27:35 +08:00
return fmt.Sprintf("%sdeadlines", QueueKeyPrefix(qname))
}
// PausedKey returns a redis key to indicate that the given queue is paused.
func PausedKey(qname string) string {
2021-04-28 22:27:35 +08:00
return fmt.Sprintf("%spaused", QueueKeyPrefix(qname))
}
// ProcessedKey returns a redis key for processed count for the given day for the queue.
func ProcessedKey(qname string, t time.Time) string {
2021-04-28 22:27:35 +08:00
return fmt.Sprintf("%sprocessed:%s", QueueKeyPrefix(qname), t.UTC().Format("2006-01-02"))
}
// FailedKey returns a redis key for failure count for the given day for the queue.
func FailedKey(qname string, t time.Time) string {
2021-04-28 22:27:35 +08:00
return fmt.Sprintf("%sfailed:%s", QueueKeyPrefix(qname), t.UTC().Format("2006-01-02"))
}
// ServerInfoKey returns a redis key for process info.
func ServerInfoKey(hostname string, pid int, serverID string) string {
return fmt.Sprintf("asynq:servers:{%s:%d:%s}", hostname, pid, serverID)
2020-01-31 22:48:58 +08:00
}
// WorkersKey returns a redis key for the workers given hostname, pid, and server ID.
func WorkersKey(hostname string, pid int, serverID string) string {
return fmt.Sprintf("asynq:workers:{%s:%d:%s}", hostname, pid, serverID)
}
// SchedulerEntriesKey returns a redis key for the scheduler entries given scheduler ID.
func SchedulerEntriesKey(schedulerID string) string {
return fmt.Sprintf("asynq:schedulers:{%s}", schedulerID)
}
// SchedulerHistoryKey returns a redis key for the scheduler's history for the given entry.
func SchedulerHistoryKey(entryID string) string {
return fmt.Sprintf("asynq:scheduler_history:%s", entryID)
2020-02-20 22:34:09 +08:00
}
// UniqueKey returns a redis key with the given type, payload, and queue name.
2021-03-21 04:42:13 +08:00
func UniqueKey(qname, tasktype string, payload []byte) string {
2021-04-28 22:27:35 +08:00
return fmt.Sprintf("%sunique:%s:%s", QueueKeyPrefix(qname), tasktype, string(payload))
}
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.
2021-03-21 04:42:13 +08:00
Payload []byte
2019-12-22 23:15:45 +08:00
2019-12-28 12:37:15 +08:00
// ID is a unique identifier for each task.
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
// 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 archive.
2020-02-12 13:53:59 +08:00
//
// Use zero to indicate no timeout.
Timeout int64
// 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 archive.
//
// Use zero to indicate no deadline.
Deadline int64
// 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
// EncodeMessage marshals the given task message and returns an encoded bytes.
func EncodeMessage(msg *TaskMessage) ([]byte, error) {
if msg == nil {
return nil, fmt.Errorf("cannot encode nil message")
}
return proto.Marshal(&pb.TaskMessage{
Type: msg.Type,
2021-03-21 04:42:13 +08:00
Payload: msg.Payload,
Id: msg.ID.String(),
Queue: msg.Queue,
Retry: int32(msg.Retry),
Retried: int32(msg.Retried),
ErrorMsg: msg.ErrorMsg,
Timeout: msg.Timeout,
Deadline: msg.Deadline,
UniqueKey: msg.UniqueKey,
})
}
// DecodeMessage unmarshals the given bytes and returns a decoded task message.
func DecodeMessage(data []byte) (*TaskMessage, error) {
var pbmsg pb.TaskMessage
if err := proto.Unmarshal(data, &pbmsg); err != nil {
return nil, err
}
return &TaskMessage{
Type: pbmsg.GetType(),
2021-03-21 04:42:13 +08:00
Payload: pbmsg.GetPayload(),
ID: uuid.MustParse(pbmsg.GetId()),
Queue: pbmsg.GetQueue(),
Retry: int(pbmsg.GetRetry()),
Retried: int(pbmsg.GetRetried()),
ErrorMsg: pbmsg.GetErrorMsg(),
Timeout: pbmsg.GetTimeout(),
Deadline: pbmsg.GetDeadline(),
UniqueKey: pbmsg.GetUniqueKey(),
}, nil
2020-06-12 11:58:27 +08:00
}
2020-07-13 21:29:41 +08:00
// Z represents sorted set member.
type Z struct {
Message *TaskMessage
Score int64
}
// ServerState represents state of a server.
// ServerState methods are concurrency safe.
type ServerState struct {
2020-05-19 11:47:35 +08:00
mu sync.Mutex
val ServerStateValue
2020-02-18 22:57:39 +08:00
}
// NewServerState returns a new state instance.
// Initial state is set to StateNew.
func NewServerState() *ServerState {
return &ServerState{val: StateNew}
2020-05-19 11:47:35 +08:00
}
type ServerStateValue int
2020-02-18 22:57:39 +08:00
const (
// StateNew represents a new server. Server begins in
// this state and then transition to StatusActive when
// Start or Run is callled.
StateNew ServerStateValue = iota
2020-02-18 22:57:39 +08:00
// StateActive indicates the server is up and active.
StateActive
2020-02-18 22:57:39 +08:00
// StateStopped indicates the server is up but no longer processing new tasks.
StateStopped
2020-04-13 02:41:50 +08:00
// StateClosed indicates the server has been shutdown.
StateClosed
2020-02-18 22:57:39 +08:00
)
var serverStates = []string{
"new",
"active",
2020-02-18 22:57:39 +08:00
"stopped",
"closed",
2020-02-18 22:57:39 +08:00
}
func (s *ServerState) String() string {
2020-05-19 11:47:35 +08:00
s.mu.Lock()
defer s.mu.Unlock()
if StateNew <= s.val && s.val <= StateClosed {
return serverStates[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 *ServerState) Get() ServerStateValue {
2020-05-19 11:47:35 +08:00
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 *ServerState) Set(v ServerStateValue) {
2020-05-19 11:47:35 +08:00
s.mu.Lock()
s.val = v
s.mu.Unlock()
2020-02-20 23:44:13 +08:00
}
// ServerInfo holds information about a running server.
type ServerInfo struct {
2020-02-20 23:44:13 +08:00
Host string
PID int
ServerID string
2020-02-02 14:22:48 +08:00
Concurrency int
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
}
// EncodeServerInfo marshals the given ServerInfo and returns the encoded bytes.
func EncodeServerInfo(info *ServerInfo) ([]byte, error) {
if info == nil {
return nil, fmt.Errorf("cannot encode nil server info")
}
queues := make(map[string]int32)
for q, p := range info.Queues {
queues[q] = int32(p)
}
started, err := ptypes.TimestampProto(info.Started)
if err != nil {
return nil, err
}
return proto.Marshal(&pb.ServerInfo{
Host: info.Host,
Pid: int32(info.PID),
ServerId: info.ServerID,
Concurrency: int32(info.Concurrency),
Queues: queues,
StrictPriority: info.StrictPriority,
Status: info.Status,
StartTime: started,
ActiveWorkerCount: int32(info.ActiveWorkerCount),
})
}
// DecodeServerInfo decodes the given bytes into ServerInfo.
func DecodeServerInfo(b []byte) (*ServerInfo, error) {
var pbmsg pb.ServerInfo
if err := proto.Unmarshal(b, &pbmsg); err != nil {
return nil, err
}
queues := make(map[string]int)
for q, p := range pbmsg.GetQueues() {
queues[q] = int(p)
}
startTime, err := ptypes.Timestamp(pbmsg.GetStartTime())
if err != nil {
return nil, err
}
return &ServerInfo{
Host: pbmsg.GetHost(),
PID: int(pbmsg.GetPid()),
ServerID: pbmsg.GetServerId(),
Concurrency: int(pbmsg.GetConcurrency()),
Queues: queues,
StrictPriority: pbmsg.GetStrictPriority(),
Status: pbmsg.GetStatus(),
Started: startTime,
ActiveWorkerCount: int(pbmsg.GetActiveWorkerCount()),
}, nil
}
2020-02-20 23:44:13 +08:00
// WorkerInfo holds information about a running worker.
type WorkerInfo struct {
Host string
PID int
ServerID string
ID string
Type string
2021-03-21 04:42:13 +08:00
Payload []byte
Queue string
Started time.Time
2021-01-28 07:55:43 +08:00
Deadline time.Time
2020-02-20 23:44:13 +08:00
}
// EncodeWorkerInfo marshals the given WorkerInfo and returns the encoded bytes.
func EncodeWorkerInfo(info *WorkerInfo) ([]byte, error) {
if info == nil {
return nil, fmt.Errorf("cannot encode nil worker info")
}
startTime, err := ptypes.TimestampProto(info.Started)
if err != nil {
return nil, err
}
deadline, err := ptypes.TimestampProto(info.Deadline)
if err != nil {
return nil, err
}
return proto.Marshal(&pb.WorkerInfo{
Host: info.Host,
Pid: int32(info.PID),
ServerId: info.ServerID,
TaskId: info.ID,
TaskType: info.Type,
2021-03-21 04:42:13 +08:00
TaskPayload: info.Payload,
Queue: info.Queue,
StartTime: startTime,
Deadline: deadline,
})
}
// DecodeWorkerInfo decodes the given bytes into WorkerInfo.
func DecodeWorkerInfo(b []byte) (*WorkerInfo, error) {
var pbmsg pb.WorkerInfo
if err := proto.Unmarshal(b, &pbmsg); err != nil {
return nil, err
}
startTime, err := ptypes.Timestamp(pbmsg.GetStartTime())
if err != nil {
return nil, err
}
deadline, err := ptypes.Timestamp(pbmsg.GetDeadline())
if err != nil {
return nil, err
}
return &WorkerInfo{
Host: pbmsg.GetHost(),
PID: int(pbmsg.GetPid()),
ServerID: pbmsg.GetServerId(),
ID: pbmsg.GetTaskId(),
Type: pbmsg.GetTaskType(),
2021-03-21 04:42:13 +08:00
Payload: pbmsg.GetTaskPayload(),
Queue: pbmsg.GetQueue(),
Started: startTime,
Deadline: deadline,
}, nil
}
// SchedulerEntry holds information about a periodic task registered with a scheduler.
type SchedulerEntry struct {
// Identifier of this entry.
ID string
// Spec describes the schedule of this entry.
Spec string
// Type is the task type of the periodic task.
Type string
// Payload is the payload of the periodic task.
2021-03-21 04:42:13 +08:00
Payload []byte
// Opts is the options for the periodic task.
Opts []string
// Next shows the next time the task will be enqueued.
Next time.Time
// Prev shows the last time the task was enqueued.
// Zero time if task was never enqueued.
Prev time.Time
}
// EncodeSchedulerEntry marshals the given entry and returns an encoded bytes.
func EncodeSchedulerEntry(entry *SchedulerEntry) ([]byte, error) {
if entry == nil {
return nil, fmt.Errorf("cannot encode nil scheduler entry")
}
next, err := ptypes.TimestampProto(entry.Next)
if err != nil {
return nil, err
}
prev, err := ptypes.TimestampProto(entry.Prev)
if err != nil {
return nil, err
}
return proto.Marshal(&pb.SchedulerEntry{
Id: entry.ID,
Spec: entry.Spec,
TaskType: entry.Type,
2021-03-21 04:42:13 +08:00
TaskPayload: entry.Payload,
EnqueueOptions: entry.Opts,
NextEnqueueTime: next,
PrevEnqueueTime: prev,
})
}
// DecodeSchedulerEntry unmarshals the given bytes and returns a decoded SchedulerEntry.
func DecodeSchedulerEntry(b []byte) (*SchedulerEntry, error) {
var pbmsg pb.SchedulerEntry
if err := proto.Unmarshal(b, &pbmsg); err != nil {
return nil, err
}
next, err := ptypes.Timestamp(pbmsg.GetNextEnqueueTime())
if err != nil {
return nil, err
}
prev, err := ptypes.Timestamp(pbmsg.GetPrevEnqueueTime())
if err != nil {
return nil, err
}
return &SchedulerEntry{
ID: pbmsg.GetId(),
Spec: pbmsg.GetSpec(),
Type: pbmsg.GetTaskType(),
2021-03-21 04:42:13 +08:00
Payload: pbmsg.GetTaskPayload(),
Opts: pbmsg.GetEnqueueOptions(),
Next: next,
Prev: prev,
}, nil
}
// SchedulerEnqueueEvent holds information about an enqueue event by a scheduler.
type SchedulerEnqueueEvent struct {
// ID of the task that was enqueued.
TaskID string
// Time the task was enqueued.
EnqueuedAt time.Time
}
// EncodeSchedulerEnqueueEvent marshals the given event
// and returns an encoded bytes.
func EncodeSchedulerEnqueueEvent(event *SchedulerEnqueueEvent) ([]byte, error) {
if event == nil {
return nil, fmt.Errorf("cannot encode nil enqueue event")
}
enqueuedAt, err := ptypes.TimestampProto(event.EnqueuedAt)
if err != nil {
return nil, err
}
return proto.Marshal(&pb.SchedulerEnqueueEvent{
TaskId: event.TaskID,
EnqueueTime: enqueuedAt,
})
}
// DecodeSchedulerEnqueueEvent unmarshals the given bytes
// and returns a decoded SchedulerEnqueueEvent.
func DecodeSchedulerEnqueueEvent(b []byte) (*SchedulerEnqueueEvent, error) {
var pbmsg pb.SchedulerEnqueueEvent
if err := proto.Unmarshal(b, &pbmsg); err != nil {
return nil, err
}
enqueuedAt, err := ptypes.Timestamp(pbmsg.GetEnqueueTime())
if err != nil {
return nil, err
}
return &SchedulerEnqueueEvent{
TaskID: pbmsg.GetTaskId(),
EnqueuedAt: enqueuedAt,
}, nil
}
2020-09-06 03:43:15 +08:00
// Cancelations is a collection that holds cancel functions for all active tasks.
//
2020-02-18 22:57:39 +08:00
// Cancelations are safe for concurrent use by multipel goroutines.
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.
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.
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) {
c.mu.Lock()
defer c.mu.Unlock()
2020-02-20 23:44:13 +08:00
fn, ok = c.cancelFuncs[id]
return fn, ok
}
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 {
Ping() error
2020-04-18 22:55:10 +08:00
Enqueue(msg *TaskMessage) error
EnqueueUnique(msg *TaskMessage, ttl time.Duration) error
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
Archive(msg *TaskMessage, errMsg string) error
ForwardIfReady(qnames ...string) error
2020-08-18 11:44:41 +08:00
ListDeadlineExceeded(deadline time.Time, qnames ...string) ([]*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
}