Initial commit: Open sourcing all of the Maple Open Technologies code.
This commit is contained in:
commit
755d54a99d
2010 changed files with 448675 additions and 0 deletions
|
|
@ -0,0 +1,179 @@
|
|||
package scheduler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/leaderelection"
|
||||
)
|
||||
|
||||
// Task represents a scheduled task
|
||||
type Task interface {
|
||||
Name() string
|
||||
Schedule() string
|
||||
Execute(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Scheduler manages all scheduled tasks
|
||||
// Tasks are only executed if this instance is the leader (when leader election is enabled)
|
||||
type Scheduler struct {
|
||||
config *config.Config
|
||||
logger *zap.Logger
|
||||
cron *cron.Cron
|
||||
tasks []Task
|
||||
mu sync.RWMutex
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
leaderElection leaderelection.LeaderElection // Leader election instance (can be nil if disabled)
|
||||
}
|
||||
|
||||
// ProvideScheduler creates a new Scheduler instance for Wire DI
|
||||
func ProvideScheduler(
|
||||
cfg *config.Config,
|
||||
logger *zap.Logger,
|
||||
leaderElection leaderelection.LeaderElection,
|
||||
) *Scheduler {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
logger = logger.Named("Scheduler")
|
||||
|
||||
return &Scheduler{
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
cron: cron.New(),
|
||||
tasks: make([]Task, 0),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
leaderElection: leaderElection,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterTask registers a task to be scheduled
|
||||
func (s *Scheduler) RegisterTask(task Task) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.logger.Info("Registering scheduled task",
|
||||
zap.String("task", task.Name()),
|
||||
zap.String("schedule", task.Schedule()))
|
||||
|
||||
// Add task to scheduler
|
||||
_, err := s.cron.AddFunc(task.Schedule(), func() {
|
||||
s.executeTask(task)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to register task",
|
||||
zap.String("task", task.Name()),
|
||||
zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
s.tasks = append(s.tasks, task)
|
||||
s.logger.Info("✅ Task registered successfully",
|
||||
zap.String("task", task.Name()))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeTask executes a task with error handling and logging
|
||||
// Tasks are only executed if this instance is the leader (when leader election is enabled)
|
||||
func (s *Scheduler) executeTask(task Task) {
|
||||
// Check if leader election is enabled
|
||||
if s.config.LeaderElection.Enabled && s.leaderElection != nil {
|
||||
// Only execute if this instance is the leader
|
||||
if !s.leaderElection.IsLeader() {
|
||||
s.logger.Debug("Skipping task execution - not the leader",
|
||||
zap.String("task", task.Name()),
|
||||
zap.String("instance_id", s.leaderElection.GetInstanceID()))
|
||||
return
|
||||
}
|
||||
|
||||
// Log that leader is executing the task
|
||||
s.logger.Info("👑 Leader executing scheduled task",
|
||||
zap.String("task", task.Name()),
|
||||
zap.String("instance_id", s.leaderElection.GetInstanceID()))
|
||||
} else {
|
||||
// Leader election disabled, execute normally
|
||||
s.logger.Info("Executing scheduled task",
|
||||
zap.String("task", task.Name()))
|
||||
}
|
||||
|
||||
// Create a context for this execution
|
||||
ctx := s.ctx
|
||||
|
||||
// Execute the task
|
||||
if err := task.Execute(ctx); err != nil {
|
||||
s.logger.Error("Task execution failed",
|
||||
zap.String("task", task.Name()),
|
||||
zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Info("✅ Task completed successfully",
|
||||
zap.String("task", task.Name()))
|
||||
}
|
||||
|
||||
// Start starts the scheduler
|
||||
func (s *Scheduler) Start() error {
|
||||
s.mu.RLock()
|
||||
taskCount := len(s.tasks)
|
||||
s.mu.RUnlock()
|
||||
|
||||
// Log leader election status
|
||||
if s.config.LeaderElection.Enabled && s.leaderElection != nil {
|
||||
s.logger.Info("🕐 Starting scheduler with leader election",
|
||||
zap.Int("registered_tasks", taskCount),
|
||||
zap.Bool("leader_election_enabled", true),
|
||||
zap.String("instance_id", s.leaderElection.GetInstanceID()))
|
||||
|
||||
s.logger.Info("ℹ️ Tasks will ONLY execute on the leader instance")
|
||||
} else {
|
||||
s.logger.Info("🕐 Starting scheduler without leader election",
|
||||
zap.Int("registered_tasks", taskCount),
|
||||
zap.Bool("leader_election_enabled", false))
|
||||
|
||||
s.logger.Warn("⚠️ Leader election is disabled - tasks will run on ALL instances")
|
||||
}
|
||||
|
||||
if taskCount == 0 {
|
||||
s.logger.Warn("No tasks registered, scheduler will run but do nothing")
|
||||
}
|
||||
|
||||
s.cron.Start()
|
||||
|
||||
s.logger.Info("✅ Scheduler started successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the scheduler gracefully
|
||||
func (s *Scheduler) Stop() error {
|
||||
s.logger.Info("Stopping scheduler...")
|
||||
|
||||
// Cancel all running tasks
|
||||
s.cancel()
|
||||
|
||||
// Stop the cron scheduler
|
||||
ctx := s.cron.Stop()
|
||||
<-ctx.Done()
|
||||
|
||||
s.logger.Info("✅ Scheduler stopped successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRegisteredTasks returns a list of registered task names
|
||||
func (s *Scheduler) GetRegisteredTasks() []string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
taskNames := make([]string, len(s.tasks))
|
||||
for i, task := range s.tasks {
|
||||
taskNames[i] = task.Name()
|
||||
}
|
||||
|
||||
return taskNames
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue