Skip to content

Commit 1788b17

Browse files
committed
queue rebuilds for old releases
1 parent 22de827 commit 1788b17

7 files changed

+286
-5
lines changed

.sqlx/query-007c5f49470ce1bc503f82003377d80cc4be3282ca1b9c37c1b2e8c28dff53d0.json

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-dd1b692e4dc6aaa210f53b1cf3f57a4282b79c6c20859bb88a451afc3b1a404d.json

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/bin/cratesfyi.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ enum CommandLine {
155155
repository_stats_updater: Toggle,
156156
#[arg(long = "cdn-invalidator", default_value = "enabled", value_enum)]
157157
cdn_invalidator: Toggle,
158+
#[arg(long = "queue-rebuilds", default_value = "enabled", value_enum)]
159+
queue_rebuilds: Toggle,
158160
},
159161

160162
StartBuildServer {
@@ -192,13 +194,17 @@ impl CommandLine {
192194
metric_server_socket_addr,
193195
repository_stats_updater,
194196
cdn_invalidator,
197+
queue_rebuilds,
195198
} => {
196199
if repository_stats_updater == Toggle::Enabled {
197200
docs_rs::utils::daemon::start_background_repository_stats_updater(&ctx)?;
198201
}
199202
if cdn_invalidator == Toggle::Enabled {
200203
docs_rs::utils::daemon::start_background_cdn_invalidator(&ctx)?;
201204
}
205+
if queue_rebuilds == Toggle::Enabled {
206+
docs_rs::utils::daemon::start_background_queue_rebuild(&ctx)?;
207+
}
202208

203209
start_background_metrics_webserver(Some(metric_server_socket_addr), &ctx)?;
204210

src/build_queue.rs

Lines changed: 184 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ use crate::{cdn, BuildPackageSummary};
88
use crate::{Config, Index, InstanceMetrics, RustwideBuilder};
99
use anyhow::Context as _;
1010
use fn_error_context::context;
11-
use futures_util::stream::TryStreamExt;
11+
use futures_util::{stream::TryStreamExt, StreamExt};
1212
use sqlx::Connection as _;
1313
use std::collections::HashMap;
1414
use std::sync::Arc;
1515
use tokio::runtime::Runtime;
16-
use tracing::{debug, error, info};
16+
use tracing::{debug, error, info, instrument};
17+
18+
pub(crate) const REBUILD_PRIORITY: i32 = 20;
1719

1820
// Threshold priority to decide whether a crate will in the rebuild-queue-list.
1921
// If crate is in the rebuild-queue-list it won't in the build-queue-list.
@@ -656,12 +658,191 @@ impl BuildQueue {
656658
}
657659
}
658660

661+
/// Queue rebuilds as configured.
662+
///
663+
/// The idea is to rebuild:
664+
/// * the latest release of each crate
665+
/// * when the nightly version is older than our configured threshold
666+
/// * and there was a successful build for that release, that included documentation.
667+
/// * starting with the oldest nightly versions.
668+
/// * also checking if there is already a build queued.
669+
///
670+
/// This might exclude releases from rebuilds that
671+
/// * previously failed but would succeed with a newer nightly version
672+
/// * previously failed but would succeed just with a retry.
673+
#[instrument(skip_all)]
674+
pub async fn queue_rebuilds(
675+
conn: &mut sqlx::PgConnection,
676+
config: &Config,
677+
build_queue: &AsyncBuildQueue,
678+
) -> Result<()> {
679+
let already_queued_rebuilds = sqlx::query_scalar!(
680+
r#"SELECT COUNT(*) as "count!" FROM queue WHERE priority >= $1"#,
681+
REBUILD_PRIORITY
682+
)
683+
.fetch_one(&mut *conn)
684+
.await?;
685+
686+
let rebuilds_to_queue = config
687+
.max_queued_rebuilds
688+
.expect("config.max_queued_rebuilds not set") as i64
689+
- already_queued_rebuilds;
690+
691+
if rebuilds_to_queue <= 0 {
692+
info!("not queueing rebuilds; queue limit reached");
693+
return Ok(());
694+
}
695+
696+
let mut results = sqlx::query!(
697+
"SELECT i.* FROM (
698+
SELECT
699+
c.name,
700+
r.version,
701+
max(b.rustc_nightly_date) as rustc_nightly_date
702+
703+
FROM crates AS c
704+
INNER JOIN releases AS r ON c.latest_version_id = r.id
705+
INNER JOIN builds AS b ON r.id = b.rid
706+
707+
WHERE
708+
r.rustdoc_status = TRUE
709+
710+
GROUP BY c.name, r.version
711+
) as i
712+
WHERE i.rustc_nightly_date < $1
713+
ORDER BY i.rustc_nightly_date ASC
714+
LIMIT $2",
715+
config
716+
.rebuild_up_to_date
717+
.expect("config.rebuild_up_to_date not set"),
718+
rebuilds_to_queue,
719+
)
720+
.fetch(&mut *conn);
721+
722+
while let Some(row) = results.next().await {
723+
let row = row?;
724+
725+
if !build_queue
726+
.has_build_queued(&row.name, &row.version)
727+
.await?
728+
{
729+
info!("queueing rebuild for {} {}...", &row.name, &row.version);
730+
build_queue
731+
.add_crate(&row.name, &row.version, REBUILD_PRIORITY, None)
732+
.await?;
733+
}
734+
}
735+
736+
Ok(())
737+
}
738+
659739
#[cfg(test)]
660740
mod tests {
741+
use crate::test::FakeBuild;
742+
661743
use super::*;
662-
use chrono::Utc;
744+
use chrono::{NaiveDate, Utc};
663745
use std::time::Duration;
664746

747+
#[test]
748+
fn test_dont_rebuild_when_new() {
749+
crate::test::async_wrapper(|env| async move {
750+
env.override_config(|config| {
751+
config.max_queued_rebuilds = Some(100);
752+
config.rebuild_up_to_date = Some(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap());
753+
});
754+
755+
env.async_fake_release()
756+
.await
757+
.name("foo")
758+
.version("0.1.0")
759+
.builds(vec![FakeBuild::default()
760+
.rustc_version("rustc 1.84.0-nightly (e7c0d2750 2020-10-15)")])
761+
.create_async()
762+
.await?;
763+
764+
let build_queue = env.async_build_queue().await;
765+
assert!(build_queue.queued_crates().await?.is_empty());
766+
767+
let mut conn = env.async_db().await.async_conn().await;
768+
queue_rebuilds(&mut conn, &env.config(), &build_queue).await?;
769+
770+
assert!(build_queue.queued_crates().await?.is_empty());
771+
772+
Ok(())
773+
})
774+
}
775+
776+
#[test]
777+
fn test_rebuild_when_old() {
778+
crate::test::async_wrapper(|env| async move {
779+
env.override_config(|config| {
780+
config.max_queued_rebuilds = Some(100);
781+
config.rebuild_up_to_date = Some(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap());
782+
});
783+
784+
env.async_fake_release()
785+
.await
786+
.name("foo")
787+
.version("0.1.0")
788+
.builds(vec![FakeBuild::default()
789+
.rustc_version("rustc 1.84.0-nightly (e7c0d2750 2020-10-15)")])
790+
.create_async()
791+
.await?;
792+
793+
let build_queue = env.async_build_queue().await;
794+
assert!(build_queue.queued_crates().await?.is_empty());
795+
796+
let mut conn = env.async_db().await.async_conn().await;
797+
queue_rebuilds(&mut conn, &env.config(), &build_queue).await?;
798+
799+
let queue = build_queue.queued_crates().await?;
800+
assert_eq!(queue.len(), 1);
801+
assert_eq!(queue[0].name, "foo");
802+
assert_eq!(queue[0].version, "0.1.0");
803+
assert_eq!(queue[0].priority, REBUILD_PRIORITY);
804+
805+
Ok(())
806+
})
807+
}
808+
809+
#[test]
810+
fn test_dont_rebuild_when_full() {
811+
crate::test::async_wrapper(|env| async move {
812+
env.override_config(|config| {
813+
config.max_queued_rebuilds = Some(1);
814+
config.rebuild_up_to_date = Some(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap());
815+
});
816+
817+
let build_queue = env.async_build_queue().await;
818+
build_queue
819+
.add_crate("foo1", "0.1.0", REBUILD_PRIORITY, None)
820+
.await?;
821+
build_queue
822+
.add_crate("foo2", "0.1.0", REBUILD_PRIORITY, None)
823+
.await?;
824+
825+
env.async_fake_release()
826+
.await
827+
.name("foo")
828+
.version("0.1.0")
829+
.builds(vec![FakeBuild::default()
830+
.rustc_version("rustc 1.84.0-nightly (e7c0d2750 2020-10-15)")])
831+
.create_async()
832+
.await?;
833+
834+
let build_queue = env.async_build_queue().await;
835+
assert_eq!(build_queue.queued_crates().await?.len(), 2);
836+
837+
let mut conn = env.async_db().await.async_conn().await;
838+
queue_rebuilds(&mut conn, &env.config(), &build_queue).await?;
839+
840+
assert_eq!(build_queue.queued_crates().await?.len(), 2);
841+
842+
Ok(())
843+
})
844+
}
845+
665846
#[test]
666847
fn test_add_duplicate_doesnt_fail_last_priority_wins() {
667848
crate::test::async_wrapper(|env| async move {

src/config.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::{cdn::CdnKind, storage::StorageKind};
22
use anyhow::{anyhow, bail, Context, Result};
3+
use chrono::NaiveDate;
34
use std::{env::VarError, error::Error, path::PathBuf, str::FromStr, time::Duration};
45
use tracing::trace;
56
use url::Url;
@@ -113,6 +114,10 @@ pub struct Config {
113114
pub(crate) build_default_memory_limit: Option<usize>,
114115
pub(crate) include_default_targets: bool,
115116
pub(crate) disable_memory_limit: bool,
117+
118+
// automatic rebuild configuration
119+
pub(crate) max_queued_rebuilds: Option<u16>,
120+
pub(crate) rebuild_up_to_date: Option<NaiveDate>,
116121
}
117122

118123
impl Config {
@@ -230,6 +235,8 @@ impl Config {
230235
"DOCSRS_BUILD_WORKSPACE_REINITIALIZATION_INTERVAL",
231236
86400,
232237
)?),
238+
max_queued_rebuilds: maybe_env("DOCSRS_MAX_QUEUED_REBUILDS")?,
239+
rebuild_up_to_date: maybe_env("DOCSRS_REBUILD_UP_TO_DATE")?,
233240
})
234241
}
235242
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//! documentation of crates for the Rust Programming Language.
33
#![allow(clippy::cognitive_complexity)]
44

5-
pub use self::build_queue::{AsyncBuildQueue, BuildQueue};
5+
pub use self::build_queue::{queue_rebuilds, AsyncBuildQueue, BuildQueue};
66
pub use self::config::Config;
77
pub use self::context::Context;
88
pub use self::docbuilder::PackageKind;

src/utils/daemon.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! This daemon will start web server, track new packages and build them
44
55
use crate::{
6-
cdn,
6+
cdn, queue_rebuilds,
77
utils::{queue_builder, report_error},
88
web::start_web_server,
99
AsyncBuildQueue, Config, Context, Index, RustwideBuilder,
@@ -91,6 +91,35 @@ pub fn start_background_repository_stats_updater(context: &dyn Context) -> Resul
9191
Ok(())
9292
}
9393

94+
pub fn start_background_queue_rebuild(context: &dyn Context) -> Result<(), Error> {
95+
let runtime = context.runtime()?;
96+
let pool = context.pool()?;
97+
let config = context.config()?;
98+
let build_queue = runtime.block_on(context.async_build_queue())?;
99+
100+
if config.max_queued_rebuilds.is_none() || config.rebuild_up_to_date.is_none() {
101+
info!("rebuild config incomplete, skipping rebuild queueing");
102+
return Ok(());
103+
}
104+
105+
async_cron(
106+
&runtime,
107+
"background queue rebuilder",
108+
Duration::from_secs(60 * 60),
109+
move || {
110+
let pool = pool.clone();
111+
let build_queue = build_queue.clone();
112+
let config = config.clone();
113+
async move {
114+
let mut conn = pool.get_async().await?;
115+
queue_rebuilds(&mut conn, &config, &build_queue).await?;
116+
Ok(())
117+
}
118+
},
119+
);
120+
Ok(())
121+
}
122+
94123
pub fn start_background_cdn_invalidator(context: &dyn Context) -> Result<(), Error> {
95124
let metrics = context.instance_metrics()?;
96125
let config = context.config()?;
@@ -183,6 +212,7 @@ pub fn start_daemon<C: Context + Send + Sync + 'static>(
183212

184213
start_background_repository_stats_updater(&*context)?;
185214
start_background_cdn_invalidator(&*context)?;
215+
start_background_queue_rebuild(&*context)?;
186216

187217
// NOTE: if a error occurred earlier in `start_daemon`, the server will _not_ be joined -
188218
// instead it will get killed when the process exits.

0 commit comments

Comments
 (0)