Waiting and Recovery¶
The scheduler splitting a flake job across workers, parking an evaluation without progress, re-offering a returned job and cleaning up after a server restart.
flowchart LR
P[Queued / Fetching / Evaluating*] -- no capable worker --> W[Waiting]
B[Building] -- no worker fitting / graph stuck --> W
W -- eval_workers, draining cleared --> P
W -- workers fit / heal freeing a shared build --> B
Fetch and Eval Split¶
assign_queued_evals (gradient-scheduler/src/loops/eval.rs) can ask the pool once per pass for an idle evaluation-only worker (WorkerPool::has_idle_eval_only_worker: active, eval without fetch, no assigned job).
| Pool at assignment | Job steps |
|---|---|
| An idle evaluation-only worker present | [FetchFlake], then a follow-up [EvaluateFlake, EvaluateDerivations] |
| Otherwise | [FetchFlake, EvaluateFlake, EvaluateDerivations] in one job |
- Decision per pass: one idle worker will split every evaluation queued in that pass.
- Source: a repository evaluation must carry
FlakeSource::Repositoryas source. A/nix/store/...source (gradient buildupload) must carryFlakeSource::Cachedas source. That source will still runFetchFlaketo resolve inputs with the project SSH key (flake_job_for_eval_source). - Routing:
WorkerCaps::can_eval(gradient-pool/src/worker_caps.rs) will requirefetchfor aFetchFlakestep andevalfor an evaluation step. - Follow-up:
handle_job_completed(job_handlers/build_status.rs) will react toJobCompletedof a fetch-only job (is_fetch_only). The handler must readevaluation.flake_sourceand enqueuePendingEvalJob::cached_followupunder the sameeval:{id}key. The follow-up will carryFlakeSource::Cachedplus the source as a required path. A missingflake_sourcewill fail the evaluation permanently. - Worker side: the evaluating worker will substitute the source from the cache and evaluate
path:<store path>(gradient-worker/src/executor/eval.rs). - Steering:
ReserveFetchWorkersRule(gradient-pool/src/score/rules/builtin.rs) will score a fetch-capable worker-300 * (1 - idle / total)for a job withoutFetchFlake. The penalty will fade as workers go idle. The rule will never refuse the job. See Scoring.
Waiting Reasons¶
The waiting pass, refresh_waiting_state (gradient-scheduler/src/waiting_state.rs), can start at the end of every build assignment pass (5 s tick or kick) over every in-flight evaluation. The pass will write the reason into evaluation.waiting_reason only on a change of its JSON.
WaitingReason |
Parked from | Unparked | Owner |
|---|---|---|---|
eval_workers { capability, connected_workers } |
Fetching without a fetch worker. Queued, EvaluatingFlake, EvaluatingDerivation without an eval worker |
To Queued once the capability is connected |
Waiting pass |
workers { unmet, connected_workers, available_architectures } |
Building with no worker fitting the (architecture, required_features) of any blocking shared build |
To Building once one can fit. Aborted after 300 s on the same unmet set, unless wait_for_workers is set on the task |
Waiting pass |
graph_stuck { pending_shared_builds } |
Building with a fitting worker for every blocking shared build, yet none can be handed out |
To Building once the heal can free a shared build |
Waiting pass |
draining |
Every in-flight evaluation during an instance drain | To Queued at the end of draining or at startup (unpark_draining_evals) |
Waiting pass |
approval, no_cache, cache_storage_full |
Trigger conditions | Webhook and cache hooks (gradient-ci/src/unpark.rs) |
Never touched by the waiting pass |
- Pre-build parks skip each shared build the evaluation already batched. A stall mid-walk will still park.
- Blocking builds are the named shared builds kept by
blocks_evaluation, in a status that can still be wanted. A shared build wanted by nothing is left out. - Counters first:
phase_from_counterscan decide without reading a shared build.named = 0will hand the evaluation to the pre-build rules.active = 0will finalize the evaluation throughcheck_evaluation_done.building > 0will keep theBuildingstatus. - Unbuildable abort:
unbuildable(gradient-scheduler/src/unbuildable.rs) can hand aworkerspark with a non-emptyunmetset back to the scheduler. The hand-back will take place once the park is older than 300 s. The task must also havewait_for_workers = falseset.abort_unbuildable_evaluationwill mark the evaluationAbortedand abort its shared builds. The function will also record a warning naming each missing architecture and feature set. The grace must keep a server restart or worker redeploy from aborting work before the pool is back. - Shared build read:
BuildabilityCheckerwill otherwise read every blocking shared build.AssessmentMemocan reuse the verdict for up to 60 s while the counters and the pool fingerprint stay equal. An empty read will recount the drifted counters and finalize.
Graph-Stuck Heal¶
A workers verdict with an empty unmet set will turn into the heal. attempt_graph_unstick must send Transition::Repair { scope: RepairScope::Unstick } to the graph writer. The function will then re-assess without the memo. The repair pass repair_build_graph (gradient-db/src/graph/repair.rs) will run the steps below. The first failed step will fail the transition.
requeue_failed_closure: thawing failed shared builds in the closure toCreated.Unstickwill leave a deterministic build failure and its subtree alone.repair_cached_shared_builds_for_eval: completing shared builds with every output in the cache, thenadvance_fetchablefor the builds needing them.repair_dependency_failed: failing the builds needing a build with a lasting failure.adopt_pending_closure: naming orphaned pending shared builds for the evaluation, then updating their needs-build marks.promote_closure: queueing every build now passing the queue conditions.
| Driver | When |
|---|---|
| Waiting pass | On entry to graph_stuck and on every change of pending_shared_builds (unstick_due) |
graph-stuck-reheal pass |
Every metrics.graphConsistencyIntervalSecs (300 s) for each graph_stuck evaluation |
| Counters | Queueing the set the moment its queue conditions hold |
- Lost
.drv:recover_drv_stuck_evalswill check evaluations ingraph_stuckfor over 120 s. Its target is a walked shared build needing a build and not available in a cache. That build must have every dependency done and no.drvNAR.trigger_drv_recoverywill start aDrvRecoveryevaluation of the same commit for such a build. ADrvRecoveryevaluation stuck the same way will fail.
Re-Offering Returned Jobs¶
Offers are deltas. The server will send a worker only candidates missing from its sent_candidates set. The worker must score every offered candidate. Each offer will reset the set to the candidates currently visible to the worker. A claimed, finished or aborted job will drop out of the set. Returning a job to the pool will clear its sent flag.
| Event | Mechanism |
|---|---|
| Enqueue | SchedulerMsg::Enqueue calling remove_sent_candidate for every worker, also for the cached follow-up reusing eval:{id} |
| Reject | SchedulerMsg::Rejected returning the job to pending and clearing its sent flag |
| Assignment pass | SchedulerMsg::ReOffer bumping the offer generation while any job is pending. Sessions then pull the delta |
Startup Recovery¶
recover_interrupted_work (gradient-db/src/maintenance/recovery.rs) will run once at server start (gradient-web/src/lib.rs), before unpark_draining_evals starts.
- Assignments: all open
dispatched_jobrows close asAbandonedrows. Both assignment selections refuse a job with an open row. Re-queued work would otherwise wait for its worker to reconnect or for the 1800 s check for abandoned assignments. - Attempts:
Runningbuild attempts turnAborted. - Shared builds: every
Buildingshared build must turnQueuedfirst.unpromote_ungatedwill move those with failing queue conditions back toCreatedstatus. - Evaluations:
Fetching,EvaluatingFlakeandEvaluatingDerivationevaluations turnQueued, with a phase event each. Their eval job was on a worker.Buildingevaluations keep their status and their re-queued builds. -
Clusters: all open cluster attempts close (
PrepareFailedwhen unstarted,Abortedotherwise). ARunningcluster will go back toQueued, or toAbortedwhen a member evaluation or shared build can no longer finish. -
Queuedevaluations return through the eval assignment pass.Waitingones through the waiting pass. - Recovery will skip the live effects of
update_evaluation_statuscalls. The rows are consistent on their own.