# 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.