github.com/wfusion/gofusion@v1.1.14/common/infra/asynq/pkg/base/base.go (about)

     1  // Copyright 2020 Kentaro Hibino. All rights reserved.
     2  // Use of this source code is governed by a MIT license
     3  // that can be found in the LICENSE file.
     4  
     5  // Package base defines foundational types and constants used in asynq package.
     6  package base
     7  
     8  import (
     9  	"context"
    10  	"crypto/md5"
    11  	"encoding/hex"
    12  	"fmt"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/redis/go-redis/v9"
    18  	"google.golang.org/protobuf/proto"
    19  	"google.golang.org/protobuf/types/known/timestamppb"
    20  
    21  	"github.com/wfusion/gofusion/common/infra/asynq/pkg/errors"
    22  	"github.com/wfusion/gofusion/common/infra/asynq/pkg/timeutil"
    23  
    24  	pb "github.com/wfusion/gofusion/common/infra/asynq/pkg/proto"
    25  )
    26  
    27  // Version of asynq library and CLI.
    28  const Version = "0.24.1"
    29  
    30  // DefaultQueueName is the queue name used if none are specified by user.
    31  const DefaultQueueName = "default"
    32  
    33  // DefaultQueue is the redis key for the default queue.
    34  var DefaultQueue = PendingKey(DefaultQueueName)
    35  
    36  // Global Redis keys.
    37  const (
    38  	AllServers    = "asynq:servers"    // ZSET
    39  	AllWorkers    = "asynq:workers"    // ZSET
    40  	AllSchedulers = "asynq:schedulers" // ZSET
    41  	AllQueues     = "asynq:queues"     // SET
    42  	CancelChannel = "asynq:cancel"     // PubSub channel
    43  )
    44  
    45  // TaskState denotes the state of a task.
    46  type TaskState int
    47  
    48  const (
    49  	TaskStateActive TaskState = iota + 1
    50  	TaskStatePending
    51  	TaskStateScheduled
    52  	TaskStateRetry
    53  	TaskStateArchived
    54  	TaskStateCompleted
    55  	TaskStateAggregating // describes a state where task is waiting in a group to be aggregated
    56  )
    57  
    58  func (s TaskState) String() string {
    59  	switch s {
    60  	case TaskStateActive:
    61  		return "active"
    62  	case TaskStatePending:
    63  		return "pending"
    64  	case TaskStateScheduled:
    65  		return "scheduled"
    66  	case TaskStateRetry:
    67  		return "retry"
    68  	case TaskStateArchived:
    69  		return "archived"
    70  	case TaskStateCompleted:
    71  		return "completed"
    72  	case TaskStateAggregating:
    73  		return "aggregating"
    74  	}
    75  	panic(fmt.Sprintf("internal error: unknown task state %d", s))
    76  }
    77  
    78  func TaskStateFromString(s string) (TaskState, error) {
    79  	switch s {
    80  	case "active":
    81  		return TaskStateActive, nil
    82  	case "pending":
    83  		return TaskStatePending, nil
    84  	case "scheduled":
    85  		return TaskStateScheduled, nil
    86  	case "retry":
    87  		return TaskStateRetry, nil
    88  	case "archived":
    89  		return TaskStateArchived, nil
    90  	case "completed":
    91  		return TaskStateCompleted, nil
    92  	case "aggregating":
    93  		return TaskStateAggregating, nil
    94  	}
    95  	return 0, errors.E(errors.FailedPrecondition, fmt.Sprintf("%q is not supported task state", s))
    96  }
    97  
    98  // ValidateQueueName validates a given qname to be used as a queue name.
    99  // Returns nil if valid, otherwise returns non-nil error.
   100  func ValidateQueueName(qname string) error {
   101  	if len(strings.TrimSpace(qname)) == 0 {
   102  		return fmt.Errorf("queue name must contain one or more characters")
   103  	}
   104  	return nil
   105  }
   106  
   107  // QueueKeyPrefix returns a prefix for all keys in the given queue.
   108  func QueueKeyPrefix(qname string) string {
   109  	return fmt.Sprintf("asynq:{%s}:", qname)
   110  }
   111  
   112  // TaskKeyPrefix returns a prefix for task key.
   113  func TaskKeyPrefix(qname string) string {
   114  	return fmt.Sprintf("%st:", QueueKeyPrefix(qname))
   115  }
   116  
   117  // TaskKey returns a redis key for the given task message.
   118  func TaskKey(qname, id string) string {
   119  	return fmt.Sprintf("%s%s", TaskKeyPrefix(qname), id)
   120  }
   121  
   122  // PendingKey returns a redis key for the given queue name.
   123  func PendingKey(qname string) string {
   124  	return fmt.Sprintf("%spending", QueueKeyPrefix(qname))
   125  }
   126  
   127  // ActiveKey returns a redis key for the active tasks.
   128  func ActiveKey(qname string) string {
   129  	return fmt.Sprintf("%sactive", QueueKeyPrefix(qname))
   130  }
   131  
   132  // ScheduledKey returns a redis key for the scheduled tasks.
   133  func ScheduledKey(qname string) string {
   134  	return fmt.Sprintf("%sscheduled", QueueKeyPrefix(qname))
   135  }
   136  
   137  // RetryKey returns a redis key for the retry tasks.
   138  func RetryKey(qname string) string {
   139  	return fmt.Sprintf("%sretry", QueueKeyPrefix(qname))
   140  }
   141  
   142  // ArchivedKey returns a redis key for the archived tasks.
   143  func ArchivedKey(qname string) string {
   144  	return fmt.Sprintf("%sarchived", QueueKeyPrefix(qname))
   145  }
   146  
   147  // LeaseKey returns a redis key for the lease.
   148  func LeaseKey(qname string) string {
   149  	return fmt.Sprintf("%slease", QueueKeyPrefix(qname))
   150  }
   151  
   152  func CompletedKey(qname string) string {
   153  	return fmt.Sprintf("%scompleted", QueueKeyPrefix(qname))
   154  }
   155  
   156  // PausedKey returns a redis key to indicate that the given queue is paused.
   157  func PausedKey(qname string) string {
   158  	return fmt.Sprintf("%spaused", QueueKeyPrefix(qname))
   159  }
   160  
   161  // ProcessedTotalKey returns a redis key for total processed count for the given queue.
   162  func ProcessedTotalKey(qname string) string {
   163  	return fmt.Sprintf("%sprocessed", QueueKeyPrefix(qname))
   164  }
   165  
   166  // FailedTotalKey returns a redis key for total failure count for the given queue.
   167  func FailedTotalKey(qname string) string {
   168  	return fmt.Sprintf("%sfailed", QueueKeyPrefix(qname))
   169  }
   170  
   171  // ProcessedKey returns a redis key for processed count for the given day for the queue.
   172  func ProcessedKey(qname string, t time.Time) string {
   173  	return fmt.Sprintf("%sprocessed:%s", QueueKeyPrefix(qname), t.UTC().Format("2006-01-02"))
   174  }
   175  
   176  // FailedKey returns a redis key for failure count for the given day for the queue.
   177  func FailedKey(qname string, t time.Time) string {
   178  	return fmt.Sprintf("%sfailed:%s", QueueKeyPrefix(qname), t.UTC().Format("2006-01-02"))
   179  }
   180  
   181  // ServerInfoKey returns a redis key for process info.
   182  func ServerInfoKey(hostname string, pid int, serverID string) string {
   183  	return fmt.Sprintf("asynq:servers:{%s:%d:%s}", hostname, pid, serverID)
   184  }
   185  
   186  // WorkersKey returns a redis key for the workers given hostname, pid, and server ID.
   187  func WorkersKey(hostname string, pid int, serverID string) string {
   188  	return fmt.Sprintf("asynq:workers:{%s:%d:%s}", hostname, pid, serverID)
   189  }
   190  
   191  // SchedulerEntriesKey returns a redis key for the scheduler entries given scheduler ID.
   192  func SchedulerEntriesKey(schedulerID string) string {
   193  	return fmt.Sprintf("asynq:schedulers:{%s}", schedulerID)
   194  }
   195  
   196  // SchedulerHistoryKey returns a redis key for the scheduler's history for the given entry.
   197  func SchedulerHistoryKey(entryID string) string {
   198  	return fmt.Sprintf("asynq:scheduler_history:%s", entryID)
   199  }
   200  
   201  // UniqueKey returns a redis key with the given type, payload, and queue name.
   202  func UniqueKey(qname, tasktype string, payload []byte) string {
   203  	if payload == nil {
   204  		return fmt.Sprintf("%sunique:%s:", QueueKeyPrefix(qname), tasktype)
   205  	}
   206  	checksum := md5.Sum(payload)
   207  	return fmt.Sprintf("%sunique:%s:%s", QueueKeyPrefix(qname), tasktype, hex.EncodeToString(checksum[:]))
   208  }
   209  
   210  // GroupKeyPrefix returns a prefix for group key.
   211  func GroupKeyPrefix(qname string) string {
   212  	return fmt.Sprintf("%sg:", QueueKeyPrefix(qname))
   213  }
   214  
   215  // GroupKey returns a redis key used to group tasks belong in the same group.
   216  func GroupKey(qname, gkey string) string {
   217  	return fmt.Sprintf("%s%s", GroupKeyPrefix(qname), gkey)
   218  }
   219  
   220  // AggregationSetKey returns a redis key used for an aggregation set.
   221  func AggregationSetKey(qname, gname, setID string) string {
   222  	return fmt.Sprintf("%s:%s", GroupKey(qname, gname), setID)
   223  }
   224  
   225  // AllGroups return a redis key used to store all group keys used in a given queue.
   226  func AllGroups(qname string) string {
   227  	return fmt.Sprintf("%sgroups", QueueKeyPrefix(qname))
   228  }
   229  
   230  // AllAggregationSets returns a redis key used to store all aggregation sets (set of tasks staged to be aggregated)
   231  // in a given queue.
   232  func AllAggregationSets(qname string) string {
   233  	return fmt.Sprintf("%saggregation_sets", QueueKeyPrefix(qname))
   234  }
   235  
   236  // TaskMessage is the internal representation of a task with additional metadata fields.
   237  // Serialized data of this type gets written to redis.
   238  type TaskMessage struct {
   239  	// Type indicates the kind of the task to be performed.
   240  	Type string
   241  
   242  	// Payload holds data needed to process the task.
   243  	Payload []byte
   244  
   245  	// ID is a unique identifier for each task.
   246  	ID string
   247  
   248  	// Queue is a name this message should be enqueued to.
   249  	Queue string
   250  
   251  	// Retry is the max number of retry for this task.
   252  	Retry int
   253  
   254  	// Retried is the number of times we've retried this task so far.
   255  	Retried int
   256  
   257  	// ErrorMsg holds the error message from the last failure.
   258  	ErrorMsg string
   259  
   260  	// Time of last failure in Unix time,
   261  	// the number of seconds elapsed since January 1, 1970 UTC.
   262  	//
   263  	// Use zero to indicate no last failure
   264  	LastFailedAt int64
   265  
   266  	// Timeout specifies timeout in seconds.
   267  	// If task processing doesn't complete within the timeout, the task will be retried
   268  	// if retry count is remaining. Otherwise it will be moved to the archive.
   269  	//
   270  	// Use zero to indicate no timeout.
   271  	Timeout int64
   272  
   273  	// Deadline specifies the deadline for the task in Unix time,
   274  	// the number of seconds elapsed since January 1, 1970 UTC.
   275  	// If task processing doesn't complete before the deadline, the task will be retried
   276  	// if retry count is remaining. Otherwise it will be moved to the archive.
   277  	//
   278  	// Use zero to indicate no deadline.
   279  	Deadline int64
   280  
   281  	// UniqueKey holds the redis key used for uniqueness lock for this task.
   282  	//
   283  	// Empty string indicates that no uniqueness lock was used.
   284  	UniqueKey string
   285  
   286  	// GroupKey holds the group key used for task aggregation.
   287  	//
   288  	// Empty string indicates no aggregation is used for this task.
   289  	GroupKey string
   290  
   291  	// Retention specifies the number of seconds the task should be retained after completion.
   292  	Retention int64
   293  
   294  	// CompletedAt is the time the task was processed successfully in Unix time,
   295  	// the number of seconds elapsed since January 1, 1970 UTC.
   296  	//
   297  	// Use zero to indicate no value.
   298  	CompletedAt int64
   299  }
   300  
   301  // EncodeMessage marshals the given task message and returns an encoded bytes.
   302  func EncodeMessage(msg *TaskMessage) ([]byte, error) {
   303  	if msg == nil {
   304  		return nil, fmt.Errorf("cannot encode nil message")
   305  	}
   306  	return proto.Marshal(&pb.TaskMessage{
   307  		Type:         msg.Type,
   308  		Payload:      msg.Payload,
   309  		Id:           msg.ID,
   310  		Queue:        msg.Queue,
   311  		Retry:        int32(msg.Retry),
   312  		Retried:      int32(msg.Retried),
   313  		ErrorMsg:     msg.ErrorMsg,
   314  		LastFailedAt: msg.LastFailedAt,
   315  		Timeout:      msg.Timeout,
   316  		Deadline:     msg.Deadline,
   317  		UniqueKey:    msg.UniqueKey,
   318  		GroupKey:     msg.GroupKey,
   319  		Retention:    msg.Retention,
   320  		CompletedAt:  msg.CompletedAt,
   321  	})
   322  }
   323  
   324  // DecodeMessage unmarshals the given bytes and returns a decoded task message.
   325  func DecodeMessage(data []byte) (*TaskMessage, error) {
   326  	var pbmsg pb.TaskMessage
   327  	if err := proto.Unmarshal(data, &pbmsg); err != nil {
   328  		return nil, err
   329  	}
   330  	return &TaskMessage{
   331  		Type:         pbmsg.GetType(),
   332  		Payload:      pbmsg.GetPayload(),
   333  		ID:           pbmsg.GetId(),
   334  		Queue:        pbmsg.GetQueue(),
   335  		Retry:        int(pbmsg.GetRetry()),
   336  		Retried:      int(pbmsg.GetRetried()),
   337  		ErrorMsg:     pbmsg.GetErrorMsg(),
   338  		LastFailedAt: pbmsg.GetLastFailedAt(),
   339  		Timeout:      pbmsg.GetTimeout(),
   340  		Deadline:     pbmsg.GetDeadline(),
   341  		UniqueKey:    pbmsg.GetUniqueKey(),
   342  		GroupKey:     pbmsg.GetGroupKey(),
   343  		Retention:    pbmsg.GetRetention(),
   344  		CompletedAt:  pbmsg.GetCompletedAt(),
   345  	}, nil
   346  }
   347  
   348  // TaskInfo describes a task message and its metadata.
   349  type TaskInfo struct {
   350  	Message       *TaskMessage
   351  	State         TaskState
   352  	NextProcessAt time.Time
   353  	Result        []byte
   354  }
   355  
   356  // Z represents sorted set member.
   357  type Z struct {
   358  	Message *TaskMessage
   359  	Score   int64
   360  }
   361  
   362  // ServerInfo holds information about a running server.
   363  type ServerInfo struct {
   364  	Host              string
   365  	PID               int
   366  	ServerID          string
   367  	Concurrency       int
   368  	Queues            map[string]int
   369  	StrictPriority    bool
   370  	Status            string
   371  	Started           time.Time
   372  	ActiveWorkerCount int
   373  }
   374  
   375  // EncodeServerInfo marshals the given ServerInfo and returns the encoded bytes.
   376  func EncodeServerInfo(info *ServerInfo) ([]byte, error) {
   377  	if info == nil {
   378  		return nil, fmt.Errorf("cannot encode nil server info")
   379  	}
   380  	queues := make(map[string]int32)
   381  	for q, p := range info.Queues {
   382  		queues[q] = int32(p)
   383  	}
   384  	started := timestamppb.New(info.Started)
   385  	if err := started.CheckValid(); err != nil {
   386  		return nil, err
   387  	}
   388  	return proto.Marshal(&pb.ServerInfo{
   389  		Host:              info.Host,
   390  		Pid:               int32(info.PID),
   391  		ServerId:          info.ServerID,
   392  		Concurrency:       int32(info.Concurrency),
   393  		Queues:            queues,
   394  		StrictPriority:    info.StrictPriority,
   395  		Status:            info.Status,
   396  		StartTime:         started,
   397  		ActiveWorkerCount: int32(info.ActiveWorkerCount),
   398  	})
   399  }
   400  
   401  // DecodeServerInfo decodes the given bytes into ServerInfo.
   402  func DecodeServerInfo(b []byte) (*ServerInfo, error) {
   403  	var pbmsg pb.ServerInfo
   404  	if err := proto.Unmarshal(b, &pbmsg); err != nil {
   405  		return nil, err
   406  	}
   407  	queues := make(map[string]int)
   408  	for q, p := range pbmsg.GetQueues() {
   409  		queues[q] = int(p)
   410  	}
   411  	startTime := pbmsg.GetStartTime().AsTime()
   412  	if err := pbmsg.GetStartTime().CheckValid(); err != nil {
   413  		return nil, err
   414  	}
   415  	return &ServerInfo{
   416  		Host:              pbmsg.GetHost(),
   417  		PID:               int(pbmsg.GetPid()),
   418  		ServerID:          pbmsg.GetServerId(),
   419  		Concurrency:       int(pbmsg.GetConcurrency()),
   420  		Queues:            queues,
   421  		StrictPriority:    pbmsg.GetStrictPriority(),
   422  		Status:            pbmsg.GetStatus(),
   423  		Started:           startTime,
   424  		ActiveWorkerCount: int(pbmsg.GetActiveWorkerCount()),
   425  	}, nil
   426  }
   427  
   428  // WorkerInfo holds information about a running worker.
   429  type WorkerInfo struct {
   430  	Host     string
   431  	PID      int
   432  	ServerID string
   433  	ID       string
   434  	Type     string
   435  	Payload  []byte
   436  	Queue    string
   437  	Started  time.Time
   438  	Deadline time.Time
   439  }
   440  
   441  // EncodeWorkerInfo marshals the given WorkerInfo and returns the encoded bytes.
   442  func EncodeWorkerInfo(info *WorkerInfo) ([]byte, error) {
   443  	if info == nil {
   444  		return nil, fmt.Errorf("cannot encode nil worker info")
   445  	}
   446  	startTime := timestamppb.New(info.Started)
   447  	if err := startTime.CheckValid(); err != nil {
   448  		return nil, err
   449  	}
   450  	deadline := timestamppb.New(info.Deadline)
   451  	if err := deadline.CheckValid(); err != nil {
   452  		return nil, err
   453  	}
   454  	return proto.Marshal(&pb.WorkerInfo{
   455  		Host:        info.Host,
   456  		Pid:         int32(info.PID),
   457  		ServerId:    info.ServerID,
   458  		TaskId:      info.ID,
   459  		TaskType:    info.Type,
   460  		TaskPayload: info.Payload,
   461  		Queue:       info.Queue,
   462  		StartTime:   startTime,
   463  		Deadline:    deadline,
   464  	})
   465  }
   466  
   467  // DecodeWorkerInfo decodes the given bytes into WorkerInfo.
   468  func DecodeWorkerInfo(b []byte) (*WorkerInfo, error) {
   469  	var pbmsg pb.WorkerInfo
   470  	if err := proto.Unmarshal(b, &pbmsg); err != nil {
   471  		return nil, err
   472  	}
   473  	startTime := pbmsg.GetDeadline().AsTime()
   474  	if err := pbmsg.GetStartTime().CheckValid(); err != nil {
   475  		return nil, err
   476  	}
   477  
   478  	deadline := pbmsg.GetDeadline().AsTime()
   479  	if err := pbmsg.GetDeadline().CheckValid(); err != nil {
   480  		return nil, err
   481  	}
   482  	return &WorkerInfo{
   483  		Host:     pbmsg.GetHost(),
   484  		PID:      int(pbmsg.GetPid()),
   485  		ServerID: pbmsg.GetServerId(),
   486  		ID:       pbmsg.GetTaskId(),
   487  		Type:     pbmsg.GetTaskType(),
   488  		Payload:  pbmsg.GetTaskPayload(),
   489  		Queue:    pbmsg.GetQueue(),
   490  		Started:  startTime,
   491  		Deadline: deadline,
   492  	}, nil
   493  }
   494  
   495  // SchedulerEntry holds information about a periodic task registered with a scheduler.
   496  type SchedulerEntry struct {
   497  	// Identifier of this entry.
   498  	ID string
   499  
   500  	// Spec describes the schedule of this entry.
   501  	Spec string
   502  
   503  	// Type is the task type of the periodic task.
   504  	Type string
   505  
   506  	// Payload is the payload of the periodic task.
   507  	Payload []byte
   508  
   509  	// Opts is the options for the periodic task.
   510  	Opts []string
   511  
   512  	// Next shows the next time the task will be enqueued.
   513  	Next time.Time
   514  
   515  	// Prev shows the last time the task was enqueued.
   516  	// Zero time if task was never enqueued.
   517  	Prev time.Time
   518  }
   519  
   520  // EncodeSchedulerEntry marshals the given entry and returns an encoded bytes.
   521  func EncodeSchedulerEntry(entry *SchedulerEntry) ([]byte, error) {
   522  	if entry == nil {
   523  		return nil, fmt.Errorf("cannot encode nil scheduler entry")
   524  	}
   525  	next := timestamppb.New(entry.Next)
   526  	if err := next.CheckValid(); err != nil {
   527  		return nil, err
   528  	}
   529  	prev := timestamppb.New(entry.Prev)
   530  	if err := prev.CheckValid(); err != nil {
   531  		return nil, err
   532  	}
   533  	return proto.Marshal(&pb.SchedulerEntry{
   534  		Id:              entry.ID,
   535  		Spec:            entry.Spec,
   536  		TaskType:        entry.Type,
   537  		TaskPayload:     entry.Payload,
   538  		EnqueueOptions:  entry.Opts,
   539  		NextEnqueueTime: next,
   540  		PrevEnqueueTime: prev,
   541  	})
   542  }
   543  
   544  // DecodeSchedulerEntry unmarshals the given bytes and returns a decoded SchedulerEntry.
   545  func DecodeSchedulerEntry(b []byte) (*SchedulerEntry, error) {
   546  	var pbmsg pb.SchedulerEntry
   547  	if err := proto.Unmarshal(b, &pbmsg); err != nil {
   548  		return nil, err
   549  	}
   550  	next := pbmsg.GetNextEnqueueTime().AsTime()
   551  	if err := pbmsg.GetNextEnqueueTime().CheckValid(); err != nil {
   552  		return nil, err
   553  	}
   554  	prev := pbmsg.GetPrevEnqueueTime().AsTime()
   555  	if err := pbmsg.GetPrevEnqueueTime().CheckValid(); err != nil {
   556  		return nil, err
   557  	}
   558  	return &SchedulerEntry{
   559  		ID:      pbmsg.GetId(),
   560  		Spec:    pbmsg.GetSpec(),
   561  		Type:    pbmsg.GetTaskType(),
   562  		Payload: pbmsg.GetTaskPayload(),
   563  		Opts:    pbmsg.GetEnqueueOptions(),
   564  		Next:    next,
   565  		Prev:    prev,
   566  	}, nil
   567  }
   568  
   569  // SchedulerEnqueueEvent holds information about an enqueue event by a scheduler.
   570  type SchedulerEnqueueEvent struct {
   571  	// ID of the task that was enqueued.
   572  	TaskID string
   573  
   574  	// Time the task was enqueued.
   575  	EnqueuedAt time.Time
   576  }
   577  
   578  // EncodeSchedulerEnqueueEvent marshals the given event
   579  // and returns an encoded bytes.
   580  func EncodeSchedulerEnqueueEvent(event *SchedulerEnqueueEvent) ([]byte, error) {
   581  	if event == nil {
   582  		return nil, fmt.Errorf("cannot encode nil enqueue event")
   583  	}
   584  	enqueuedAt := timestamppb.New(event.EnqueuedAt)
   585  	if err := enqueuedAt.CheckValid(); err != nil {
   586  		return nil, err
   587  	}
   588  	return proto.Marshal(&pb.SchedulerEnqueueEvent{
   589  		TaskId:      event.TaskID,
   590  		EnqueueTime: enqueuedAt,
   591  	})
   592  }
   593  
   594  // DecodeSchedulerEnqueueEvent unmarshals the given bytes
   595  // and returns a decoded SchedulerEnqueueEvent.
   596  func DecodeSchedulerEnqueueEvent(b []byte) (*SchedulerEnqueueEvent, error) {
   597  	var pbmsg pb.SchedulerEnqueueEvent
   598  	if err := proto.Unmarshal(b, &pbmsg); err != nil {
   599  		return nil, err
   600  	}
   601  	enqueuedAt := pbmsg.GetEnqueueTime().AsTime()
   602  	if err := pbmsg.GetEnqueueTime().CheckValid(); err != nil {
   603  		return nil, err
   604  	}
   605  	return &SchedulerEnqueueEvent{
   606  		TaskID:     pbmsg.GetTaskId(),
   607  		EnqueuedAt: enqueuedAt,
   608  	}, nil
   609  }
   610  
   611  // Cancelations is a collection that holds cancel functions for all active tasks.
   612  //
   613  // Cancelations are safe for concurrent use by multiple goroutines.
   614  type Cancelations struct {
   615  	mu          sync.Mutex
   616  	cancelFuncs map[string]context.CancelFunc
   617  }
   618  
   619  // NewCancelations returns a Cancelations instance.
   620  func NewCancelations() *Cancelations {
   621  	return &Cancelations{
   622  		cancelFuncs: make(map[string]context.CancelFunc),
   623  	}
   624  }
   625  
   626  // Add adds a new cancel func to the collection.
   627  func (c *Cancelations) Add(id string, fn context.CancelFunc) {
   628  	c.mu.Lock()
   629  	defer c.mu.Unlock()
   630  	c.cancelFuncs[id] = fn
   631  }
   632  
   633  // Delete deletes a cancel func from the collection given an id.
   634  func (c *Cancelations) Delete(id string) {
   635  	c.mu.Lock()
   636  	defer c.mu.Unlock()
   637  	delete(c.cancelFuncs, id)
   638  }
   639  
   640  // Get returns a cancel func given an id.
   641  func (c *Cancelations) Get(id string) (fn context.CancelFunc, ok bool) {
   642  	c.mu.Lock()
   643  	defer c.mu.Unlock()
   644  	fn, ok = c.cancelFuncs[id]
   645  	return fn, ok
   646  }
   647  
   648  // Lease is a time bound lease for worker to process task.
   649  // It provides a communication channel between lessor and lessee about lease expiration.
   650  type Lease struct {
   651  	once sync.Once
   652  	ch   chan struct{}
   653  
   654  	Clock timeutil.Clock
   655  
   656  	mu       sync.Mutex
   657  	expireAt time.Time // guarded by mu
   658  }
   659  
   660  func NewLease(expirationTime time.Time) *Lease {
   661  	return &Lease{
   662  		ch:       make(chan struct{}),
   663  		expireAt: expirationTime,
   664  		Clock:    timeutil.NewRealClock(),
   665  	}
   666  }
   667  
   668  // Reset changes the lease to expire at the given time.
   669  // It returns true if the lease is still valid and reset operation was successful, false if the lease had been expired.
   670  func (l *Lease) Reset(expirationTime time.Time) bool {
   671  	if !l.IsValid() {
   672  		return false
   673  	}
   674  	l.mu.Lock()
   675  	defer l.mu.Unlock()
   676  	l.expireAt = expirationTime
   677  	return true
   678  }
   679  
   680  // NotifyExpiration
   681  // Sends a notification to lessee about expired lease.
   682  // Returns true if notification was sent, returns false if the lease is still valid and notification was not sent.
   683  func (l *Lease) NotifyExpiration() bool {
   684  	if l.IsValid() {
   685  		return false
   686  	}
   687  	l.once.Do(l.closeCh)
   688  	return true
   689  }
   690  
   691  func (l *Lease) closeCh() {
   692  	close(l.ch)
   693  }
   694  
   695  // Done returns a communication channel from which the lessee can read to get notified
   696  // when lessor notifies about lease expiration.
   697  func (l *Lease) Done() <-chan struct{} {
   698  	return l.ch
   699  }
   700  
   701  // Deadline returns the expiration time of the lease.
   702  func (l *Lease) Deadline() time.Time {
   703  	l.mu.Lock()
   704  	defer l.mu.Unlock()
   705  	return l.expireAt
   706  }
   707  
   708  // IsValid returns true if the lease's expiration time is in the future or equals to the current time,
   709  // returns false otherwise.
   710  func (l *Lease) IsValid() bool {
   711  	now := l.Clock.Now()
   712  	l.mu.Lock()
   713  	defer l.mu.Unlock()
   714  	return l.expireAt.After(now) || l.expireAt.Equal(now)
   715  }
   716  
   717  // Broker is a message broker that supports operations to manage task queues.
   718  //
   719  // See rdb.RDB as a reference implementation.
   720  //nolint: revive // interface too long issue
   721  type Broker interface {
   722  	Ping() error
   723  	Close() error
   724  	Enqueue(ctx context.Context, msg *TaskMessage) error
   725  	EnqueueUnique(ctx context.Context, msg *TaskMessage, ttl time.Duration) error
   726  	Dequeue(qnames ...string) (*TaskMessage, time.Time, error)
   727  	Done(ctx context.Context, msg *TaskMessage) error
   728  	MarkAsComplete(ctx context.Context, msg *TaskMessage) error
   729  	Requeue(ctx context.Context, msg *TaskMessage) error
   730  	Schedule(ctx context.Context, msg *TaskMessage, processAt time.Time) error
   731  	ScheduleUnique(ctx context.Context, msg *TaskMessage, processAt time.Time, ttl time.Duration) error
   732  	Retry(ctx context.Context, msg *TaskMessage, processAt time.Time, errMsg string, isFailure bool) error
   733  	Archive(ctx context.Context, msg *TaskMessage, errMsg string) error
   734  	ForwardIfReady(qnames ...string) error
   735  
   736  	// Group aggregation related methods
   737  	AddToGroup(ctx context.Context, msg *TaskMessage, gname string) error
   738  	AddToGroupUnique(ctx context.Context, msg *TaskMessage, uniqueKey, groupKey string, ttl time.Duration) error
   739  	ListGroups(qname string) ([]string, error)
   740  	AggregationCheck(qname, gname string, t time.Time, gracePeriod, maxDelay time.Duration, maxSize int) (aggregationSetID string, err error)
   741  	ReadAggregationSet(qname, gname, aggregationSetID string) ([]*TaskMessage, time.Time, error)
   742  	DeleteAggregationSet(ctx context.Context, qname, gname, aggregationSetID string) error
   743  	ReclaimStaleAggregationSets(qname string) error
   744  
   745  	// Task retention related method
   746  	DeleteExpiredCompletedTasks(qname string) error
   747  
   748  	// Lease related methods
   749  	ListLeaseExpired(cutoff time.Time, qnames ...string) ([]*TaskMessage, error)
   750  	ExtendLease(qname string, ids ...string) (time.Time, error)
   751  
   752  	// State snapshot related methods
   753  	WriteServerState(info *ServerInfo, workers []*WorkerInfo, ttl time.Duration) error
   754  	ClearServerState(host string, pid int, serverID string) error
   755  
   756  	// Cancelation related methods
   757  	CancelationPubSub() (*redis.PubSub, error) // TODO: Need to decouple from redis to support other brokers
   758  	PublishCancelation(id string) error
   759  
   760  	WriteResult(qname, id string, data []byte) (n int, err error)
   761  }