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  }