Change:
As discovered by in rust-lang/cargo, a Cargo test is intermittently failing on Windows because a source file generated by Rustdoc is not being written before the process exits. The suspected issue is that Rustdoc spawns asynchronous writes here: However, Rustdoc never checks that the writes are completed. We should add a mechanism that prevents the process from exiting before these write tasks are completed. One possible solution: Add a field to . Increment at the start of , and decrement at the end of the spawned Rayon task. Add a method that spin-loops until is zero. (Alternatively: use a condition variable, and have notify the condvar when is decremented.) Either call explicitly in the end of the Rustdoc process, or add it as a implementation to . cc

Discussion:
"itertools", "minifier", "once_cell", <del> "rayon", </del> "regex", "rustdoc-json-types", "serde", "serde_json", "smallvec", "tempfile", <ins> "threadpool", </ins> "tracing", "tracing-subscriber", "tracing-tree",

] [[package]] <ins> name = "threadpool" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" dependencies = [ "num_cpus", ] [[package]] </ins> name = "tidy" version = "0.1.0" dependencies = [

tempfile = "3" tracing = "0.1" tracing-tree = "0.2.0" <ins> threadpool = "1.8.1" </ins> [dependencies.tracing-subscriber] version = "0.3.3" default-features = false features = ["fmt", "env-filter", "smallvec", "parking_lot", "ansi"] <del> [target.'cfg(windows)'.dependencies] rayon = "1.5.1" </del> [dev-dependencies] expect-test = "1.4.0"

//! //! On Windows this indirects IO into threads to work around performance issues //! with Defender (and other similar virus scanners that do blocking operations). <del> //! On other platforms this is a thin shim to fs. </del> //! //! Only calls needed to permit this workaround have been abstracted: thus //! fs::read is still done directly via the fs module; if in future rustdoc //! needs to read-after-write from a file, then it would be added to this //! abstraction. <ins> use std::cmp::max; </ins> use std::fs; use std::io; use std::path::{Path, PathBuf}; use std::string::ToString; use std::sync::mpsc::Sender; <ins> use std::thread::available_parallelism; use threadpool::ThreadPool; </ins> pub(crate) trait PathError { fn new<S, P: AsRef<Path>>(e: S, path: P) -> Self

pub(crate) struct DocFS { sync_only: bool, errors: Option<Sender<String>>, <ins> pool: ThreadPool, </ins> } impl DocFS { pub(crate) fn new(errors: Sender<String>) -> DocFS { <del> DocFS { sync_only: false, errors: Some(errors) } </del> <ins> const MINIMUM_NB_THREADS: usize = 2; DocFS { sync_only: false, errors: Some(errors), pool: ThreadPool::new( available_parallelism() .map(|nb| max(nb.get(), MINIMUM_NB_THREADS)) .unwrap_or(MINIMUM_NB_THREADS), ), } </ins> } pub(crate) fn set_sync_only(&mut self, sync_only: bool) {

where E: PathError, { <del> #[cfg(windows)] </del> if !self.sync_only { // A possible future enhancement after more detailed profiling would // be to create the file sync so errors are reported eagerly. let sender = self.errors.clone().expect("can't write after closing"); <del> rayon::spawn(move || { </del> <ins> self.pool.execute(move || { </ins> fs::write(&path, contents).unwrap_or_else(|e| { sender.send(format!(""{}": {}", path.display(), e)).unwrap_or_else(|_| { panic!("failed to send error on "{}"", path.display())

fs::write(&path, contents).map_err(|e| E::new(e, path))?; } <del> #[cfg(not(windows))] fs::write(&path, contents).map_err(|e| E::new(e, path))?; </del> Ok(()) } } <ins> impl Drop for DocFS { fn drop(&mut self) { self.pool.join(); } } </ins>