From fee57241a2939d768057065a890d44235f05d86e Mon Sep 17 00:00:00 2001 From: Carbonhell Date: Sat, 15 Nov 2025 16:10:43 +0100 Subject: [PATCH 1/7] feat(cli): add cratesfyi command to queue rebuilds for specific broken nightly dates --- src/bin/cratesfyi.rs | 23 ++++- src/build_queue.rs | 234 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 4 +- 3 files changed, 259 insertions(+), 2 deletions(-) diff --git a/src/bin/cratesfyi.rs b/src/bin/cratesfyi.rs index 0f28c637c..fb0aa3bf8 100644 --- a/src/bin/cratesfyi.rs +++ b/src/bin/cratesfyi.rs @@ -1,9 +1,10 @@ use anyhow::{Context as _, Result, anyhow}; +use chrono::NaiveDate; use clap::{Parser, Subcommand, ValueEnum}; use docs_rs::{ Config, Context, Index, PackageKind, RustwideBuilder, db::{self, CrateId, Overrides, add_path_into_database, types::version::Version}, - start_background_metrics_webserver, start_web_server, + queue_rebuilds_faulty_rustdoc, start_background_metrics_webserver, start_web_server, utils::{ ConfigName, daemon::start_background_service_metric_collector, get_config, get_crate_pattern_and_priority, list_crate_priorities, queue_builder, @@ -274,6 +275,17 @@ enum QueueSubcommand { #[arg(long, conflicts_with("reference"))] head: bool, }, + + /// Queue rebuilds for broken nightly versions of rustdoc + RebuildBrokenNightly { + /// Start date of nightly builds to rebuild (inclusive) + #[arg(name = "START", short = 's', long = "start")] + start_nightly_date: NaiveDate, + + /// End date of nightly builds to rebuild (exclusive, optional) + #[arg(name = "END", short = 'e', long = "end")] + end_nightly_date: Option, + }, } impl QueueSubcommand { @@ -316,6 +328,15 @@ impl QueueSubcommand { } Self::DefaultPriority { subcommand } => subcommand.handle_args(ctx)?, + + Self::RebuildBrokenNightly { start_nightly_date, end_nightly_date } => { + ctx.runtime.block_on(async move { + let mut conn = ctx.pool.get_async().await?; + let queued_rebuilds_amount = queue_rebuilds_faulty_rustdoc(&mut conn, &ctx.async_build_queue, &start_nightly_date, &end_nightly_date).await?; + println!("Queued {queued_rebuilds_amount} rebuilds for broken nightly versions of rustdoc"); + Ok::<(), anyhow::Error>(()) + })? + } } Ok(()) } diff --git a/src/build_queue.rs b/src/build_queue.rs index 84c4b037b..6e91afb37 100644 --- a/src/build_queue.rs +++ b/src/build_queue.rs @@ -13,6 +13,7 @@ use crate::{ utils::{ConfigName, get_config, get_crate_priority, report_error, retry, set_config}, }; use anyhow::Context as _; +use chrono::NaiveDate; use fn_error_context::context; use futures_util::{StreamExt, stream::TryStreamExt}; use opentelemetry::metrics::Counter; @@ -44,6 +45,8 @@ impl BuildQueueMetrics { /// collapsed in the UI. /// For normal build priorities we use smaller values. pub(crate) const REBUILD_PRIORITY: i32 = 20; +// TODO what value should we use here? +pub(crate) const BROKEN_RUSTDOC_REBUILD_PRIORITY: i32 = 30; #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize)] pub(crate) struct QueuedCrate { @@ -775,9 +778,75 @@ pub async fn queue_rebuilds( Ok(()) } +/// Queue rebuilds for failed crates due to a faulty version of rustdoc +/// +/// It is assumed that the version of rustdoc matches the one of rustc, which is persisted in the DB. +/// The priority of the resulting rebuild requests will be lower than previously failed builds. +/// If a crate is already queued to be rebuilt, it will not be requeued. +/// Start date is inclusive, end date is exclusive. +#[instrument(skip_all)] +pub async fn queue_rebuilds_faulty_rustdoc( + conn: &mut sqlx::PgConnection, + build_queue: &AsyncBuildQueue, + start_nightly_date: &NaiveDate, + end_nightly_date: &Option, +) -> Result { + let end_nightly_date = + end_nightly_date.unwrap_or_else(|| start_nightly_date.succ_opt().unwrap()); + let mut results = sqlx::query!( + r#" +SELECT c.name, + r.version AS "version: Version" +FROM crates AS c + JOIN releases AS r + ON c.latest_version_id = r.id + AND r.rustdoc_status = TRUE + JOIN LATERAL ( + SELECT b.id, + b.build_status, + b.rustc_nightly_date, + COALESCE(b.build_finished, b.build_started) AS last_build_attempt + FROM builds AS b + WHERE b.rid = r.id + ORDER BY last_build_attempt DESC + LIMIT 1 + ) AS b ON b.build_status = 'failure' AND b.rustc_nightly_date >= $1 AND b.rustc_nightly_date < $2 + +"#, start_nightly_date, end_nightly_date + ) + .fetch(&mut *conn); + + let mut results_count = 0; + while let Some(row) = results.next().await { + let row = row?; + + if !build_queue + .has_build_queued(&row.name, &row.version) + .await? + { + results_count += 1; + info!( + "queueing rebuild for {} {} (priority {})...", + &row.name, &row.version, BROKEN_RUSTDOC_REBUILD_PRIORITY + ); + build_queue + .add_crate( + &row.name, + &row.version, + BROKEN_RUSTDOC_REBUILD_PRIORITY, + None, + ) + .await?; + } + } + + Ok(results_count) +} + #[cfg(test)] mod tests { use super::*; + use crate::db::types::BuildStatus; use crate::test::{FakeBuild, TestEnvironment, V1, V2}; use chrono::Utc; @@ -817,6 +886,171 @@ mod tests { Ok(()) } + /// Verifies whether a rebuild is queued for a crate that previously failed with a nightly version of rustdoc. + #[tokio::test(flavor = "multi_thread")] + async fn test_rebuild_broken_rustdoc_specific_date_simple() -> Result<()> { + let env = TestEnvironment::with_config( + TestEnvironment::base_config() + .max_queued_rebuilds(Some(100)) + .build()?, + ) + .await?; + + for i in 1..5 { + let nightly_date = NaiveDate::from_ymd_opt(2020, 10, i).unwrap(); + env.fake_release() + .await + .name(&format!("foo{}", i)) + .version(V1) + .builds(vec![ + FakeBuild::default() + .rustc_version( + format!( + "rustc 1.84.0-nightly (e7c0d2750 {})", + nightly_date.format("%Y-%m-%d") + ) + .as_str(), + ) + .build_status(BuildStatus::Failure), + ]) + .create() + .await?; + } + + let build_queue = env.async_build_queue(); + assert!(build_queue.queued_crates().await?.is_empty()); + + let mut conn = env.async_db().async_conn().await; + queue_rebuilds_faulty_rustdoc( + &mut conn, + build_queue, + &NaiveDate::from_ymd_opt(2020, 10, 3).unwrap(), + &None, + ) + .await?; + + let queue = build_queue.queued_crates().await?; + assert_eq!(queue.len(), 1); + assert_eq!(queue[0].name, "foo3"); + assert_eq!(queue[0].version, V1); + assert_eq!(queue[0].priority, BROKEN_RUSTDOC_REBUILD_PRIORITY); + + Ok(()) + } + + /// Verified whether a rebuild is NOT queued since the latest build for the specific crate is marked as successful. + #[tokio::test(flavor = "multi_thread")] + async fn test_rebuild_broken_rustdoc_specific_date_skipped() -> Result<()> { + let env = TestEnvironment::with_config( + TestEnvironment::base_config() + .max_queued_rebuilds(Some(100)) + .build()?, + ) + .await?; + + env.fake_release() + .await + .name("foo") + .version(V1) + .builds(vec![ + FakeBuild::default() + .rustc_version( + format!( + "rustc 1.84.0-nightly (e7c0d2750 {})", + NaiveDate::from_ymd_opt(2020, 10, 1) + .unwrap() + .format("%Y-%m-%d") + ) + .as_str(), + ) + .build_status(BuildStatus::Failure), + FakeBuild::default() + .rustc_version( + format!( + "rustc 1.84.0-nightly (e7c0d2750 {})", + NaiveDate::from_ymd_opt(2020, 10, 1) + .unwrap() + .format("%Y-%m-%d") + ) + .as_str(), + ) + .build_status(BuildStatus::Success), + ]) + .create() + .await?; + + let build_queue = env.async_build_queue(); + assert!(build_queue.queued_crates().await?.is_empty()); + + let mut conn = env.async_db().async_conn().await; + queue_rebuilds_faulty_rustdoc( + &mut conn, + build_queue, + &NaiveDate::from_ymd_opt(2020, 10, 1).unwrap(), + &None, + ) + .await?; + + let queue = build_queue.queued_crates().await?; + assert_eq!(queue.len(), 0); + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_rebuild_broken_rustdoc_date_range() -> Result<()> { + let env = TestEnvironment::with_config( + TestEnvironment::base_config() + .max_queued_rebuilds(Some(100)) + .build()?, + ) + .await?; + + for i in 1..6 { + let nightly_date = NaiveDate::from_ymd_opt(2020, 10, i).unwrap(); + env.fake_release() + .await + .name(&format!("foo{}", i)) + .version(V1) + .builds(vec![ + FakeBuild::default() + .rustc_version( + format!( + "rustc 1.84.0-nightly (e7c0d2750 {})", + nightly_date.format("%Y-%m-%d") + ) + .as_str(), + ) + .build_status(BuildStatus::Failure), + ]) + .create() + .await?; + } + + let build_queue = env.async_build_queue(); + assert!(build_queue.queued_crates().await?.is_empty()); + + let mut conn = env.async_db().async_conn().await; + queue_rebuilds_faulty_rustdoc( + &mut conn, + build_queue, + &NaiveDate::from_ymd_opt(2020, 10, 3).unwrap(), + &Some(NaiveDate::from_ymd_opt(2020, 10, 5)).unwrap(), + ) + .await?; + + let queue = build_queue.queued_crates().await?; + assert_eq!(queue.len(), 2); + assert_eq!(queue[0].name, "foo3"); + assert_eq!(queue[0].version, V1); + assert_eq!(queue[0].priority, BROKEN_RUSTDOC_REBUILD_PRIORITY); + assert_eq!(queue[1].name, "foo4"); + assert_eq!(queue[1].version, V1); + assert_eq!(queue[1].priority, BROKEN_RUSTDOC_REBUILD_PRIORITY); + + Ok(()) + } + #[tokio::test(flavor = "multi_thread")] async fn test_still_rebuild_when_full_with_failed() -> Result<()> { let env = TestEnvironment::with_config( diff --git a/src/lib.rs b/src/lib.rs index fc9d1423f..e45e42b16 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,9 @@ //! documentation of crates for the Rust Programming Language. #![allow(clippy::cognitive_complexity)] -pub use self::build_queue::{AsyncBuildQueue, BuildQueue, queue_rebuilds}; +pub use self::build_queue::{ + AsyncBuildQueue, BuildQueue, queue_rebuilds, queue_rebuilds_faulty_rustdoc, +}; pub use self::config::Config; pub use self::context::Context; pub use self::docbuilder::PackageKind; From 57442eb5e64035e92bf91ad4a3f8156db8dd4b51 Mon Sep 17 00:00:00 2001 From: Carbonhell Date: Sat, 15 Nov 2025 16:35:43 +0100 Subject: [PATCH 2/7] chore(sqlx): add updated sqlx prepare artifact --- ...067c225de3d323f74d36512c579a6896c68b6.json | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .sqlx/query-64031f2e97958452a6d6da840fd067c225de3d323f74d36512c579a6896c68b6.json diff --git a/.sqlx/query-64031f2e97958452a6d6da840fd067c225de3d323f74d36512c579a6896c68b6.json b/.sqlx/query-64031f2e97958452a6d6da840fd067c225de3d323f74d36512c579a6896c68b6.json new file mode 100644 index 000000000..730433aea --- /dev/null +++ b/.sqlx/query-64031f2e97958452a6d6da840fd067c225de3d323f74d36512c579a6896c68b6.json @@ -0,0 +1,29 @@ +{ + "db_name": "PostgreSQL", + "query": "\nSELECT c.name,\n r.version AS \"version: Version\"\nFROM crates AS c\n JOIN releases AS r\n ON c.latest_version_id = r.id\n AND r.rustdoc_status = TRUE\n JOIN LATERAL (\n SELECT b.id,\n b.build_status,\n b.rustc_nightly_date,\n COALESCE(b.build_finished, b.build_started) AS last_build_attempt\n FROM builds AS b\n WHERE b.rid = r.id\n ORDER BY last_build_attempt DESC\n LIMIT 1\n ) AS b ON b.build_status = 'failure' AND b.rustc_nightly_date >= $1 AND b.rustc_nightly_date < $2\n\n", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "version: Version", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Date", + "Date" + ] + }, + "nullable": [ + false, + false + ] + }, + "hash": "64031f2e97958452a6d6da840fd067c225de3d323f74d36512c579a6896c68b6" +} From 0628c27151b18f0cf9e61d51fdf547557e24b971 Mon Sep 17 00:00:00 2001 From: Carbonhell Date: Sat, 15 Nov 2025 16:40:05 +0100 Subject: [PATCH 3/7] chore(tests): remove unneeded Some().unwrap() --- src/build_queue.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/build_queue.rs b/src/build_queue.rs index 6e91afb37..83edd70ae 100644 --- a/src/build_queue.rs +++ b/src/build_queue.rs @@ -1035,7 +1035,7 @@ mod tests { &mut conn, build_queue, &NaiveDate::from_ymd_opt(2020, 10, 3).unwrap(), - &Some(NaiveDate::from_ymd_opt(2020, 10, 5)).unwrap(), + &NaiveDate::from_ymd_opt(2020, 10, 5), ) .await?; From 1be6f5b74eb39a0af23cde5f485f16e171fe7396 Mon Sep 17 00:00:00 2001 From: Carbonhell Date: Mon, 24 Nov 2025 00:19:09 +0100 Subject: [PATCH 4/7] refactor(queue): centralize definition for common priority levels fix(queue): update query for faulty rustdoc builds to not assume build status implies anything on the rustdoc result, match on all releases of each crate rather than the latest only, and use the release_build_status table to simplify the query fix(logs): use fields for info! instead of string formatting docs(cratesfyi): clarify usage of command arguments for the new RebuildBrokenNightly command chore(tests): remove unneeded test environment config --- src/bin/cratesfyi.rs | 2 +- src/build_queue.rs | 231 +++++++++++++++++++---------------- src/utils/consistency/mod.rs | 7 +- src/utils/queue.rs | 27 ++-- src/web/builds.rs | 7 +- src/web/releases.rs | 16 +-- 6 files changed, 153 insertions(+), 137 deletions(-) diff --git a/src/bin/cratesfyi.rs b/src/bin/cratesfyi.rs index fb0aa3bf8..b8be90127 100644 --- a/src/bin/cratesfyi.rs +++ b/src/bin/cratesfyi.rs @@ -276,7 +276,7 @@ enum QueueSubcommand { head: bool, }, - /// Queue rebuilds for broken nightly versions of rustdoc + /// Queue rebuilds for broken nightly versions of rustdoc, either for a single date (start) or a range (start inclusive, end exclusive) RebuildBrokenNightly { /// Start date of nightly builds to rebuild (inclusive) #[arg(name = "START", short = 's', long = "start")] diff --git a/src/build_queue.rs b/src/build_queue.rs index 83edd70ae..2664c998e 100644 --- a/src/build_queue.rs +++ b/src/build_queue.rs @@ -40,13 +40,21 @@ impl BuildQueueMetrics { } } -/// The static priority for background rebuilds. -/// Used when queueing rebuilds, and when rendering them -/// collapsed in the UI. -/// For normal build priorities we use smaller values. -pub(crate) const REBUILD_PRIORITY: i32 = 20; -// TODO what value should we use here? -pub(crate) const BROKEN_RUSTDOC_REBUILD_PRIORITY: i32 = 30; + + +pub(crate) const PRIORITY_DEFAULT: i32 = 0; +/// Used for workspaces to avoid blocking the queue (done through the cratesfyi CLI, not used in code) +#[allow(dead_code)] +pub(crate) const PRIORITY_DEPRIORITIZED: i32 = 1; +/// Rebuilds triggered from crates.io, see issue #2442 +pub(crate) const PRIORITY_MANUAL_FROM_CRATES_IO: i32 = 5; +/// Used for rebuilds queued through cratesfyi for crate versions failed due to a broken Rustdoc nightly version. +/// Note: a broken rustdoc version does not necessarily imply a failed build. +pub(crate) const PRIORITY_BROKEN_RUSTDOC: i32 = 10; +/// Used by the synchronize cratesfyi command when queueing builds that are in the crates.io index but not in the database. +pub(crate) const PRIORITY_CONSISTENCY_CHECK: i32 = 15; +/// The static priority for background rebuilds, used when queueing rebuilds, and when rendering them collapsed in the UI. +pub(crate) const PRIORITY_CONTINUOUS: i32 = 20; #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize)] pub(crate) struct QueuedCrate { @@ -726,7 +734,7 @@ pub async fn queue_rebuilds( .pending_count_by_priority() .await? .iter() - .filter_map(|(priority, count)| (*priority >= REBUILD_PRIORITY).then_some(count)) + .filter_map(|(priority, count)| (*priority >= PRIORITY_CONTINUOUS).then_some(count)) .sum(); let rebuilds_to_queue = config @@ -770,7 +778,7 @@ pub async fn queue_rebuilds( { info!("queueing rebuild for {} {}...", &row.name, &row.version); build_queue - .add_crate(&row.name, &row.version, REBUILD_PRIORITY, None) + .add_crate(&row.name, &row.version, PRIORITY_CONTINUOUS, None) .await?; } } @@ -799,18 +807,16 @@ SELECT c.name, r.version AS "version: Version" FROM crates AS c JOIN releases AS r - ON c.latest_version_id = r.id - AND r.rustdoc_status = TRUE - JOIN LATERAL ( - SELECT b.id, - b.build_status, - b.rustc_nightly_date, - COALESCE(b.build_finished, b.build_started) AS last_build_attempt - FROM builds AS b - WHERE b.rid = r.id - ORDER BY last_build_attempt DESC - LIMIT 1 - ) AS b ON b.build_status = 'failure' AND b.rustc_nightly_date >= $1 AND b.rustc_nightly_date < $2 + ON c.id = r.crate_id + JOIN release_build_status AS rbs + ON rbs.rid = r.id + AND rbs.build_status != 'in_progress' + JOIN builds AS b + ON b.rid = r.id + AND b.build_finished = rbs.last_build_time + AND b.rustc_nightly_date >= $1 + AND b.rustc_nightly_date < $2 + "#, start_nightly_date, end_nightly_date ) @@ -826,16 +832,13 @@ FROM crates AS c { results_count += 1; info!( - "queueing rebuild for {} {} (priority {})...", - &row.name, &row.version, BROKEN_RUSTDOC_REBUILD_PRIORITY + name=%row.name, + version=%row.version, + priority=PRIORITY_BROKEN_RUSTDOC, + "queueing rebuild" ); build_queue - .add_crate( - &row.name, - &row.version, - BROKEN_RUSTDOC_REBUILD_PRIORITY, - None, - ) + .add_crate(&row.name, &row.version, PRIORITY_BROKEN_RUSTDOC, None) .await?; } } @@ -881,35 +884,41 @@ mod tests { assert_eq!(queue.len(), 1); assert_eq!(queue[0].name, "foo"); assert_eq!(queue[0].version, V1); - assert_eq!(queue[0].priority, REBUILD_PRIORITY); + assert_eq!(queue[0].priority, PRIORITY_CONTINUOUS); Ok(()) } - /// Verifies whether a rebuild is queued for a crate that previously failed with a nightly version of rustdoc. + /// Verifies whether a rebuild is queued for all releases with the latest build performed with a specific nightly version of rustdoc #[tokio::test(flavor = "multi_thread")] async fn test_rebuild_broken_rustdoc_specific_date_simple() -> Result<()> { - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .max_queued_rebuilds(Some(100)) - .build()?, - ) - .await?; + let env = TestEnvironment::new().await?; - for i in 1..5 { - let nightly_date = NaiveDate::from_ymd_opt(2020, 10, i).unwrap(); + // Matrix of test builds (crate name, nightly date, version) + let build_matrix = [ + // Should be skipped since this is not the latest build for this release + ("foo1", NaiveDate::from_ymd_opt(2020, 10, 1).unwrap(), V1), + // All those should match + ("foo1", NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), V1), + ("foo1", NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), V2), + ("foo2", NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), V1), + // Should be skipped since the nightly doesn't match + ("foo2", NaiveDate::from_ymd_opt(2020, 10, 3).unwrap(), V2), + ]; + for build in build_matrix.into_iter() { + let (crate_name, nightly, version) = build; env.fake_release() .await - .name(&format!("foo{}", i)) - .version(V1) + .name(crate_name) + .version(version) .builds(vec![ FakeBuild::default() .rustc_version( format!( "rustc 1.84.0-nightly (e7c0d2750 {})", - nightly_date.format("%Y-%m-%d") + nightly.format("%Y-%m-%d") ) - .as_str(), + .as_str(), ) .build_status(BuildStatus::Failure), ]) @@ -924,60 +933,60 @@ mod tests { queue_rebuilds_faulty_rustdoc( &mut conn, build_queue, - &NaiveDate::from_ymd_opt(2020, 10, 3).unwrap(), + &NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), &None, ) .await?; let queue = build_queue.queued_crates().await?; - assert_eq!(queue.len(), 1); - assert_eq!(queue[0].name, "foo3"); + assert_eq!(queue.len(), 3); + assert_eq!(queue[0].name, "foo1"); assert_eq!(queue[0].version, V1); - assert_eq!(queue[0].priority, BROKEN_RUSTDOC_REBUILD_PRIORITY); + assert_eq!(queue[0].priority, PRIORITY_BROKEN_RUSTDOC); + assert_eq!(queue[1].name, "foo1"); + assert_eq!(queue[1].version, V2); + assert_eq!(queue[1].priority, PRIORITY_BROKEN_RUSTDOC); + assert_eq!(queue[2].name, "foo2"); + assert_eq!(queue[2].version, V1); + assert_eq!(queue[2].priority, PRIORITY_BROKEN_RUSTDOC); Ok(()) } - /// Verified whether a rebuild is NOT queued since the latest build for the specific crate is marked as successful. + /// Verifies whether a rebuild is NOT queued for any crate if the nightly specified doesn't match any latest build of any release #[tokio::test(flavor = "multi_thread")] async fn test_rebuild_broken_rustdoc_specific_date_skipped() -> Result<()> { - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .max_queued_rebuilds(Some(100)) - .build()?, - ) - .await?; + let env = TestEnvironment::new().await?; - env.fake_release() - .await - .name("foo") - .version(V1) - .builds(vec![ - FakeBuild::default() - .rustc_version( - format!( - "rustc 1.84.0-nightly (e7c0d2750 {})", - NaiveDate::from_ymd_opt(2020, 10, 1) - .unwrap() - .format("%Y-%m-%d") - ) - .as_str(), - ) - .build_status(BuildStatus::Failure), - FakeBuild::default() - .rustc_version( - format!( - "rustc 1.84.0-nightly (e7c0d2750 {})", - NaiveDate::from_ymd_opt(2020, 10, 1) - .unwrap() - .format("%Y-%m-%d") + // Matrix of test builds (crate name, nightly date, version) + let build_matrix = [ + // Should be skipped since this is not the latest build for this release even if the nightly matches + ("foo1", NaiveDate::from_ymd_opt(2020, 10, 3).unwrap(), V1), + // Should be skipped since the nightly doesn't match + ("foo1", NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), V1), + // Should be skipped since the nightly doesn't match + ("foo2", NaiveDate::from_ymd_opt(2020, 10, 4).unwrap(), V1), + ]; + for build in build_matrix.into_iter() { + let (crate_name, nightly, version) = build; + env.fake_release() + .await + .name(crate_name) + .version(version) + .builds(vec![ + FakeBuild::default() + .rustc_version( + format!( + "rustc 1.84.0-nightly (e7c0d2750 {})", + nightly.format("%Y-%m-%d") + ) + .as_str(), ) - .as_str(), - ) - .build_status(BuildStatus::Success), - ]) - .create() - .await?; + .build_status(BuildStatus::Failure), + ]) + .create() + .await?; + } let build_queue = env.async_build_queue(); assert!(build_queue.queued_crates().await?.is_empty()); @@ -986,7 +995,7 @@ mod tests { queue_rebuilds_faulty_rustdoc( &mut conn, build_queue, - &NaiveDate::from_ymd_opt(2020, 10, 1).unwrap(), + &NaiveDate::from_ymd_opt(2020, 10, 3).unwrap(), &None, ) .await?; @@ -997,29 +1006,36 @@ mod tests { Ok(()) } + /// Verifies whether a rebuild is queued for all releases with the latest build performed with a nightly version between two dates #[tokio::test(flavor = "multi_thread")] async fn test_rebuild_broken_rustdoc_date_range() -> Result<()> { - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .max_queued_rebuilds(Some(100)) - .build()?, - ) - .await?; + let env = TestEnvironment::new().await?; - for i in 1..6 { - let nightly_date = NaiveDate::from_ymd_opt(2020, 10, i).unwrap(); + // Matrix of test builds (crate name, nightly date, version) + let build_matrix = [ + // Should be skipped since this is not the latest build for this release + ("foo1", NaiveDate::from_ymd_opt(2020, 10, 1).unwrap(), V1), + // All those should match + ("foo1", NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), V1), + ("foo1", NaiveDate::from_ymd_opt(2020, 10, 3).unwrap(), V2), + ("foo2", NaiveDate::from_ymd_opt(2020, 10, 4).unwrap(), V1), + // Should be skipped since the nightly doesn't match (end date is exclusive) + ("foo2", NaiveDate::from_ymd_opt(2020, 10, 5).unwrap(), V2), + ]; + for build in build_matrix.into_iter() { + let (crate_name, nightly, version) = build; env.fake_release() .await - .name(&format!("foo{}", i)) - .version(V1) + .name(crate_name) + .version(version) .builds(vec![ FakeBuild::default() .rustc_version( format!( "rustc 1.84.0-nightly (e7c0d2750 {})", - nightly_date.format("%Y-%m-%d") + nightly.format("%Y-%m-%d") ) - .as_str(), + .as_str(), ) .build_status(BuildStatus::Failure), ]) @@ -1034,19 +1050,22 @@ mod tests { queue_rebuilds_faulty_rustdoc( &mut conn, build_queue, - &NaiveDate::from_ymd_opt(2020, 10, 3).unwrap(), + &NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), &NaiveDate::from_ymd_opt(2020, 10, 5), ) .await?; let queue = build_queue.queued_crates().await?; - assert_eq!(queue.len(), 2); - assert_eq!(queue[0].name, "foo3"); + assert_eq!(queue.len(), 3); + assert_eq!(queue[0].name, "foo1"); assert_eq!(queue[0].version, V1); - assert_eq!(queue[0].priority, BROKEN_RUSTDOC_REBUILD_PRIORITY); - assert_eq!(queue[1].name, "foo4"); + assert_eq!(queue[0].priority, PRIORITY_BROKEN_RUSTDOC); + assert_eq!(queue[1].name, "foo1"); + assert_eq!(queue[1].version, V2); + assert_eq!(queue[1].priority, PRIORITY_BROKEN_RUSTDOC); + assert_eq!(queue[1].name, "foo2"); assert_eq!(queue[1].version, V1); - assert_eq!(queue[1].priority, BROKEN_RUSTDOC_REBUILD_PRIORITY); + assert_eq!(queue[1].priority, PRIORITY_BROKEN_RUSTDOC); Ok(()) } @@ -1062,10 +1081,10 @@ mod tests { let build_queue = env.async_build_queue(); build_queue - .add_crate("foo1", &V1, REBUILD_PRIORITY, None) + .add_crate("foo1", &V1, PRIORITY_CONTINUOUS, None) .await?; build_queue - .add_crate("foo2", &V1, REBUILD_PRIORITY, None) + .add_crate("foo2", &V1, PRIORITY_CONTINUOUS, None) .await?; let mut conn = env.async_db().async_conn().await; @@ -1104,10 +1123,10 @@ mod tests { let build_queue = env.async_build_queue(); build_queue - .add_crate("foo1", &V1, REBUILD_PRIORITY, None) + .add_crate("foo1", &V1, PRIORITY_CONTINUOUS, None) .await?; build_queue - .add_crate("foo2", &V1, REBUILD_PRIORITY, None) + .add_crate("foo2", &V1, PRIORITY_CONTINUOUS, None) .await?; env.fake_release() diff --git a/src/utils/consistency/mod.rs b/src/utils/consistency/mod.rs index d1c977448..81695f246 100644 --- a/src/utils/consistency/mod.rs +++ b/src/utils/consistency/mod.rs @@ -1,3 +1,4 @@ +use crate::build_queue::PRIORITY_CONSISTENCY_CHECK; use crate::{Context, db::delete}; use anyhow::{Context as _, Result}; use itertools::Itertools; @@ -8,8 +9,6 @@ mod db; mod diff; mod index; -const BUILD_PRIORITY: i32 = 15; - /// consistency check /// /// will compare our database with the local crates.io index and @@ -101,7 +100,7 @@ where if !dry_run && let Err(err) = ctx .async_build_queue - .add_crate(name, version, BUILD_PRIORITY, None) + .add_crate(name, version, PRIORITY_CONSISTENCY_CHECK, None) .await { warn!("{:?}", err); @@ -128,7 +127,7 @@ where if !dry_run && let Err(err) = ctx .async_build_queue - .add_crate(name, version, BUILD_PRIORITY, None) + .add_crate(name, version, PRIORITY_CONSISTENCY_CHECK, None) .await { warn!("{:?}", err); diff --git a/src/utils/queue.rs b/src/utils/queue.rs index 7df91672a..a5b6181b3 100644 --- a/src/utils/queue.rs +++ b/src/utils/queue.rs @@ -1,8 +1,7 @@ //! Utilities for interacting with the build queue use crate::error::Result; use futures_util::stream::TryStreamExt; - -const DEFAULT_PRIORITY: i32 = 0; +use crate::build_queue::PRIORITY_DEFAULT; /// Get the build queue priority for a crate, returns the matching pattern too pub async fn list_crate_priorities(conn: &mut sqlx::PgConnection) -> Result> { @@ -34,7 +33,7 @@ pub async fn get_crate_pattern_and_priority( pub async fn get_crate_priority(conn: &mut sqlx::PgConnection, name: &str) -> Result { Ok(get_crate_pattern_and_priority(conn, name) .await? - .map_or(DEFAULT_PRIORITY, |(_, priority)| priority)) + .map_or(PRIORITY_DEFAULT, |(_, priority)| priority)) } /// Set all crates that match [`pattern`] to have a certain priority @@ -96,22 +95,22 @@ mod tests { ); assert_eq!( get_crate_priority(&mut conn, "docsrs").await?, - DEFAULT_PRIORITY + PRIORITY_DEFAULT ); set_crate_priority(&mut conn, "_c_", 100).await?; assert_eq!(get_crate_priority(&mut conn, "rcc").await?, 100); - assert_eq!(get_crate_priority(&mut conn, "rc").await?, DEFAULT_PRIORITY); + assert_eq!(get_crate_priority(&mut conn, "rc").await?, PRIORITY_DEFAULT); set_crate_priority(&mut conn, "hexponent", 10).await?; assert_eq!(get_crate_priority(&mut conn, "hexponent").await?, 10); assert_eq!( get_crate_priority(&mut conn, "hexponents").await?, - DEFAULT_PRIORITY + PRIORITY_DEFAULT ); assert_eq!( get_crate_priority(&mut conn, "floathexponent").await?, - DEFAULT_PRIORITY + PRIORITY_DEFAULT ); Ok(()) @@ -133,7 +132,7 @@ mod tests { ); assert_eq!( get_crate_priority(&mut conn, "docsrs-").await?, - DEFAULT_PRIORITY + PRIORITY_DEFAULT ); Ok(()) @@ -160,7 +159,7 @@ mod tests { ); assert_eq!( get_crate_priority(&mut conn, "unrelated").await?, - DEFAULT_PRIORITY + PRIORITY_DEFAULT ); Ok(()) @@ -175,23 +174,23 @@ mod tests { assert_eq!( get_crate_priority(&mut conn, "docsrs").await?, - DEFAULT_PRIORITY + PRIORITY_DEFAULT ); assert_eq!( get_crate_priority(&mut conn, "rcc").await?, - DEFAULT_PRIORITY + PRIORITY_DEFAULT ); assert_eq!( get_crate_priority(&mut conn, "lasso").await?, - DEFAULT_PRIORITY + PRIORITY_DEFAULT ); assert_eq!( get_crate_priority(&mut conn, "hexponent").await?, - DEFAULT_PRIORITY + PRIORITY_DEFAULT ); assert_eq!( get_crate_priority(&mut conn, "rust4lyfe").await?, - DEFAULT_PRIORITY + PRIORITY_DEFAULT ); Ok(()) diff --git a/src/web/builds.rs b/src/web/builds.rs index 65660a2dc..b400aca3a 100644 --- a/src/web/builds.rs +++ b/src/web/builds.rs @@ -1,3 +1,4 @@ +use crate::build_queue::PRIORITY_MANUAL_FROM_CRATES_IO; use crate::{ AsyncBuildQueue, Config, db::{ @@ -139,10 +140,6 @@ async fn build_trigger_check( Ok(()) } -// Priority according to issue #2442; positive here as it's inverted. -// FUTURE: move to a crate-global enum with all special priorities? -const TRIGGERED_REBUILD_PRIORITY: i32 = 5; - pub(crate) async fn build_trigger_rebuild_handler( Path((name, version)): Path<(String, Version)>, mut conn: DbConnection, @@ -176,7 +173,7 @@ pub(crate) async fn build_trigger_rebuild_handler( .add_crate( &name, &version, - TRIGGERED_REBUILD_PRIORITY, + PRIORITY_MANUAL_FROM_CRATES_IO, None, /* because crates.io is the only service that calls this endpoint */ ) .await diff --git a/src/web/releases.rs b/src/web/releases.rs index 84d997ada..a1eed2485 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -1,8 +1,10 @@ //! Releases web handlersrelease +use super::cache::CachePolicy; +use crate::build_queue::PRIORITY_CONTINUOUS; use crate::{ AsyncBuildQueue, Config, InstanceMetrics, RegistryApi, - build_queue::{QueuedCrate, REBUILD_PRIORITY}, + build_queue::QueuedCrate, cdn, db::types::version::Version, impl_axum_webpage, @@ -37,8 +39,6 @@ use std::{ use tracing::{trace, warn}; use url::form_urlencoded; -use super::cache::CachePolicy; - /// Number of release in home page const RELEASES_IN_HOME: i64 = 15; /// Releases in /releases page @@ -780,7 +780,7 @@ pub(crate) async fn build_queue_handler( .collect_vec(); queue.retain_mut(|krate| { - if krate.priority >= REBUILD_PRIORITY { + if krate.priority >= PRIORITY_CONTINUOUS { rebuild_queue.push(krate.clone()); false } else { @@ -1989,12 +1989,14 @@ mod tests { async_wrapper(|env| async move { let web = env.web_app().await; let queue = env.async_build_queue(); - queue.add_crate("foo", &V1, REBUILD_PRIORITY, None).await?; queue - .add_crate("bar", &V2, REBUILD_PRIORITY + 1, None) + .add_crate("foo", &V1, PRIORITY_CONTINUOUS, None) + .await?; + queue + .add_crate("bar", &V2, PRIORITY_CONTINUOUS + 1, None) .await?; queue - .add_crate("baz", &V3, REBUILD_PRIORITY - 1, None) + .add_crate("baz", &V3, PRIORITY_CONTINUOUS - 1, None) .await?; let full = kuchikiki::parse_html().one(web.get("/releases/queue").await?.text().await?); From 362a655ef518f63011801f8c6d1fa42042feb1b7 Mon Sep 17 00:00:00 2001 From: Carbonhell Date: Mon, 24 Nov 2025 00:29:21 +0100 Subject: [PATCH 5/7] chore: fmt --- src/build_queue.rs | 14 +++++++------- src/utils/queue.rs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/build_queue.rs b/src/build_queue.rs index 2664c998e..5d04e297c 100644 --- a/src/build_queue.rs +++ b/src/build_queue.rs @@ -40,8 +40,6 @@ impl BuildQueueMetrics { } } - - pub(crate) const PRIORITY_DEFAULT: i32 = 0; /// Used for workspaces to avoid blocking the queue (done through the cratesfyi CLI, not used in code) #[allow(dead_code)] @@ -818,9 +816,11 @@ FROM crates AS c AND b.rustc_nightly_date < $2 -"#, start_nightly_date, end_nightly_date +"#, + start_nightly_date, + end_nightly_date ) - .fetch(&mut *conn); + .fetch(&mut *conn); let mut results_count = 0; while let Some(row) = results.next().await { @@ -918,7 +918,7 @@ mod tests { "rustc 1.84.0-nightly (e7c0d2750 {})", nightly.format("%Y-%m-%d") ) - .as_str(), + .as_str(), ) .build_status(BuildStatus::Failure), ]) @@ -980,7 +980,7 @@ mod tests { "rustc 1.84.0-nightly (e7c0d2750 {})", nightly.format("%Y-%m-%d") ) - .as_str(), + .as_str(), ) .build_status(BuildStatus::Failure), ]) @@ -1035,7 +1035,7 @@ mod tests { "rustc 1.84.0-nightly (e7c0d2750 {})", nightly.format("%Y-%m-%d") ) - .as_str(), + .as_str(), ) .build_status(BuildStatus::Failure), ]) diff --git a/src/utils/queue.rs b/src/utils/queue.rs index a5b6181b3..92d697ed6 100644 --- a/src/utils/queue.rs +++ b/src/utils/queue.rs @@ -1,7 +1,7 @@ //! Utilities for interacting with the build queue +use crate::build_queue::PRIORITY_DEFAULT; use crate::error::Result; use futures_util::stream::TryStreamExt; -use crate::build_queue::PRIORITY_DEFAULT; /// Get the build queue priority for a crate, returns the matching pattern too pub async fn list_crate_priorities(conn: &mut sqlx::PgConnection) -> Result> { From 684aca4ae0fcc2da78989bcee918b0abb5e64209 Mon Sep 17 00:00:00 2001 From: Carbonhell Date: Mon, 24 Nov 2025 00:41:38 +0100 Subject: [PATCH 6/7] tweak(queue): remove pointless filter on build_status, as even if a build is still in progress with a broken nightly, we'd want it to be included in the list of rebuilds chore(sqlx): run prepare --- ...33ac319d1d8169e9115215775968a047e4069.json | 29 +++++++++++++++++++ ...067c225de3d323f74d36512c579a6896c68b6.json | 29 ------------------- src/build_queue.rs | 1 - 3 files changed, 29 insertions(+), 30 deletions(-) create mode 100644 .sqlx/query-13d3402d8d5bd2f1fee96566ed633ac319d1d8169e9115215775968a047e4069.json delete mode 100644 .sqlx/query-64031f2e97958452a6d6da840fd067c225de3d323f74d36512c579a6896c68b6.json diff --git a/.sqlx/query-13d3402d8d5bd2f1fee96566ed633ac319d1d8169e9115215775968a047e4069.json b/.sqlx/query-13d3402d8d5bd2f1fee96566ed633ac319d1d8169e9115215775968a047e4069.json new file mode 100644 index 000000000..791fc49a7 --- /dev/null +++ b/.sqlx/query-13d3402d8d5bd2f1fee96566ed633ac319d1d8169e9115215775968a047e4069.json @@ -0,0 +1,29 @@ +{ + "db_name": "PostgreSQL", + "query": "\nSELECT c.name,\n r.version AS \"version: Version\"\nFROM crates AS c\n JOIN releases AS r\n ON c.id = r.crate_id\n JOIN release_build_status AS rbs\n ON rbs.rid = r.id\n JOIN builds AS b\n ON b.rid = r.id\n AND b.build_finished = rbs.last_build_time\n AND b.rustc_nightly_date >= $1\n AND b.rustc_nightly_date < $2\n\n\n", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "version: Version", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Date", + "Date" + ] + }, + "nullable": [ + false, + false + ] + }, + "hash": "13d3402d8d5bd2f1fee96566ed633ac319d1d8169e9115215775968a047e4069" +} diff --git a/.sqlx/query-64031f2e97958452a6d6da840fd067c225de3d323f74d36512c579a6896c68b6.json b/.sqlx/query-64031f2e97958452a6d6da840fd067c225de3d323f74d36512c579a6896c68b6.json deleted file mode 100644 index 730433aea..000000000 --- a/.sqlx/query-64031f2e97958452a6d6da840fd067c225de3d323f74d36512c579a6896c68b6.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\nSELECT c.name,\n r.version AS \"version: Version\"\nFROM crates AS c\n JOIN releases AS r\n ON c.latest_version_id = r.id\n AND r.rustdoc_status = TRUE\n JOIN LATERAL (\n SELECT b.id,\n b.build_status,\n b.rustc_nightly_date,\n COALESCE(b.build_finished, b.build_started) AS last_build_attempt\n FROM builds AS b\n WHERE b.rid = r.id\n ORDER BY last_build_attempt DESC\n LIMIT 1\n ) AS b ON b.build_status = 'failure' AND b.rustc_nightly_date >= $1 AND b.rustc_nightly_date < $2\n\n", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "name", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "version: Version", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Date", - "Date" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "64031f2e97958452a6d6da840fd067c225de3d323f74d36512c579a6896c68b6" -} diff --git a/src/build_queue.rs b/src/build_queue.rs index 5d04e297c..a46ff1e39 100644 --- a/src/build_queue.rs +++ b/src/build_queue.rs @@ -808,7 +808,6 @@ FROM crates AS c ON c.id = r.crate_id JOIN release_build_status AS rbs ON rbs.rid = r.id - AND rbs.build_status != 'in_progress' JOIN builds AS b ON b.rid = r.id AND b.build_finished = rbs.last_build_time From b8bc08ccee932df9118adb0c49274ffc39b67b7e Mon Sep 17 00:00:00 2001 From: Carbonhell Date: Mon, 24 Nov 2025 21:54:41 +0100 Subject: [PATCH 7/7] fix(tests): correct index used in assert --- src/build_queue.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/build_queue.rs b/src/build_queue.rs index a46ff1e39..df42d8040 100644 --- a/src/build_queue.rs +++ b/src/build_queue.rs @@ -1062,9 +1062,9 @@ mod tests { assert_eq!(queue[1].name, "foo1"); assert_eq!(queue[1].version, V2); assert_eq!(queue[1].priority, PRIORITY_BROKEN_RUSTDOC); - assert_eq!(queue[1].name, "foo2"); - assert_eq!(queue[1].version, V1); - assert_eq!(queue[1].priority, PRIORITY_BROKEN_RUSTDOC); + assert_eq!(queue[2].name, "foo2"); + assert_eq!(queue[2].version, V1); + assert_eq!(queue[2].priority, PRIORITY_BROKEN_RUSTDOC); Ok(()) }