| 1604 | 1604 | | self.starts_wanted.set(true); |
| 1605 | 1605 | | return Ok(()); |
| 1606 | 1606 | | } |
| 1607 | | − | self.start_queued_except(&std::collections::HashSet::new()).await |
| 1607 | + | self.start_queued_except(&std::collections::HashSet::new()).await.map(|_| ()) |
| 1608 | + | } |
| 1609 | + | |
| 1610 | + | /// [`Self::start_queued`], and while the runner refuses jobs for want of |
| 1611 | + | /// a machine, again after each of MACHINE_RETRY_MS rather than at the |
| 1612 | + | /// next sweep (#292). Only where nothing waits on it: after a request's |
| 1613 | + | /// answer, and at the end of the sweep. |
| 1614 | + | pub async fn start_queued_retrying(&self, held: &std::collections::HashSet<String>) -> Result<()> { |
| 1615 | + | for pause in crate::MACHINE_RETRY_MS { |
| 1616 | + | if !self.start_queued_except(held).await? { |
| 1617 | + | return Ok(()); |
| 1618 | + | } |
| 1619 | + | worker::Delay::from(std::time::Duration::from_millis(pause)).await; |
| 1620 | + | } |
| 1621 | + | self.start_queued_except(held).await.map(|_| ()) |
| 1608 | 1622 | | } |
| 1609 | 1623 | | |
| 1610 | | − | /// [`Self::start_queued`], leaving `held` for a later sweep. |
| 1611 | | − | async fn start_queued_except(&self, held: &std::collections::HashSet<String>) -> Result<()> { |
| 1624 | + | /// [`Self::start_queued`], leaving `held` for a later sweep. Returns |
| 1625 | + | /// whether the runner refused any job for want of a machine. |
| 1626 | + | async fn start_queued_except(&self, held: &std::collections::HashSet<String>) -> Result<bool> { |
| 1612 | 1627 | | let queued = self |
| 1613 | 1628 | | .db |
| 1614 | 1629 | | // Self-hosted jobs are taken by their runners (runners.rs). |
| ⋯ |
| 1727 | 1742 | | .unwrap_or_else(|error| fail(FailureCode::Conflict, format!("The runner could not be reached: {error}"))) |
| 1728 | 1743 | | }); |
| 1729 | 1744 | | let started = futures_util::future::join_all(starts).await; |
| 1745 | + | let mut no_machine = false; |
| 1730 | 1746 | | for ((job, at, run, _), started) in claimed_jobs.into_iter().zip(started) { |
| 1731 | 1747 | | if let Outcome::Fail(refused) = started { |
| 1732 | 1748 | | // No machine free (every sandbox of its class taken, or a |
| 1733 | | − | // rollout replacing them): back in the queue, tried again at |
| 1734 | | − | // the next sweep, until it has waited WAIT_FOR_MACHINE_MS. |
| 1749 | + | // rollout replacing them): back in the queue, tried again |
| 1750 | + | // within seconds (start_queued_retrying) or at the next |
| 1751 | + | // sweep, until it has waited WAIT_FOR_MACHINE_MS. |
| 1735 | 1752 | | if refused.code == FailureCode::Unavailable { |
| 1736 | 1753 | | let since = job.waiting_since.clone().unwrap_or_else(|| at.clone()); |
| 1737 | 1754 | | if crate::waited_for_a_machine(Some(&since), now_ms()) { |
| ⋯ |
| 1749 | 1766 | | .run() |
| 1750 | 1767 | | .await?; |
| 1751 | 1768 | | worker::console_warn!("actions: job {} waits for a machine: {}", job.id, refused.message); |
| 1769 | + | no_machine = true; |
| 1752 | 1770 | | continue; |
| 1753 | 1771 | | } |
| 1754 | 1772 | | // Billing would not pay for a scheduled run's job: the |
| ⋯ |
| 1767 | 1785 | | self.job_started(&job.id).await?; |
| 1768 | 1786 | | } |
| 1769 | 1787 | | } |
| 1770 | | − | Ok(()) |
| 1788 | + | Ok(no_machine) |
| 1771 | 1789 | | } |
| 1772 | 1790 | | |
| 1773 | 1791 | | /// Finishes a job and moves its run along. |
| ⋯ |
| 2977 | 2995 | | { |
| 2978 | 2996 | | worker::console_error!("actions: the logs' sweep failed: {error}"); |
| 2979 | 2997 | | } |
| 2980 | | − | self.start_queued_except(&lost).await |
| 2998 | + | self.start_queued_retrying(&lost).await |
| 2981 | 2999 | | } |
| 2982 | 3000 | | |
| 2983 | 3001 | | /// A job whose sandbox never reported (`never_reported`): back in the |