Skip to content

Errors and sentinels

Sentinel Message prefix Means
dispatch.ErrNotFound dispatch: not found no such job, adapter or object
dispatch.ErrUnsupported dispatch: unsupported this cluster or adapter can't do it
dispatch.ErrInvalid dispatch: invalid the request is malformed, or not allowed in the job's current state
dispatch.ErrRejected dispatch: rejected refused: nobody would enforce a rule, an adapter's answer is incoherent, the scheduler said no
dispatch.ErrUnauthorized dispatch: unauthorized this actor may not do it
dispatch.ErrUnreachable dispatch: unreachable the scheduler can't be reached
dispatch.ErrReadOnly dispatch: read-only a write on a read-only backend
dispatch.ErrUnavailable dispatch: record unavailable the record store can't be reached, or a commit couldn't get through before the context ended
dispatch.ErrOutcomeUnknown dispatch: outcome unknown a backend call whose effect can't be determined
dispatch.ErrAlreadyRegistered dispatch: already registered a second registration of an adapter or extension

Test with errors.Is. A sentinel classifies an error and is added to its chain, so the error beneath it (a store's, a scheduler's) survives. The dispatch.Err* values are the same values as model.Err*; the root package re-exports them. model.Invalidf builds an error classified as ErrInvalid.

Validation errors

JobSpec.Validate, RecoveryPolicy.Validate and Extensions.Validate report every refusal they find, not only the first, joined with errors.Join. Each refusal is a *dispatch.ValidationError (the same type as *model.ValidationError) and is classified as ErrInvalid, so errors.Is(err, dispatch.ErrInvalid) holds for the joined error and for each refusal in it.

Field Holds
Path a JSON Pointer (RFC 6901) to the offending value, over the JSON field names of the record schema
Msg what is wrong with it

JobSpec.Validate paths start at the spec: /nodes, /command/0, /env/FOO, /recovery/rules/0/for, /recovery/rules/1/on/0, /extensions/1. RecoveryPolicy.Validate paths start at the policy (/rules/0/for), and Extensions.Validate paths at the list (/1). An env key containing / or ~ is escaped as RFC 6901 says (~1, ~0).

A refusal prints as dispatch: invalid: <path>: <msg>, for example dispatch: invalid: /recovery/rules/0/for: Hold needs a positive For; the joined error prints one refusal per line. errors.As(err, &v) on the joined error finds the first refusal. To read every one:

if joined, ok := err.(interface{ Unwrap() []error }); ok {
    for _, e := range joined.Unwrap() {
        var v *dispatch.ValidationError
        if errors.As(e, &v) {
            fmt.Println(v.Path, v.Msg)
        }
    }
}

Other packages' sentinels

Sentinel Means
records.ErrConflict the record's revision moved since it was read; read again and decide again
records.ErrNotOwner a commit carried a lease that's no longer the current owner's
records.ErrOwned Acquire found the record owned by another holder
records.ErrNotFound no such record; wraps dispatch.ErrNotFound
records.ErrSchema an entry or header whose schema is unknown or whose body doesn't decode strictly
records.ErrUnknownName an enum name in a record entry this build doesn't know
supervise.ErrDisplaced the supervisor lost ownership of the job, and stopped
checkpoint.ErrMalformed an entry name, epoch, step or framework marker that doesn't parse
checkpoint.ErrInvalid an argument the operation refuses
checkpoint.ErrExists the publication's entry already exists; it wasn't touched
memory.ErrFinished Open of an object that already ended; wraps ErrRejected
memory.ErrExpired a hold, release or requeue call that started past its deadline; wraps ErrRejected
memory.ErrStaleHold a release naming a hold instance other than the one in force; wraps ErrRejected
memory.ErrNotApplicable an Env action the object's state or the shape doesn't allow; wraps ErrRejected
memory.ErrCrashed a Requeue ended between its steps by the harness's WithCrashBetweenSteps; wraps ErrOutcomeUnknown
creds.ErrNoAnswer a credential source whose location is absent from this node; the next source is tried
creds.ErrRefused a credential source that is present but refused, for its content or its permissions; resolving stops
creds.ErrNoSource no credential source in the list answered
creds.ErrInvalid a source list or location that doesn't parse
proxy.ErrInvalid a proxy URL that isn't http(s)://host[:port], or carries userinfo, a path, a query or a fragment
s3.ErrNoConditionalWrites the store overwrote an object that a put with If-None-Match: * should have left alone
s3.ErrInvalid an argument or configuration the S3 store refuses
s3.ErrProtocol an answer from the store that breaks the S3 protocol

