package cron import ( "context" "fmt" "sync" "github.com/robfig/cron/v3" "git.tswf.io/infra/go-synapse-backupper/pkg/domain" ) // cronScheduler wraps robfig/cron/v3 to implement domain.Scheduler. type cronScheduler struct { cron *cron.Cron waitGroup sync.WaitGroup stopCancel context.CancelFunc stoppedCtx context.Context } // NewCronScheduler creates a new cron-based scheduler. // The expr must be a valid cron expression. When WithSeconds is used // (as in this implementation) the expression should contain six fields. func NewCronScheduler( expr string, job func(), ) ( domain.Scheduler, error, ) { scheduler := &cronScheduler{ cron: cron.New(cron.WithSeconds()), } _, err := scheduler.cron.AddFunc( expr, func() { scheduler.waitGroup.Add(1) defer scheduler.waitGroup.Done() job() }, ) if err != nil { return nil, fmt.Errorf("invalid cron expression %q: %w", expr, err) } scheduler.stoppedCtx, scheduler.stopCancel = context.WithCancel(context.Background()) return scheduler, nil } // Start begins executing the scheduled job. func (s *cronScheduler) Start() { s.cron.Start() } // Stop halts the scheduler and returns a context that is cancelled // once all in-flight jobs have completed. func (s *cronScheduler) Stop() context.Context { s.cron.Stop() go func() { s.waitGroup.Wait() s.stopCancel() }() return s.stoppedCtx }