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

     1  package jobs
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/Axway/agent-sdk/pkg/util/errors"
     9  	"github.com/Axway/agent-sdk/pkg/util/log"
    10  )
    11  
    12  type baseJob struct {
    13  	JobExecution
    14  	id               string        // UUID generated for this job
    15  	name             string        // Name of the job
    16  	job              Job           // the job definition
    17  	jobType          string        // type of job
    18  	status           JobStatus     // current job status
    19  	err              error         // the error thrown
    20  	statusLock       *sync.RWMutex // lock on preventing status write/read at the same time
    21  	isReadyLock      *sync.RWMutex // lock on preventing isReady write/read at the same time
    22  	isReadyWaitLock  *sync.RWMutex // lock on preventing isReady write/read at the same time
    23  	backoffLock      *sync.RWMutex // lock on preventing backoff write/read at the same time
    24  	failsLock        *sync.RWMutex // lock on preventing consecutiveFails write/read at the same time
    25  	failChan         chan string   // channel to send signal to pool of failure
    26  	errorLock        *sync.RWMutex // lock on preventing error write/read at the same time
    27  	jobLock          sync.Mutex    // lock used for signalling that the job is being executed
    28  	consecutiveFails int
    29  	backoff          *backoff
    30  	isReady          bool
    31  	isReadyWait      bool
    32  	stopReadyChan    chan int
    33  	logger           log.FieldLogger
    34  	isStopped        bool
    35  	stoppedLock      *sync.Mutex
    36  	timeout          time.Duration
    37  }
    38  
    39  type jobOpt func(*baseJob)
    40  
    41  func WithJobTimeout(timeout time.Duration) jobOpt {
    42  	return func(b *baseJob) {
    43  		b.timeout = timeout
    44  	}
    45  }
    46  
    47  // newBaseJob - creates a single run job and sets up the structure for different job types
    48  func newBaseJob(newJob Job, failJobChan chan string, name string) (JobExecution, error) {
    49  	thisJob := createBaseJob(newJob, failJobChan, name, JobTypeSingleRun)
    50  
    51  	go thisJob.start()
    52  	return &thisJob, nil
    53  }
    54  
    55  // createBaseJob - creates a single run job and returns it
    56  func createBaseJob(newJob Job, failJobChan chan string, name string, jobType string) baseJob {
    57  	id := newUUID()
    58  	logger := log.NewFieldLogger().
    59  		WithPackage("sdk.jobs").
    60  		WithComponent("baseJob").
    61  		WithField("job-name", name).
    62  		WithField("job-id", id)
    63  
    64  	backoff := newBackoffTimeout(10*time.Millisecond, 10*time.Minute, 2)
    65  
    66  	return baseJob{
    67  		id:              id,
    68  		name:            name,
    69  		job:             newJob,
    70  		jobType:         jobType,
    71  		status:          JobStatusInitializing,
    72  		failChan:        failJobChan,
    73  		statusLock:      &sync.RWMutex{},
    74  		isReadyLock:     &sync.RWMutex{},
    75  		isReadyWaitLock: &sync.RWMutex{},
    76  		backoffLock:     &sync.RWMutex{},
    77  		failsLock:       &sync.RWMutex{},
    78  		errorLock:       &sync.RWMutex{},
    79  		backoff:         backoff,
    80  		isReady:         false,
    81  		isReadyWait:     false,
    82  		stopReadyChan:   make(chan int, 1),
    83  		logger:          logger,
    84  		stoppedLock:     &sync.Mutex{},
    85  	}
    86  }
    87  
    88  func (b *baseJob) executeJob() {
    89  	b.setError(b.job.Execute())
    90  	b.SetStatus(JobStatusFinished)
    91  	if b.getError() != nil {
    92  		b.SetStatus(JobStatusFailed)
    93  	}
    94  }
    95  
    96  func (b *baseJob) callWithTimeout(execution func() error) error {
    97  	var executionError error
    98  	// execution time limit is set
    99  	timeLimit := executionTimeLimit
   100  	if b.timeout > 0 {
   101  		timeLimit = b.timeout
   102  	}
   103  	if timeLimit > 0 {
   104  		// start a go routine to execute the job
   105  		executed := make(chan error)
   106  		go func() {
   107  			executed <- execution()
   108  		}()
   109  
   110  		// either the job finishes or a timeout is hit
   111  		select {
   112  		case err := <-executed:
   113  			executionError = err
   114  		case <-time.After(timeLimit): // execute the job with a time limit
   115  			executionError = fmt.Errorf("job %s (%s) timed out", b.name, b.id)
   116  		}
   117  	} else {
   118  		executionError = execution()
   119  	}
   120  
   121  	return executionError
   122  }
   123  
   124  func (b *baseJob) executeCronJob() {
   125  	// Lock the mutex for external syn with the job
   126  	b.jobLock.Lock()
   127  	defer b.jobLock.Unlock()
   128  
   129  	b.setError(b.callWithTimeout(b.job.Execute))
   130  	if b.getError() != nil {
   131  		if b.failChan != nil {
   132  			b.failChan <- b.id
   133  		}
   134  		b.SetStatus(JobStatusFailed)
   135  	}
   136  }
   137  
   138  // getBackoff - get the job backoff
   139  func (b *baseJob) getBackoff() *backoff {
   140  	b.backoffLock.Lock()
   141  	defer b.backoffLock.Unlock()
   142  	return b.backoff
   143  }
   144  
   145  // setBackoff - set the job backoff
   146  func (b *baseJob) setBackoff(backoff *backoff) {
   147  	b.backoffLock.Lock()
   148  	defer b.backoffLock.Unlock()
   149  	b.backoff = backoff
   150  }
   151  
   152  // SetStatus - locks the job, execution can not take place until the Unlock func is called
   153  func (b *baseJob) SetStatus(status JobStatus) {
   154  	b.statusLock.Lock()
   155  	defer b.statusLock.Unlock()
   156  	b.status = status
   157  }
   158  
   159  // setReadyWait - set flag to indicate the job is waiting for ready
   160  func (b *baseJob) setReadyWait(waitReady bool) {
   161  	b.isReadyWaitLock.Lock()
   162  	defer b.isReadyWaitLock.Unlock()
   163  	b.isReadyWait = waitReady
   164  }
   165  
   166  // isWaitingForReady - return true if job is waiting for ready
   167  func (b *baseJob) isWaitingForReady() bool {
   168  	b.isReadyWaitLock.Lock()
   169  	defer b.isReadyWaitLock.Unlock()
   170  	return b.isReadyWait
   171  }
   172  
   173  // SetIsReady - set that the job is now ready
   174  func (b *baseJob) SetIsReady() {
   175  	b.isReadyLock.Lock()
   176  	defer b.isReadyLock.Unlock()
   177  	b.isReady = true
   178  }
   179  
   180  // UnsetIsReady - set that the job is now ready
   181  func (b *baseJob) UnsetIsReady() {
   182  	b.isReadyLock.Lock()
   183  	defer b.isReadyLock.Unlock()
   184  	b.isReady = false
   185  }
   186  
   187  // IsReady - set that the job is now ready
   188  func (b *baseJob) IsReady() bool {
   189  	b.isReadyLock.Lock()
   190  	defer b.isReadyLock.Unlock()
   191  	return b.isReady
   192  }
   193  
   194  func (b *baseJob) getIsStopped() bool {
   195  	b.stoppedLock.Lock()
   196  	defer b.stoppedLock.Unlock()
   197  	return b.isStopped
   198  }
   199  
   200  func (b *baseJob) setIsStopped(stopped bool) {
   201  	b.stoppedLock.Lock()
   202  	defer b.stoppedLock.Unlock()
   203  	b.isStopped = stopped
   204  }
   205  
   206  // Lock - locks the job, execution can not take place until the Unlock func is called
   207  func (b *baseJob) Lock() {
   208  	b.jobLock.Lock()
   209  }
   210  
   211  // Unlock - unlocks the job, execution can now take place
   212  func (b *baseJob) Unlock() {
   213  	b.jobLock.Unlock()
   214  }
   215  
   216  func (b *baseJob) getConsecutiveFails() int {
   217  	b.failsLock.Lock()
   218  	defer b.failsLock.Unlock()
   219  	return b.consecutiveFails
   220  }
   221  
   222  func (b *baseJob) setConsecutiveFails(fails int) {
   223  	b.failsLock.Lock()
   224  	defer b.failsLock.Unlock()
   225  	b.consecutiveFails = fails
   226  }
   227  
   228  func (b *baseJob) getError() error {
   229  	b.errorLock.Lock()
   230  	defer b.errorLock.Unlock()
   231  	return b.err
   232  }
   233  
   234  func (b *baseJob) setError(err error) {
   235  	b.errorLock.Lock()
   236  	defer b.errorLock.Unlock()
   237  	b.err = err
   238  }
   239  
   240  // GetStatusValue - returns the job status
   241  func (b *baseJob) updateStatus() JobStatus {
   242  	b.statusLock.Lock()
   243  	defer b.statusLock.Unlock()
   244  	newStatus := b.status
   245  	jobStatus := b.callWithTimeout(b.job.Status)
   246  	if jobStatus != nil { // on error set the status to failed
   247  		b.logger.WithError(jobStatus).Error("job failed")
   248  
   249  		newStatus = JobStatusFailed
   250  	}
   251  
   252  	b.status = newStatus
   253  	b.logger.Tracef("current job status %s", jobStatusToString[newStatus])
   254  	return b.status
   255  }
   256  
   257  // GetStatus - returns the job status
   258  func (b *baseJob) GetStatus() JobStatus {
   259  	b.statusLock.Lock()
   260  	defer b.statusLock.Unlock()
   261  	return b.status
   262  }
   263  
   264  // GetID - returns the ID for this job
   265  func (b *baseJob) GetID() string {
   266  	return b.id
   267  }
   268  
   269  // GetName - returns the name for this job, returns the ID if name is blank
   270  func (b *baseJob) GetName() string {
   271  	if b.name == "" {
   272  		return b.id
   273  	}
   274  	return b.name
   275  }
   276  
   277  // GetJob - returns the Job interface
   278  func (b *baseJob) GetJob() JobExecution {
   279  	return b
   280  }
   281  
   282  // Ready - checks that the job is ready
   283  func (b *baseJob) Ready() bool {
   284  	return b.job.Ready()
   285  }
   286  
   287  // waitForReady - waits for the Ready func to return true
   288  func (b *baseJob) waitForReady() {
   289  	b.logger.Debugf("waiting for job to be ready: %s", b.GetName())
   290  	b.setReadyWait(true)
   291  	defer b.setReadyWait(false)
   292  
   293  	for {
   294  		select {
   295  		case ready := <-b.stopReadyChan:
   296  			if b.getBackoff() != nil {
   297  				b.getBackoff().reset()
   298  			}
   299  			if ready == 1 {
   300  				b.SetIsReady()
   301  			} else {
   302  				b.UnsetIsReady()
   303  			}
   304  			return
   305  		default:
   306  			if b.job.Ready() {
   307  				b.logger.Debug("job is ready")
   308  				b.stopReadyIfWaiting(1)
   309  			} else {
   310  				if b.getBackoff() != nil {
   311  					b.logger.Tracef("job is not ready, checking again in %v seconds", b.getBackoff().getCurrentTimeout())
   312  					b.getBackoff().sleep()
   313  					b.getBackoff().increaseTimeout()
   314  				}
   315  			}
   316  		}
   317  	}
   318  }
   319  
   320  func (b *baseJob) stopReadyIfWaiting(ready int) {
   321  	if b.isWaitingForReady() {
   322  		b.stopReadyChan <- ready
   323  	}
   324  
   325  }
   326  
   327  // start - waits for Ready to return true then calls the Execute function from the Job definition
   328  func (b *baseJob) start() {
   329  	b.startLog()
   330  	b.waitForReady()
   331  
   332  	b.SetStatus(JobStatusRunning)
   333  	b.executeJob()
   334  }
   335  
   336  // stop - noop in base
   337  func (b *baseJob) stop() {
   338  	b.stopLog()
   339  }
   340  
   341  func (b *baseJob) startLog() {
   342  	b.logger.Debugf("Starting %v", b.jobType)
   343  }
   344  
   345  func (b *baseJob) stopLog() {
   346  	b.logger.Debugf("Stopping %v ", b.jobType)
   347  }
   348  
   349  func (b *baseJob) setExecutionError() {
   350  	b.setError(errors.Wrap(ErrExecutingJob, b.getError().Error()).FormatError(b.jobType, b.id))
   351  }