creds, proxy and s3 are checkpoint/creds, checkpoint/proxy and checkpoint/s3, the packages under cmd/publish.

Each adapter defines its own sentinels as well, classified by the ones above: slurm.ErrExpired and kubeflow.ErrStaleHold wrap ErrRejected, and kubeflow.ErrPodsRemain wraps ErrUnreachable. Their pages list the rest, and which scheduler answers map to which sentinel: Slurm's errors and Kubeflow's error mapping.

What each call returns

Registry and Open

Call Returns
dispatch.Register ErrInvalid for an empty name or nil factory; ErrAlreadyRegistered for a name already taken
model.RegisterExtension ErrInvalid for a nil, unnamed or pointer prototype; ErrAlreadyRegistered for a namespace and name already taken
dispatch.Open ErrNotFound when no adapter is registered under the name; ErrInvalid when Config.Adapter doesn't encode as JSON; otherwise whatever the factory returns

Observer

Call Returns
Get the backend's Find error; with a store, the store's error and ErrInvalid from a record that doesn't fold
List ErrUnsupported without a store; otherwise as Get, for each job

Operator

Every Operator call returns ErrInvalid for an actor with no kind or no id. Store errors reach you as ErrUnavailable, ErrNotFound, ErrInvalid, records.ErrConflict or records.ErrNotOwner; any other store error is wrapped in ErrUnavailable. Control intents are committed with records.CommitControl, which retries a revision conflict until the context ends, then returns ErrUnavailable wrapping the context's error.

Call Refuses with
Submit ErrInvalid for an invalid actor, or a negative WithSubmitRetries, WithCleanupDeadline or WithUnresolvedAge; everything MakePlan refuses; ErrRejected when the idempotency key names a job with a different spec
Cancel the backend's StopJob error, returned with a receipt whose Committed is true: the cancel is recorded even if the stop failed
Hold ErrInvalid for a deadline not in the future, or a job that's cancelled or finished
Release ErrInvalid when the job isn't held, the hold is a recovery hold, or the job is cancelled or finished
Resubmit ErrInvalid unless a recovery hold is in force and the job isn't cancelled or finished
Supersede ErrInvalid unless the job is finished; then as Submit
GiveUp nothing of its own; on a finished job it records the intent and changes nothing
Abandon ErrUnauthorized unless the actor is a human; ErrInvalid for a target naming neither or both of a submission and a foreign object, or a negative attempt; ErrInvalid for a submission or attempt no escalation names yet, or a foreign target the record doesn't hold as a foreign object
Requeue ErrUnsupported when the adapter has no Requeuer; ErrInvalid for a job that's cancelled or finished

Stopper

Call Returns
EmergencyStop ErrInvalid for an invalid actor; otherwise the backend's StopJob error, with its report

Supervisor

Call Returns
supervise.New ErrInvalid for a nil backend or store, a drain or poll that isn't positive, or a poll slower than an evidence window the adapter declares (when it has no Watcher)
Supervisor.Run nil once the record settles; the context's error once it ends; records.ErrOwned if another supervisor holds the job; ErrDisplaced if ownership is lost; records.ErrNotFound when the store has no such record; an error wrapping ErrInvalid when the record doesn't fold or a step can't be executed
Supervisor.Reconcile the store's List error; otherwise every unsettled record is visited, and the errors are joined, one per job: an Acquire error other than ErrOwned, or a pass's error

Run retries every other error at its poll interval: a backend Find that fails, a store read that fails, a commit whose reply was lost.

How the supervisor reads an adapter's errors

From Error Recorded as
Start ErrRejected, ErrInvalid, ErrUnsupported a refused attempt
Start anything else, ErrOutcomeUnknown included an attempt with an unknown outcome, which may be retried
Hold ErrRejected, ErrUnsupported the hold refused; the record stops holding
Open, Stop, Release, Requeue, RestoreRequeueBit any nothing; the next pass's Find shows what happened
Ceased any a cessation fact that says not ceased, with the error's text as its detail
StopJob, Find any the pass ends and is retried

Each adapter's error page says which of its Start errors are refusals and which leave the outcome unknown: Slurm and Kubeflow.