// getWorkFromSpec forcibly retrieves a work unit from a work spec. // It could create a work unit if spec is a continuous spec with no // available units. It ignores other constraints, such as whether the // work spec is paused. func (w *worker) getWorkFromSpec(spec *workSpec, meta *coordinate.WorkSpecMeta) *attempt { var unit *workUnit now := w.Coordinate().clock.Now() if len(spec.available) != 0 { unit = spec.available.Next() } else if meta.CanStartContinuous(now) { // Make a brand new work unit. Its key is the string // form of a time_t. seconds := now.Unix() nano := now.Nanosecond() milli := nano / 1000000 name := fmt.Sprintf("%d.%03d", seconds, milli) var exists bool unit, exists = spec.workUnits[name] if !exists { unit = &workUnit{ name: name, data: map[string]interface{}{}, workSpec: spec, } spec.workUnits[name] = unit } spec.meta.NextContinuous = now.Add(meta.Interval) } else { return nil } return w.makeAttempt(unit, time.Duration(0)) }
func (w *worker) requestAttemptsForSpec(req coordinate.AttemptRequest, spec *workSpec, meta *coordinate.WorkSpecMeta) ([]coordinate.Attempt, error) { var ( attempts []coordinate.Attempt count int err error ) // Adjust the work unit count based on what's possible here count = req.NumberOfWorkUnits if count < 1 { count = 1 } if meta.MaxAttemptsReturned > 0 && count > meta.MaxAttemptsReturned { count = meta.MaxAttemptsReturned } if meta.MaxRunning > 0 && count > meta.MaxRunning-meta.PendingCount { count = meta.MaxRunning - meta.PendingCount } continuous := false length := time.Duration(15) * time.Minute err = withTx(w, false, func(tx *sql.Tx) error { var err error now := w.Coordinate().clock.Now() // Take an advisory lock for the work spec. It doesn't // make sense for multiple concurrent actors to progress // beyond this point (they will hit the same work units // in chooseAndMakeAttempts() and one will roll back) and // this reduces the database load. We are still protected // by standard SQL transactionality. params := queryParams{} query := "SELECT pg_advisory_xact_lock(0, " + params.Param(spec.id) + ")" _, err = tx.Exec(query, params...) if err != nil { return err } // Try to create attempts from pre-existing work units // (assuming we expect there to be some) if meta.AvailableCount > 0 { attempts, err = w.chooseAndMakeAttempts(tx, spec, count, now, length) } if err != nil || len(attempts) > 0 { return err } // If there were none, but the selected work spec is // continuous, maybe we can create a work unit and an // attempt if meta.CanStartContinuous(now) { var unit *workUnit var attempt *attempt continuous = true unit, err = w.createContinuousUnit(tx, spec, meta, now) if err == nil && unit != nil { attempt, err = makeAttempt(tx, unit, w, length) } if err == nil && attempt != nil { attempts = []coordinate.Attempt{attempt} } } // Whatever happened, end of the road return err }) // On a very bad day, we could have gone to create continuous // work units, but an outright INSERT failed from a duplicate // key. If this happened, just pretend we didn't actually get // any attempts back, which will trigger the retry loop in our // caller. if continuous && err != nil && isDuplicateUnitName(err) { attempts = nil err = nil } return attempts, err }