github.com/mattermost/mattermost-server/v5@v5.39.3/jobs/schedulers.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package jobs 5 6 import ( 7 "errors" 8 "fmt" 9 "time" 10 11 "github.com/mattermost/mattermost-server/v5/model" 12 "github.com/mattermost/mattermost-server/v5/shared/mlog" 13 ) 14 15 type Schedulers struct { 16 stop chan bool 17 stopped chan bool 18 configChanged chan *model.Config 19 clusterLeaderChanged chan bool 20 listenerId string 21 jobs *JobServer 22 isLeader bool 23 running bool 24 25 schedulers []model.Scheduler 26 nextRunTimes []*time.Time 27 } 28 29 var ( 30 ErrSchedulersNotRunning = errors.New("job schedulers are not running") 31 ErrSchedulersRunning = errors.New("job schedulers are running") 32 ErrSchedulersUninitialized = errors.New("job schedulers are not initialized") 33 ) 34 35 func (srv *JobServer) InitSchedulers() error { 36 srv.mut.Lock() 37 defer srv.mut.Unlock() 38 if srv.schedulers != nil && srv.schedulers.running { 39 return ErrSchedulersRunning 40 } 41 mlog.Debug("Initialising schedulers.") 42 43 schedulers := &Schedulers{ 44 stop: make(chan bool), 45 stopped: make(chan bool), 46 configChanged: make(chan *model.Config), 47 clusterLeaderChanged: make(chan bool, 1), 48 jobs: srv, 49 isLeader: true, 50 } 51 52 if srv.DataRetentionJob != nil { 53 schedulers.schedulers = append(schedulers.schedulers, srv.DataRetentionJob.MakeScheduler()) 54 } 55 56 if srv.MessageExportJob != nil { 57 schedulers.schedulers = append(schedulers.schedulers, srv.MessageExportJob.MakeScheduler()) 58 } 59 60 if elasticsearchAggregatorInterface := srv.ElasticsearchAggregator; elasticsearchAggregatorInterface != nil { 61 schedulers.schedulers = append(schedulers.schedulers, elasticsearchAggregatorInterface.MakeScheduler()) 62 } 63 64 if ldapSyncInterface := srv.LdapSync; ldapSyncInterface != nil { 65 schedulers.schedulers = append(schedulers.schedulers, ldapSyncInterface.MakeScheduler()) 66 } 67 68 if migrationsInterface := srv.Migrations; migrationsInterface != nil { 69 schedulers.schedulers = append(schedulers.schedulers, migrationsInterface.MakeScheduler()) 70 } 71 72 if pluginsInterface := srv.Plugins; pluginsInterface != nil { 73 schedulers.schedulers = append(schedulers.schedulers, pluginsInterface.MakeScheduler()) 74 } 75 76 if expiryNotifyInterface := srv.ExpiryNotify; expiryNotifyInterface != nil { 77 schedulers.schedulers = append(schedulers.schedulers, expiryNotifyInterface.MakeScheduler()) 78 } 79 80 if activeUsersInterface := srv.ActiveUsers; activeUsersInterface != nil { 81 schedulers.schedulers = append(schedulers.schedulers, activeUsersInterface.MakeScheduler()) 82 } 83 84 if productNoticesInterface := srv.ProductNotices; productNoticesInterface != nil { 85 schedulers.schedulers = append(schedulers.schedulers, productNoticesInterface.MakeScheduler()) 86 } 87 88 if cloudInterface := srv.Cloud; cloudInterface != nil { 89 schedulers.schedulers = append(schedulers.schedulers, cloudInterface.MakeScheduler()) 90 } 91 92 if resendInvitationEmailInterface := srv.ResendInvitationEmails; resendInvitationEmailInterface != nil { 93 schedulers.schedulers = append(schedulers.schedulers, resendInvitationEmailInterface.MakeScheduler()) 94 } 95 96 if importDeleteInterface := srv.ImportDelete; importDeleteInterface != nil { 97 schedulers.schedulers = append(schedulers.schedulers, importDeleteInterface.MakeScheduler()) 98 } 99 100 if exportDeleteInterface := srv.ExportDelete; exportDeleteInterface != nil { 101 schedulers.schedulers = append(schedulers.schedulers, exportDeleteInterface.MakeScheduler()) 102 } 103 104 schedulers.nextRunTimes = make([]*time.Time, len(schedulers.schedulers)) 105 srv.schedulers = schedulers 106 107 return nil 108 } 109 110 // Start starts the schedulers. This call is not safe for concurrent use. 111 // Synchronization should be implemented by the caller. 112 func (schedulers *Schedulers) Start() { 113 schedulers.listenerId = schedulers.jobs.ConfigService.AddConfigListener(schedulers.handleConfigChange) 114 115 go func() { 116 mlog.Info("Starting schedulers.") 117 118 defer func() { 119 mlog.Info("Schedulers stopped.") 120 close(schedulers.stopped) 121 }() 122 123 now := time.Now() 124 for idx, scheduler := range schedulers.schedulers { 125 if !scheduler.Enabled(schedulers.jobs.Config()) { 126 schedulers.nextRunTimes[idx] = nil 127 } else { 128 schedulers.setNextRunTime(schedulers.jobs.Config(), idx, now, false) 129 } 130 } 131 132 for { 133 timer := time.NewTimer(1 * time.Minute) 134 select { 135 case <-schedulers.stop: 136 mlog.Debug("Schedulers received stop signal.") 137 timer.Stop() 138 return 139 case now = <-timer.C: 140 cfg := schedulers.jobs.Config() 141 142 for idx, nextTime := range schedulers.nextRunTimes { 143 if nextTime == nil { 144 continue 145 } 146 147 if time.Now().After(*nextTime) { 148 scheduler := schedulers.schedulers[idx] 149 if scheduler == nil || !schedulers.isLeader || !scheduler.Enabled(cfg) { 150 continue 151 } 152 if _, err := schedulers.scheduleJob(cfg, scheduler); err != nil { 153 mlog.Error("Failed to schedule job", mlog.String("scheduler", scheduler.Name()), mlog.Err(err)) 154 continue 155 } 156 schedulers.setNextRunTime(cfg, idx, now, true) 157 } 158 } 159 case newCfg := <-schedulers.configChanged: 160 for idx, scheduler := range schedulers.schedulers { 161 if !schedulers.isLeader || !scheduler.Enabled(newCfg) { 162 schedulers.nextRunTimes[idx] = nil 163 } else { 164 schedulers.setNextRunTime(newCfg, idx, now, false) 165 } 166 } 167 case isLeader := <-schedulers.clusterLeaderChanged: 168 for idx := range schedulers.schedulers { 169 schedulers.isLeader = isLeader 170 if !isLeader { 171 schedulers.nextRunTimes[idx] = nil 172 } else { 173 schedulers.setNextRunTime(schedulers.jobs.Config(), idx, now, false) 174 } 175 } 176 } 177 timer.Stop() 178 } 179 }() 180 181 schedulers.running = true 182 } 183 184 // Stop stops the schedulers. This call is not safe for concurrent use. 185 // Synchronization should be implemented by the caller. 186 func (schedulers *Schedulers) Stop() { 187 mlog.Info("Stopping schedulers.") 188 close(schedulers.stop) 189 <-schedulers.stopped 190 schedulers.jobs.ConfigService.RemoveConfigListener(schedulers.listenerId) 191 schedulers.listenerId = "" 192 schedulers.running = false 193 } 194 195 func (schedulers *Schedulers) setNextRunTime(cfg *model.Config, idx int, now time.Time, pendingJobs bool) { 196 scheduler := schedulers.schedulers[idx] 197 198 if !pendingJobs { 199 pj, err := schedulers.jobs.CheckForPendingJobsByType(scheduler.JobType()) 200 if err != nil { 201 mlog.Error("Failed to set next job run time", mlog.Err(err)) 202 schedulers.nextRunTimes[idx] = nil 203 return 204 } 205 pendingJobs = pj 206 } 207 208 lastSuccessfulJob, err := schedulers.jobs.GetLastSuccessfulJobByType(scheduler.JobType()) 209 if err != nil { 210 mlog.Error("Failed to set next job run time", mlog.Err(err)) 211 schedulers.nextRunTimes[idx] = nil 212 return 213 } 214 215 schedulers.nextRunTimes[idx] = scheduler.NextScheduleTime(cfg, now, pendingJobs, lastSuccessfulJob) 216 mlog.Debug("Next run time for scheduler", mlog.String("scheduler_name", scheduler.Name()), mlog.String("next_runtime", fmt.Sprintf("%v", schedulers.nextRunTimes[idx]))) 217 } 218 219 func (schedulers *Schedulers) scheduleJob(cfg *model.Config, scheduler model.Scheduler) (*model.Job, *model.AppError) { 220 pendingJobs, err := schedulers.jobs.CheckForPendingJobsByType(scheduler.JobType()) 221 if err != nil { 222 return nil, err 223 } 224 225 lastSuccessfulJob, err2 := schedulers.jobs.GetLastSuccessfulJobByType(scheduler.JobType()) 226 if err2 != nil { 227 return nil, err 228 } 229 230 return scheduler.ScheduleJob(cfg, pendingJobs, lastSuccessfulJob) 231 } 232 233 func (schedulers *Schedulers) handleConfigChange(_, newConfig *model.Config) { 234 mlog.Debug("Schedulers received config change.") 235 select { 236 case schedulers.configChanged <- newConfig: 237 case <-schedulers.stop: 238 } 239 } 240 241 func (schedulers *Schedulers) handleClusterLeaderChange(isLeader bool) { 242 select { 243 case schedulers.clusterLeaderChanged <- isLeader: 244 default: 245 mlog.Debug("Sending cluster leader change message to schedulers failed.") 246 247 // Drain the buffered channel to make room for the latest change. 248 select { 249 case <-schedulers.clusterLeaderChanged: 250 default: 251 } 252 253 // Enqueue the latest change. This operation is safe due to this method 254 // being called under lock. 255 schedulers.clusterLeaderChanged <- isLeader 256 } 257 }