// UpdateWorkUnit causes some state change in a work unit. If the // work unit is pending, this is the principal interface to complete // or renew it; if it is already complete this can cause it to be // retried. func (jobs *JobServer) UpdateWorkUnit( workSpecName string, workUnitKey string, options map[string]interface{}, ) (bool, string, error) { // Note that in several corner cases, the behavior of this as // written disagrees with Python coordinated's: // // * If neither "lease_time" nor "status" is specified, // Python coordinated immediately returns False without // checking if workUnitKey is valid // // * Python coordinated allows arbitrary status changes, // including AVAILABLE -> FINISHED // // * This openly ignores "worker_id", as distinct from Python // coordinated, which logs an obscure warning and changes it, // but only on a renew var ( attempt coordinate.Attempt changed bool err error status coordinate.AttemptStatus uwuOptions UpdateWorkUnitOptions workSpec coordinate.WorkSpec workUnit coordinate.WorkUnit ) err = decode(&uwuOptions, options) if err == nil { workSpec, err = jobs.Namespace.WorkSpec(workSpecName) } if err == nil { workUnit, err = workSpec.WorkUnit(workUnitKey) } if err == nil { if workUnit == nil { return false, fmt.Sprintf("no such work unit key=%v", workUnitKey), nil } } if err == nil { attempt, err = workUnit.ActiveAttempt() } if err == nil && attempt != nil { status, err = attempt.Status() } if err == nil && attempt != nil { if status == coordinate.Expired || status == coordinate.Retryable { // The Python Coordinate API sees both of these // statuses as "available", and we want to fall // into the next block. attempt = nil } } if err == nil && attempt == nil { // Caller is trying to manipulate an AVAILABLE work // unit. Cowardly refuse to start a new attempt on // their behalf, or to update the persistent work unit // data this way. (In theory there's no reason we // *couldn't* do either, though I'm not aware of any // callers that do; add_work_unit will replace // existing work units and is the more typical way to // refresh data.) err = errors.New("update_work_unit will not adjust an available work unit") } if err == nil { switch status { case coordinate.Pending: changed = true // or there's an error switch uwuOptions.Status { case 0, Pending: err = uwuRenew(attempt, uwuOptions) case Available: err = attempt.Expire(uwuOptions.Data) case Finished: err = attempt.Finish(uwuOptions.Data) case Failed: err = attempt.Fail(uwuOptions.Data) default: err = errors.New("update_work_unit invalid status") } case coordinate.Expired: err = errors.New("update_work_unit logic error, trying to refresh expired unit") case coordinate.Finished: switch uwuOptions.Status { case 0, Finished: changed = false // no-op case Available: err = workUnit.ClearActiveAttempt() changed = true case Failed: changed = false // see below default: err = errors.New("update_work_unit cannot change finished unit") } case coordinate.Failed: switch uwuOptions.Status { case 0, Failed: changed = false // no-op case Available: // "retry" err = workUnit.ClearActiveAttempt() changed = true case Finished: // The Python worker, with two separate // processes, has a race wherein there // could be 15 seconds to go, the parent // kills off the child, and the child // finishes successfully, all at the same // time. In that case the successful // finish should win. err = attempt.Finish(nil) changed = true default: err = errors.New("update_work_unit cannot change failed unit") } case coordinate.Retryable: err = errors.New("update_work_unit logic error, trying to refresh retryable unit") default: err = fmt.Errorf("update_work_unit invalid attempt status %+v", status) } } return changed && err == nil, "", err }