2
0
mirror of https://github.com/hibiken/asynq.git synced 2024-11-10 11:31:58 +08:00
asynq/background.go

165 lines
4.3 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-11-24 07:22:43 +08:00
package asynq
import (
2019-11-27 22:33:04 +08:00
"fmt"
"log"
"math"
"math/rand"
2019-11-24 07:44:42 +08:00
"os"
"os/signal"
2019-11-24 07:22:43 +08:00
"sync"
"syscall"
2019-11-24 07:22:43 +08:00
"time"
2019-12-04 13:01:26 +08:00
"github.com/go-redis/redis/v7"
2019-12-04 13:01:26 +08:00
"github.com/hibiken/asynq/internal/rdb"
2019-11-24 07:22:43 +08:00
)
2019-12-07 14:00:09 +08:00
// Background is responsible for managing the background-task processing.
//
// Background manages background queues to process tasks and retry if
// necessary. If the processing of a task is unsuccessful, background will
// schedule it for a retry with an exponential backoff until either the task
// gets processed successfully or it exhausts its max retry count.
//
// Once a task exhausts its retries, it will be moved to the "dead" queue and
// will be kept in the queue for some time until a certain condition is met
// (e.g., queue size reaches a certain limit, or the task has been in the
// queue for a certain amount of time).
2019-11-24 07:22:43 +08:00
type Background struct {
mu sync.Mutex
running bool
2019-11-24 07:22:43 +08:00
2019-12-04 13:01:26 +08:00
rdb *rdb.RDB
2019-12-29 05:33:24 +08:00
scheduler *scheduler
2019-11-24 07:22:43 +08:00
processor *processor
}
// Config specifies the background-task processing behavior.
type Config struct {
2019-12-30 23:10:13 +08:00
// Maximum number of concurrent workers to process tasks.
//
// If set to zero or negative value, NewBackground will overwrite the value to one.
Concurrency int
// Function to calculate retry delay for a failed task.
//
// By default, it uses exponential backoff algorithm to calculate the delay.
//
// n is the number of times the task has been retried.
// e is the error returned by the task handler.
// t is the task in question. t is read-only, the function should not mutate t.
RetryDelayFunc func(n int, e error, t *Task) time.Duration
}
// Formula taken from https://github.com/mperham/sidekiq.
func defaultDelayFunc(n int, e error, t *Task) time.Duration {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
s := int(math.Pow(float64(n), 4)) + 15 + (r.Intn(30) * (n + 1))
return time.Duration(s) * time.Second
}
// NewBackground returns a new Background instance given a redis client
// and background processing configuration.
func NewBackground(r *redis.Client, cfg *Config) *Background {
n := cfg.Concurrency
if n < 1 {
n = 1
}
delayFunc := cfg.RetryDelayFunc
if delayFunc == nil {
delayFunc = defaultDelayFunc
}
rdb := rdb.NewRDB(r)
scheduler := newScheduler(rdb, 5*time.Second)
processor := newProcessor(rdb, n, delayFunc)
2019-11-24 07:22:43 +08:00
return &Background{
rdb: rdb,
2019-12-29 05:33:24 +08:00
scheduler: scheduler,
2019-11-24 07:22:43 +08:00
processor: processor,
}
}
// A Handler processes a task.
//
// ProcessTask should return nil if the processing of a task
// is successful.
//
// If ProcessTask return a non-nil error or panics, the task
// will be retried after delay.
//
// Note: The argument task is ready only, ProcessTask should
// not mutate the task.
type Handler interface {
ProcessTask(*Task) error
}
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as a Handler. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(*Task) error
// ProcessTask calls fn(task)
func (fn HandlerFunc) ProcessTask(task *Task) error {
return fn(task)
}
2019-11-24 07:22:43 +08:00
2019-11-24 07:44:42 +08:00
// Run starts the background-task processing and blocks until
// an os signal to exit the program is received. Once it receives
// a signal, it gracefully shuts down all pending workers and other
// goroutines to process the tasks.
func (bg *Background) Run(handler Handler) {
2019-11-24 07:44:42 +08:00
bg.start(handler)
defer bg.stop()
// Wait for a signal to terminate.
2019-11-24 07:44:42 +08:00
sigs := make(chan os.Signal, 1)
2019-12-17 21:32:31 +08:00
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT, syscall.SIGTSTP)
for {
sig := <-sigs
if sig == syscall.SIGTSTP {
bg.processor.stop()
2019-12-17 21:32:31 +08:00
continue
}
break
}
fmt.Println()
log.Println("[INFO] Starting graceful shutdown...")
2019-11-24 07:44:42 +08:00
}
// starts the background-task processing.
func (bg *Background) start(handler Handler) {
2019-11-24 07:22:43 +08:00
bg.mu.Lock()
defer bg.mu.Unlock()
if bg.running {
return
}
2019-11-24 07:22:43 +08:00
bg.running = true
bg.processor.handler = handler
2019-12-29 05:33:24 +08:00
bg.scheduler.start()
2019-11-24 07:22:43 +08:00
bg.processor.start()
}
2019-11-24 07:44:42 +08:00
// stops the background-task processing.
func (bg *Background) stop() {
2019-11-24 07:22:43 +08:00
bg.mu.Lock()
defer bg.mu.Unlock()
if !bg.running {
return
}
2019-12-29 05:33:24 +08:00
bg.scheduler.terminate()
2019-11-24 07:22:43 +08:00
bg.processor.terminate()
2019-12-04 13:01:26 +08:00
bg.rdb.Close()
bg.processor.handler = nil
bg.running = false
2019-11-24 07:22:43 +08:00
}