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 }