Skip to content

Commit 05a02f3

Browse files
committed
Change timeout to apply to the overall build process, not per-build
1 parent c088458 commit 05a02f3

File tree

1 file changed

+135
-41
lines changed

1 file changed

+135
-41
lines changed

src/docbuilder/rustwide_builder.rs

+135-41
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@ use rustwide::cmd::{Command, CommandError, SandboxBuilder, SandboxImage};
2323
use rustwide::logging::{self, LogStorage};
2424
use rustwide::toolchain::ToolchainError;
2525
use rustwide::{AlternativeRegistry, Build, Crate, Toolchain, Workspace, WorkspaceBuilder};
26-
use std::collections::{HashMap, HashSet};
27-
use std::path::Path;
28-
use std::sync::Arc;
26+
use std::{
27+
collections::{HashMap, HashSet},
28+
path::Path,
29+
sync::Arc,
30+
time::Instant,
31+
};
2932
use tracing::{debug, info, warn};
3033

3134
const USER_AGENT: &str = "docs.rs builder (https://github.com/rust-lang/docs.rs)";
@@ -244,9 +247,19 @@ impl RustwideBuilder {
244247
.run(|build| {
245248
(|| -> Result<()> {
246249
let metadata = Metadata::from_crate_root(build.host_source_dir())?;
250+
let deadline = Instant::now()
251+
.checked_add(limits.timeout())
252+
.context("deadline is not representable")?;
247253

248-
let res =
249-
self.execute_build(HOST_TARGET, true, build, &limits, &metadata, true)?;
254+
let res = self.execute_build(
255+
HOST_TARGET,
256+
true,
257+
build,
258+
&limits,
259+
&metadata,
260+
true,
261+
deadline,
262+
)?;
250263
if !res.result.successful {
251264
bail!("failed to build dummy crate for {}", self.rustc_version);
252265
}
@@ -333,15 +346,15 @@ impl RustwideBuilder {
333346
return Ok(false);
334347
}
335348

336-
self.update_toolchain()?;
337-
338-
info!("building package {} {}", name, version);
339-
340349
if is_blacklisted(&mut conn, name)? {
341350
info!("skipping build of {}, crate has been blacklisted", name);
342351
return Ok(false);
343352
}
344353

354+
self.update_toolchain()?;
355+
356+
info!("building package {} {}", name, version);
357+
345358
let limits = Limits::for_crate(&mut conn, name)?;
346359
#[cfg(target_os = "linux")]
347360
if !self.config.disable_memory_limit {
@@ -388,18 +401,55 @@ impl RustwideBuilder {
388401
default_target,
389402
other_targets,
390403
} = metadata.targets(self.config.include_default_targets);
391-
let mut targets = vec![default_target];
392-
targets.extend(&other_targets);
404+
405+
let cargo_args = metadata.cargo_args(&[], &[]);
406+
let has_build_std = cargo_args.iter().any(|arg| arg.starts_with("-Zbuild-std"))
407+
|| cargo_args
408+
.windows(2)
409+
.any(|args| args[0] == "-Z" && args[1].starts_with("build-std"));
410+
411+
let other_targets: Vec<_> = other_targets
412+
.into_iter()
413+
.filter(|target| {
414+
// If the explicit target is not a tier one target, we need to install it.
415+
if !docsrs_metadata::DEFAULT_TARGETS.contains(target) && !has_build_std {
416+
// This is a no-op if the target is already installed.
417+
if let Err(e) = self.toolchain.add_target(&self.workspace, target) {
418+
info!("Skipping target {target} since it failed to install: {e}");
419+
return false;
420+
}
421+
}
422+
true
423+
})
424+
// Limit the number of targets so that no one can try to build all 200000 possible targets
425+
.take(limits.targets())
426+
.collect();
427+
393428
// Fetch this before we enter the sandbox, so networking isn't blocked.
394-
build.fetch_build_std_dependencies(&targets)?;
429+
build.fetch_build_std_dependencies(&{
430+
let mut targets = other_targets.clone();
431+
targets.push(default_target);
432+
targets
433+
})?;
395434

396435
(|| -> Result<bool> {
436+
let deadline = Instant::now()
437+
.checked_add(limits.timeout())
438+
.context("deadline is not representable")?;
439+
397440
let mut has_docs = false;
398441
let mut successful_targets = Vec::new();
399442

400443
// Perform an initial build
401-
let mut res =
402-
self.execute_build(default_target, true, build, &limits, &metadata, false)?;
444+
let mut res = self.execute_build(
445+
default_target,
446+
true,
447+
build,
448+
&limits,
449+
&metadata,
450+
false,
451+
deadline,
452+
)?;
403453

404454
// If the build fails with the lockfile given, try using only the dependencies listed in Cargo.toml.
405455
let cargo_lock = build.host_source_dir().join("Cargo.lock");
@@ -421,6 +471,7 @@ impl RustwideBuilder {
421471
&limits,
422472
&metadata,
423473
false,
474+
deadline,
424475
)?;
425476
}
426477

@@ -448,8 +499,7 @@ impl RustwideBuilder {
448499
successful_targets.push(res.target.clone());
449500

450501
// Then build the documentation for all the targets
451-
// Limit the number of targets so that no one can try to build all 200000 possible targets
452-
for target in other_targets.into_iter().take(limits.targets()) {
502+
for target in other_targets {
453503
debug!("building package {} {} for {}", name, version, target);
454504
self.build_target(
455505
target,
@@ -458,6 +508,7 @@ impl RustwideBuilder {
458508
local_storage.path(),
459509
&mut successful_targets,
460510
&metadata,
511+
deadline,
461512
)?;
462513
}
463514
let (_, new_alg) = add_path_into_remote_archive(
@@ -572,6 +623,7 @@ impl RustwideBuilder {
572623
Ok(successful)
573624
}
574625

626+
#[allow(clippy::too_many_arguments)]
575627
fn build_target(
576628
&self,
577629
target: &str,
@@ -580,8 +632,10 @@ impl RustwideBuilder {
580632
local_storage: &Path,
581633
successful_targets: &mut Vec<String>,
582634
metadata: &Metadata,
635+
deadline: Instant,
583636
) -> Result<()> {
584-
let target_res = self.execute_build(target, false, build, limits, metadata, false)?;
637+
let target_res =
638+
self.execute_build(target, false, build, limits, metadata, false, deadline)?;
585639
if target_res.result.successful {
586640
// Cargo is not giving any error and not generating documentation of some crates
587641
// when we use a target compile options. Check documentation exists before
@@ -600,7 +654,7 @@ impl RustwideBuilder {
600654
target: &str,
601655
build: &Build,
602656
metadata: &Metadata,
603-
limits: &Limits,
657+
deadline: Instant,
604658
) -> Result<Option<DocCoverage>> {
605659
let rustdoc_flags = vec![
606660
"--output-format".to_string(),
@@ -623,7 +677,7 @@ impl RustwideBuilder {
623677
items_with_examples: 0,
624678
};
625679

626-
self.prepare_command(build, target, metadata, limits, rustdoc_flags)?
680+
self.prepare_command(build, target, metadata, rustdoc_flags, deadline)?
627681
.process_lines(&mut |line, _| {
628682
if line.starts_with('{') && line.ends_with('}') {
629683
let parsed = match serde_json::from_str::<HashMap<String, FileCoverage>>(line) {
@@ -650,6 +704,9 @@ impl RustwideBuilder {
650704
)
651705
}
652706

707+
// TODO(Nemo157): Look at pulling out a sub-builder for each crate-build
708+
// that holds a lot of this state
709+
#[allow(clippy::too_many_arguments)]
653710
fn execute_build(
654711
&self,
655712
target: &str,
@@ -658,6 +715,7 @@ impl RustwideBuilder {
658715
limits: &Limits,
659716
metadata: &Metadata,
660717
create_essential_files: bool,
718+
deadline: Instant,
661719
) -> Result<FullBuildResult> {
662720
let cargo_metadata = CargoMetadata::load_from_rustwide(
663721
&self.workspace,
@@ -682,7 +740,7 @@ impl RustwideBuilder {
682740
// we have to run coverage before the doc-build because currently it
683741
// deletes the doc-target folder.
684742
// https://github.com/rust-lang/cargo/issues/9447
685-
let doc_coverage = match self.get_coverage(target, build, metadata, limits) {
743+
let doc_coverage = match self.get_coverage(target, build, metadata, deadline) {
686744
Ok(cov) => cov,
687745
Err(err) => {
688746
info!("error when trying to get coverage: {}", err);
@@ -692,10 +750,11 @@ impl RustwideBuilder {
692750
};
693751

694752
let successful = logging::capture(&storage, || {
695-
self.prepare_command(build, target, metadata, limits, rustdoc_flags)
753+
self.prepare_command(build, target, metadata, rustdoc_flags, deadline)
696754
.and_then(|command| command.run().map_err(Error::from))
697-
.is_ok()
698-
});
755+
})
756+
.map_err(|e| info!("failed build: {e:?}"))
757+
.is_ok();
699758

700759
// For proc-macros, cargo will put the output in `target/doc`.
701760
// Move it to the target-specific directory for consistency with other builds.
@@ -732,9 +791,13 @@ impl RustwideBuilder {
732791
build: &'ws Build,
733792
target: &str,
734793
metadata: &Metadata,
735-
limits: &Limits,
736794
mut rustdoc_flags_extras: Vec<String>,
795+
deadline: Instant,
737796
) -> Result<Command<'ws, 'pl>> {
797+
let timeout = deadline
798+
.checked_duration_since(Instant::now())
799+
.context("exceeded deadline")?;
800+
738801
// Add docs.rs specific arguments
739802
let mut cargo_args = vec![
740803
"--offline".into(),
@@ -783,22 +846,7 @@ impl RustwideBuilder {
783846
rustdoc_flags_extras.extend(UNCONDITIONAL_ARGS.iter().map(|&s| s.to_owned()));
784847
let cargo_args = metadata.cargo_args(&cargo_args, &rustdoc_flags_extras);
785848

786-
// If the explicit target is not a tier one target, we need to install it.
787-
let has_build_std = cargo_args.windows(2).any(|args| {
788-
args[0].starts_with("-Zbuild-std")
789-
|| (args[0] == "-Z" && args[1].starts_with("build-std"))
790-
}) || cargo_args.last().unwrap().starts_with("-Zbuild-std");
791-
if !docsrs_metadata::DEFAULT_TARGETS.contains(&target) && !has_build_std {
792-
// This is a no-op if the target is already installed.
793-
self.toolchain
794-
.add_target(&self.workspace, target)
795-
.map_err(FailureError::compat)?;
796-
}
797-
798-
let mut command = build
799-
.cargo()
800-
.timeout(Some(limits.timeout()))
801-
.no_output_timeout(None);
849+
let mut command = build.cargo().timeout(Some(timeout)).no_output_timeout(None);
802850

803851
for (key, val) in metadata.environment_variables() {
804852
command = command.env(key, val);
@@ -885,7 +933,10 @@ pub(crate) struct BuildResult {
885933
#[cfg(test)]
886934
mod tests {
887935
use super::*;
888-
use crate::test::{assert_redirect, assert_success, wrapper, TestEnvironment};
936+
use crate::{
937+
db::Overrides,
938+
test::{assert_redirect, assert_success, wrapper, TestEnvironment},
939+
};
889940
use serde_json::Value;
890941

891942
fn remove_cache_files(env: &TestEnvironment, crate_: &str, version: &str) -> Result<()> {
@@ -1218,6 +1269,49 @@ mod tests {
12181269
});
12191270
}
12201271

1272+
#[test]
1273+
#[ignore]
1274+
fn test_timeout_skips_some_targets() {
1275+
wrapper(|env| {
1276+
let crate_ = "bs58";
1277+
let version = "0.5.0";
1278+
let mut builder = RustwideBuilder::init(env).unwrap();
1279+
let get_targets = || -> i32 {
1280+
env.db()
1281+
.conn()
1282+
.query_one(
1283+
"SELECT json_array_length(releases.doc_targets) FROM releases;",
1284+
&[],
1285+
)
1286+
.unwrap()
1287+
.get(0)
1288+
};
1289+
1290+
// Build once to time it and count how many targets are built
1291+
let start = Instant::now();
1292+
assert!(builder.build_package(crate_, version, PackageKind::CratesIo)?);
1293+
let timeout = start.elapsed() / 2;
1294+
let original_targets = get_targets();
1295+
1296+
// Build again with half the time and count how many targets are built
1297+
Overrides::save(
1298+
&mut env.db().conn(),
1299+
crate_,
1300+
Overrides {
1301+
memory: None,
1302+
targets: Some(original_targets as usize),
1303+
timeout: Some(timeout),
1304+
},
1305+
)?;
1306+
assert!(builder.build_package(crate_, version, PackageKind::CratesIo)?);
1307+
let new_targets = get_targets();
1308+
1309+
assert!(new_targets < original_targets);
1310+
1311+
Ok(())
1312+
});
1313+
}
1314+
12211315
#[test]
12221316
#[ignore]
12231317
fn test_implicit_features_for_optional_dependencies() {

0 commit comments

Comments
 (0)