contributing/RUNS-AND-JOBS.md
Rama master · réplica de lectura del repositorio original.
# Runs and jobs
## Introduction
Run is the primary unit of workload in `dstack`. Users can:
1. Submit a run using `dstack apply` or the API.
2. Stop a run using `dstack stop` or the API.
Runs are created from run configurations. There are three types of run configurations:
1. `dev-environment` — runs a VS Code server.
2. `task` — runs the user's bash script until completion.
3. `service` — runs the user's bash script and exposes a port through [dstack-proxy](PROXY.md).
A run can spawn one or multiple jobs, depending on the configuration. A task that specifies multiple `nodes` spawns a job for every node (a multi-node task). A service that specifies multiple `replicas` spawns a job for every replica. A job submission is always assigned to one particular instance. If a job fails and the configuration allows retrying, the server creates a new job submission for the job.
## Run's Lifecycle
- STEP 1: The user submits the run. `services.runs.submit_run` creates jobs with status `SUBMITTED`. The run starts in `SUBMITTED`.
- STEP 2: `RunPipeline` continuously processes unfinished runs.
- For active runs, it derives the run status from the latest job states in priority order:
1. If any non-retryable failure is present, the run becomes `TERMINATING` with the relevant `RunTerminationReason`.
2. If `stop_criteria == MASTER_DONE` and the master job is done, the run becomes `TERMINATING` with `ALL_JOBS_DONE`.
3. Otherwise, if any job is `RUNNING`, the run becomes `RUNNING`.
4. Otherwise, if any job is `PROVISIONING` or `PULLING`, the run becomes `PROVISIONING`.
5. Otherwise, if jobs are still waiting for placement or provisioning, the run stays `SUBMITTED`.
6. Otherwise, if all contributing jobs are `DONE`, the run becomes `TERMINATING` with `ALL_JOBS_DONE`.
7. Otherwise, if no active replicas remain and the run should be retried, the run becomes `PENDING`.
- Retryable replica failures are handled before the final transition is applied:
- If a replica fails with a retryable reason while other replicas are still active, `RunPipeline` creates a new `SUBMITTED` submission for that replica and terminates the old jobs in that replica.
- If all remaining work is retryable, the run ends up in `PENDING`.
- STEP 3: If the run is `PENDING`, `RunPipeline` processes it in the pending phase.
- For retrying runs, it waits for an exponential backoff before resubmitting.
- For scheduled runs, it waits until `next_triggered_at`.
- For scaled-to-zero services, it can keep the run in `PENDING` until autoscaling wants replicas again.
- Once the run is ready to continue, `RunPipeline` creates new `SUBMITTED` jobs and moves the run back to `SUBMITTED`.
- STEP 4: If the run is `TERMINATING`, `RunPipeline` marks active jobs as `TERMINATING` and assigns the corresponding `JobTerminationReason`.
- STEP 5: Once all jobs are finished, the terminating phase of `RunPipeline` either:
- assigns the final run status (`TERMINATED`, `DONE`, or `FAILED`), or
- for scheduled runs that were not stopped or aborted by the user, returns the run to `PENDING` and computes a new `next_triggered_at`.
### Services
Services' run lifecycle has some modifications:
- During STEP 1, the service itself is registered on the gateway or the in-server proxy. If the gateway is not accessible or the domain name is taken, submission fails.
- During STEP 2, active run processing also computes desired replica counts from gateway stats and handles scale-up, scale-down, rolling deployment, and cleanup of removed replica groups.
- During STEP 2, jobs already marked `SCALED_DOWN` do not contribute to the run status.
- During STEP 3, a service can stay in `PENDING` when autoscaling currently wants zero replicas.
- During STEP 5, the terminating phase of `RunPipeline` unregisters the service from the gateway.
### When can the job be retried?
`dstack` retries the run only if:
- The configuration enables `retry`.
- The job termination reason is covered by `retry.on_events`.
- The `retry.duration` is not exceeded.
## Job's Lifecycle
- STEP 1: A newly submitted job has status `SUBMITTED`. It is not assigned to any instance yet.
- STEP 2: `JobSubmittedPipeline` assigns the job in two phases:
- Assignment: claim an existing instance or reserve a *placeholder* `InstanceModel`. Placeholders are `PENDING` instances that reserve an `instance_num` and a `nodes.max` slot. `InstancePipeline` ignores them.
- Provisioning: reuse the existing instance, or cloud-provision and promote the placeholder to `PROVISIONING`.
- On success, the job becomes `PROVISIONING`.
- On failure, the job becomes `TERMINATING`. `JobTerminatingPipeline` later assigns the final failed status.
- STEP 3: `JobRunningPipeline` processes `PROVISIONING`, `PULLING`, and `RUNNING` jobs.
- While `dstack-shim` / `dstack-runner` is not responding, the job stays `PROVISIONING`.
- Once `dstack-shim` (for VM-featured backends) becomes available, the pipeline submits the image and the job becomes `PULLING`.
- Once `dstack-runner` inside the container becomes available, the pipeline uploads the code and job spec, and the job becomes `RUNNING`.
- While the job is `RUNNING`, the pipeline keeps collecting logs and runner status.
- If startup, runner communication, or replica registration fails, the job becomes `TERMINATING`.
- STEP 4: Once the job is actually ready, `JobRunningPipeline` initializes probes.
- STEP 5: `JobTerminatingPipeline` processes `TERMINATING` jobs.
- If the job has `remove_at` in the future, it waits. This gives the job time for a graceful stop.
- Once `remove_at` is in the past, it stops the container, detaches volumes, unregisters service replicas if needed, and releases the instance assignment.
- If some volumes are not detached yet, the job stays `TERMINATING` and is retried.
- When cleanup is complete, the job becomes `TERMINATED`, `DONE`, `FAILED`, or `ABORTED` based on `JobTerminationReason`.
### Services' Jobs
Services' jobs lifecycle has some modifications:
- During STEP 3, once the primary job of a replica is `RUNNING` and ready to receive traffic, `JobRunningPipeline` registers that replica on the gateway. If the gateway is not accessible, the job fails with a gateway-related termination reason.
- During STEP 5, `JobTerminatingPipeline` unregisters the replica from receiving requests before the job is fully cleaned up.