package scheduler import ( "context" "time" "git.intra.yksa.space/gsn/predictor/internal/pkg/errcodes" "github.com/go-co-op/gocron" "go.uber.org/zap" ) type Job interface { GetInterval() time.Duration GetTimeout() time.Duration GetCount() int GetAsync() bool Execute(context.Context) error } type Scheduler struct { scheduler *gocron.Scheduler logger *zap.Logger } func New(logger *zap.Logger) *Scheduler { scheduler := gocron.NewScheduler(time.UTC) return &Scheduler{ scheduler: scheduler, logger: logger, } } func (s *Scheduler) AddJob(job Job) error { interval := job.GetInterval() timeout := job.GetTimeout() count := job.GetCount() async := job.GetAsync() // Validate job parameters if !async && count != 1 { return errcodes.ErrSchedulerInvalidJob } if timeout > interval { return errcodes.ErrSchedulerTimeoutTooLong } // Create job function with timeout jobFunc := func() { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() if err := job.Execute(ctx); err != nil { s.logger.Error("job execution failed", zap.Error(err), zap.Duration("interval", interval), zap.Duration("timeout", timeout)) } else { s.logger.Debug("job executed successfully", zap.Duration("interval", interval), zap.Duration("timeout", timeout)) } } // Add job to scheduler schedulerJob := s.scheduler.Every(interval) if !async { schedulerJob = schedulerJob.SingletonMode() } if count > 0 { schedulerJob = schedulerJob.LimitRunsTo(count) } schedulerJob.Do(jobFunc) s.logger.Info("job added to scheduler", zap.Duration("interval", interval), zap.Duration("timeout", timeout), zap.Int("count", count), zap.Bool("async", async)) return nil } func (s *Scheduler) Start() { s.scheduler.StartAsync() s.logger.Info("scheduler started") } func (s *Scheduler) Stop() { s.scheduler.Stop() s.logger.Info("scheduler stopped") } func (s *Scheduler) IsRunning() bool { return s.scheduler.IsRunning() }