Skip to content

Commit 7216865

Browse files
committed
queue rebuilds for old releases
1 parent d6e3a42 commit 7216865

7 files changed

+286
-5
lines changed

.sqlx/query-007c5f49470ce1bc503f82003377d80cc4be3282ca1b9c37c1b2e8c28dff53d0.json

+22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-dd1b692e4dc6aaa210f53b1cf3f57a4282b79c6c20859bb88a451afc3b1a404d.json

+35
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/bin/cratesfyi.rs

+6
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

+184-3
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
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize)]
1921
pub(crate) struct QueuedCrate {
@@ -652,12 +654,191 @@ impl BuildQueue {
652654
}
653655
}
654656

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

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

src/config.rs

+7
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

+1-1
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

+31-1
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)