2024-01-19 20:46:37 +00:00
|
|
|
package cron
|
|
|
|
|
|
|
|
import (
|
2024-01-28 07:20:59 +00:00
|
|
|
"context"
|
2024-02-02 00:18:06 +00:00
|
|
|
"errors"
|
2024-02-25 06:58:26 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2024-01-28 21:23:38 +00:00
|
|
|
"github.com/google/uuid"
|
2024-01-28 07:20:59 +00:00
|
|
|
"go.uber.org/fx"
|
2024-01-19 20:46:37 +00:00
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
|
|
"github.com/go-co-op/gocron/v2"
|
|
|
|
)
|
|
|
|
|
2024-02-02 00:18:06 +00:00
|
|
|
var (
|
|
|
|
ErrRetryLimitReached = errors.New("Retry limit reached")
|
|
|
|
)
|
|
|
|
|
2024-01-28 07:20:59 +00:00
|
|
|
type CronService interface {
|
|
|
|
Scheduler() gocron.Scheduler
|
|
|
|
RegisterService(service CronableService)
|
|
|
|
}
|
|
|
|
|
|
|
|
type CronableService interface {
|
|
|
|
LoadInitialTasks(cron CronService) error
|
|
|
|
}
|
|
|
|
|
|
|
|
type CronServiceParams struct {
|
|
|
|
fx.In
|
|
|
|
Logger *zap.Logger
|
|
|
|
Scheduler gocron.Scheduler
|
|
|
|
}
|
|
|
|
|
|
|
|
var Module = fx.Module("cron",
|
|
|
|
fx.Options(
|
|
|
|
fx.Provide(NewCronService),
|
2024-01-28 08:54:33 +00:00
|
|
|
fx.Provide(gocron.NewScheduler),
|
2024-01-28 07:20:59 +00:00
|
|
|
),
|
2024-01-19 20:46:37 +00:00
|
|
|
)
|
|
|
|
|
2024-02-01 01:29:27 +00:00
|
|
|
type CronServiceDefault struct {
|
2024-01-19 20:46:37 +00:00
|
|
|
scheduler gocron.Scheduler
|
2024-01-28 07:20:59 +00:00
|
|
|
services []CronableService
|
|
|
|
logger *zap.Logger
|
2024-01-19 20:46:37 +00:00
|
|
|
}
|
|
|
|
|
2024-02-25 12:47:43 +00:00
|
|
|
type RetryableJobParams struct {
|
2024-01-28 21:23:38 +00:00
|
|
|
Name string
|
2024-01-28 21:32:20 +00:00
|
|
|
Tags []string
|
2024-01-28 21:23:38 +00:00
|
|
|
Function any
|
|
|
|
Args []any
|
|
|
|
Attempt uint
|
|
|
|
Limit uint
|
|
|
|
After func(jobID uuid.UUID, jobName string)
|
|
|
|
Error func(jobID uuid.UUID, jobName string, err error)
|
|
|
|
}
|
|
|
|
|
|
|
|
type CronJob struct {
|
2024-01-28 21:39:04 +00:00
|
|
|
JobId uuid.UUID
|
2024-01-28 21:23:38 +00:00
|
|
|
Job gocron.JobDefinition
|
|
|
|
Task gocron.Task
|
|
|
|
Options []gocron.JobOption
|
|
|
|
}
|
|
|
|
|
2024-02-01 01:29:27 +00:00
|
|
|
func (c *CronServiceDefault) Scheduler() gocron.Scheduler {
|
2024-01-19 20:46:37 +00:00
|
|
|
return c.scheduler
|
|
|
|
}
|
|
|
|
|
2024-02-01 01:29:27 +00:00
|
|
|
func NewCronService(lc fx.Lifecycle, params CronServiceParams) *CronServiceDefault {
|
|
|
|
sc := &CronServiceDefault{
|
2024-01-28 07:20:59 +00:00
|
|
|
logger: params.Logger,
|
|
|
|
scheduler: params.Scheduler,
|
2024-01-20 17:05:41 +00:00
|
|
|
}
|
2024-01-19 20:46:37 +00:00
|
|
|
|
2024-01-28 07:20:59 +00:00
|
|
|
lc.Append(fx.Hook{
|
|
|
|
OnStart: func(ctx context.Context) error {
|
|
|
|
return sc.start()
|
|
|
|
},
|
|
|
|
})
|
2024-01-19 20:46:37 +00:00
|
|
|
|
2024-01-28 07:20:59 +00:00
|
|
|
return sc
|
2024-01-19 20:46:37 +00:00
|
|
|
}
|
|
|
|
|
2024-02-01 01:29:27 +00:00
|
|
|
func (c *CronServiceDefault) start() error {
|
2024-01-19 20:46:37 +00:00
|
|
|
for _, service := range c.services {
|
|
|
|
err := service.LoadInitialTasks(c)
|
|
|
|
if err != nil {
|
2024-01-28 07:20:59 +00:00
|
|
|
c.logger.Fatal("Failed to load initial tasks for service", zap.Error(err))
|
2024-01-19 20:46:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-20 17:18:43 +00:00
|
|
|
go c.scheduler.Start()
|
2024-01-20 16:48:10 +00:00
|
|
|
|
2024-01-19 20:46:37 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-02-01 01:29:27 +00:00
|
|
|
func (c *CronServiceDefault) RegisterService(service CronableService) {
|
2024-01-19 20:46:37 +00:00
|
|
|
c.services = append(c.services, service)
|
|
|
|
}
|
2024-01-28 21:23:38 +00:00
|
|
|
|
2024-02-25 12:47:43 +00:00
|
|
|
func (c *CronServiceDefault) RetryableJob(params RetryableJobParams) CronJob {
|
2024-01-28 21:23:38 +00:00
|
|
|
job := gocron.OneTimeJob(gocron.OneTimeJobStartImmediately())
|
|
|
|
|
|
|
|
if params.Attempt > 0 {
|
|
|
|
job = gocron.OneTimeJob(gocron.OneTimeJobStartDateTime(time.Now().Add(time.Duration(params.Attempt) * time.Minute)))
|
|
|
|
}
|
|
|
|
|
|
|
|
task := gocron.NewTask(params.Function, params.Args...)
|
|
|
|
|
2024-02-01 23:51:05 +00:00
|
|
|
if params.After == nil {
|
|
|
|
params.After = func(jobID uuid.UUID, jobName string) {}
|
2024-01-28 21:23:38 +00:00
|
|
|
}
|
|
|
|
|
2024-02-01 23:51:05 +00:00
|
|
|
if params.Error == nil {
|
|
|
|
params.Error = func(jobID uuid.UUID, jobName string, err error) {}
|
2024-01-28 21:23:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
listeners := gocron.WithEventListeners(gocron.AfterJobRunsWithError(func(jobID uuid.UUID, jobName string, err error) {
|
|
|
|
params.Error(jobID, jobName, err)
|
|
|
|
|
2024-02-02 02:03:19 +00:00
|
|
|
if params.Attempt >= params.Limit && params.Limit > 0 {
|
2024-01-28 21:23:38 +00:00
|
|
|
c.logger.Error("Retryable task limit reached", zap.String("jobName", jobName), zap.String("jobID", jobID.String()))
|
2024-02-02 00:18:06 +00:00
|
|
|
params.Error(jobID, jobName, ErrRetryLimitReached)
|
2024-01-28 21:23:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
taskRetry := params
|
|
|
|
taskRetry.Attempt++
|
|
|
|
|
2024-02-25 12:47:43 +00:00
|
|
|
retryTask := c.RetryableJob(taskRetry)
|
2024-01-28 21:39:04 +00:00
|
|
|
retryTask.JobId = jobID
|
2024-01-28 21:23:38 +00:00
|
|
|
|
2024-01-28 21:39:04 +00:00
|
|
|
_, err = c.RerunJob(retryTask)
|
2024-01-28 21:23:38 +00:00
|
|
|
if err != nil {
|
|
|
|
c.logger.Error("Failed to create retry job", zap.Error(err))
|
|
|
|
}
|
|
|
|
}), gocron.AfterJobRuns(params.After))
|
|
|
|
|
|
|
|
name := gocron.WithName(params.Name)
|
2024-01-28 21:32:20 +00:00
|
|
|
options := []gocron.JobOption{listeners, name}
|
|
|
|
|
|
|
|
if len(params.Tags) > 0 {
|
|
|
|
options = append(options, gocron.WithTags(params.Tags...))
|
|
|
|
}
|
2024-01-28 21:23:38 +00:00
|
|
|
|
|
|
|
return CronJob{
|
|
|
|
Job: job,
|
|
|
|
Task: task,
|
2024-01-28 21:32:20 +00:00
|
|
|
Options: options,
|
2024-01-28 21:23:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-01 01:29:27 +00:00
|
|
|
func (c *CronServiceDefault) CreateJob(job CronJob) (gocron.Job, error) {
|
2024-01-28 21:23:38 +00:00
|
|
|
ret, err := c.Scheduler().NewJob(job.Job, job.Task, job.Options...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret, nil
|
|
|
|
}
|
2024-02-01 01:29:27 +00:00
|
|
|
func (c *CronServiceDefault) RerunJob(job CronJob) (gocron.Job, error) {
|
2024-01-28 21:39:04 +00:00
|
|
|
ret, err := c.Scheduler().Update(job.JobId, job.Job, job.Task, job.Options...)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret, nil
|
|
|
|
}
|
2024-02-25 06:58:26 +00:00
|
|
|
|
|
|
|
func (c *CronServiceDefault) GetJobsByPrefix(prefix string) []gocron.Job {
|
|
|
|
jobs := c.Scheduler().Jobs()
|
|
|
|
|
|
|
|
var ret []gocron.Job
|
|
|
|
|
|
|
|
for _, job := range jobs {
|
|
|
|
if strings.HasPrefix(job.Name(), prefix) {
|
|
|
|
ret = append(ret, job)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *CronServiceDefault) GetJobByName(name string) gocron.Job {
|
|
|
|
jobs := c.Scheduler().Jobs()
|
|
|
|
|
|
|
|
for _, job := range jobs {
|
|
|
|
if job.Name() == name {
|
|
|
|
return job
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *CronServiceDefault) GetJobByID(id uuid.UUID) gocron.Job {
|
|
|
|
jobs := c.Scheduler().Jobs()
|
|
|
|
|
|
|
|
for _, job := range jobs {
|
|
|
|
if job.ID() == id {
|
|
|
|
return job
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|