| 253 | 253 | | /// for another (migration 0020): good for `brokered_secrets` only. |
| 254 | 254 | | #[serde(default)] |
| 255 | 255 | | pub first_token_hash: Option<String>, |
| 256 | + | /// Since when it has waited for a machine the runner had none of |
| 257 | + | /// (migration 0025). |
| 258 | + | #[serde(default)] |
| 259 | + | pub waiting_since: Option<String>, |
| 256 | 260 | | } |
| 257 | 261 | | |
| 258 | 262 | | /// How long a cancelled job has to run its `if: always()` and `cancelled()` |
| ⋯ |
| 1663 | 1667 | | let claimed = self |
| 1664 | 1668 | | .db |
| 1665 | 1669 | | .prepare( |
| 1666 | | − | "UPDATE jobs SET status = 'in_progress', token_hash = ?, first_token_hash = NULL, started_at = ?, seen_at = ? WHERE id = ? AND status = 'queued' RETURNING id", |
| 1670 | + | "UPDATE jobs SET status = 'in_progress', token_hash = ?, first_token_hash = NULL, started_at = ?, seen_at = ?, waiting_since = NULL WHERE id = ? AND status = 'queued' RETURNING id", |
| 1667 | 1671 | | ) |
| 1668 | 1672 | | .bind(&[sha256_hex(&token).into(), at.as_str().into(), at.as_str().into(), job.id.as_str().into()])? |
| 1669 | 1673 | | .first::<Value>(None) |
| ⋯ |
| 1703 | 1707 | | .await |
| 1704 | 1708 | | .unwrap_or_else(|error| fail(FailureCode::Conflict, format!("The runner could not be reached: {error}"))); |
| 1705 | 1709 | | if let Outcome::Fail(refused) = started { |
| 1710 | + | // No machine free (every sandbox of its class taken, or a |
| 1711 | + | // rollout replacing them): back in the queue, tried again at |
| 1712 | + | // the next sweep, until it has waited WAIT_FOR_MACHINE_MS. |
| 1713 | + | // The sandboxes after it would find none either, so this |
| 1714 | + | // sweep starts no more. |
| 1715 | + | if refused.code == FailureCode::Unavailable { |
| 1716 | + | let since = job.waiting_since.clone().unwrap_or_else(|| at.clone()); |
| 1717 | + | if crate::waited_for_a_machine(Some(&since), now_ms()) { |
| 1718 | + | let minutes = crate::WAIT_FOR_MACHINE_MS / 60_000; |
| 1719 | + | let reason = format!("{} None came free in {minutes} minutes.", refused.message); |
| 1720 | + | Box::pin(self.finish_job(&job.id, "failure", Some(&reason), None)).await?; |
| 1721 | + | continue; |
| 1722 | + | } |
| 1723 | + | self.db |
| 1724 | + | .prepare( |
| 1725 | + | "UPDATE jobs SET status = 'queued', token_hash = NULL, started_at = NULL, seen_at = NULL, waiting_since = ?, reason = ? |
| 1726 | + | WHERE id = ? AND status = 'in_progress'", |
| 1727 | + | ) |
| 1728 | + | .bind(&[since.as_str().into(), refused.message.as_str().into(), job.id.as_str().into()])? |
| 1729 | + | .run() |
| 1730 | + | .await?; |
| 1731 | + | worker::console_warn!("actions: job {} waits for a machine: {}", job.id, refused.message); |
| 1732 | + | break; |
| 1733 | + | } |
| 1706 | 1734 | | // Billing would not pay for a scheduled run's job: the |
| 1707 | 1735 | | // schedule waits rather than making runs only to refuse them. |
| 1708 | 1736 | | if refused.code == FailureCode::PaymentRequired |