github.com/Axway/agent-sdk@v1.1.101/pkg/jobs/scheduledjob.go (about)

     1  package jobs
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  
     7  	"github.com/gorhill/cronexpr"
     8  
     9  	"github.com/Axway/agent-sdk/pkg/util/errors"
    10  )
    11  
    12  type scheduleJobProps struct {
    13  	schedule string
    14  	cronExp  *cronexpr.Expression
    15  	stopChan chan bool
    16  }
    17  
    18  type scheduleJob struct {
    19  	baseJob
    20  	scheduleJobProps
    21  	cronLock *sync.Mutex
    22  }
    23  
    24  // newScheduledJob - creates a job that is ran at a specific time (@hourly,@daily,@weekly,min hour dow dom)
    25  func newScheduledJob(newJob Job, schedule, name string, failJobChan chan string, opts ...jobOpt) (JobExecution, error) {
    26  	exp, err := cronexpr.Parse(schedule)
    27  	if err != nil {
    28  		return nil, errors.Wrap(ErrRegisteringJob, err.Error()).FormatError("scheduled")
    29  	}
    30  
    31  	thisJob := scheduleJob{
    32  		createBaseJob(newJob, failJobChan, name, JobTypeScheduled),
    33  		scheduleJobProps{
    34  			cronExp:  exp,
    35  			schedule: schedule,
    36  			stopChan: make(chan bool, 1),
    37  		},
    38  		&sync.Mutex{},
    39  	}
    40  
    41  	for _, o := range opts {
    42  		o(&thisJob.baseJob)
    43  	}
    44  
    45  	go thisJob.start()
    46  	return &thisJob, nil
    47  }
    48  
    49  func (b *scheduleJob) getNextExecution() time.Duration {
    50  	b.cronLock.Lock()
    51  	defer b.cronLock.Unlock()
    52  	nextTime := b.cronExp.Next(time.Now())
    53  	return time.Until(nextTime)
    54  }
    55  
    56  // start - calls the Execute function from the Job definition
    57  func (b *scheduleJob) start() {
    58  	b.startLog()
    59  	b.waitForReady()
    60  
    61  	// This could happen while rescheduling the job, pool tries to start
    62  	// and one of the job fails which triggers stop setting the flag to not ready
    63  	// Return in this case to allow pool to reschedule the job
    64  	if !b.IsReady() {
    65  		return
    66  	}
    67  	b.setIsStopped(false)
    68  	ticker := time.NewTicker(b.getNextExecution())
    69  	defer ticker.Stop()
    70  	b.SetStatus(JobStatusRunning)
    71  	for {
    72  		// Non-blocking channel read, if stopped then exit
    73  		select {
    74  		case <-b.stopChan:
    75  			b.SetStatus(JobStatusStopped)
    76  			return
    77  		case <-ticker.C:
    78  			b.executeCronJob()
    79  			if b.getError() != nil {
    80  				b.setExecutionError()
    81  			}
    82  			ticker.Stop()
    83  			ticker = time.NewTicker(b.getNextExecution())
    84  		}
    85  	}
    86  }
    87  
    88  // stop - write to the stop channel to stop the execution loop
    89  func (b *scheduleJob) stop() {
    90  	if b.getIsStopped() {
    91  		b.logger.Tracef("job has already been stopped")
    92  		return
    93  	}
    94  
    95  	b.stopLog()
    96  	if b.IsReady() {
    97  		b.logger.Tracef("writing to %s stop channel", b.GetName())
    98  		b.stopChan <- true
    99  		b.logger.Tracef("wrote to %s stop channel", b.GetName())
   100  		b.UnsetIsReady()
   101  	} else {
   102  		b.stopReadyIfWaiting(0)
   103  	}
   104  	b.setIsStopped(true)
   105  }