| //! Rustdoc's FileSystem abstraction module. | |
| //! | |
| //! On Windows this indirects IO into threads to work around performance issues | |
| //! with Defender (and other similar virus scanners that do blocking operations). | |
| //! | |
| //! 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. | |
| use std::cmp::max; | |
| use std::path::{Path, PathBuf}; | |
| use std::sync::mpsc::Sender; | |
| use std::thread::available_parallelism; | |
| use std::{fs, io}; | |
| use threadpool::ThreadPool; | |
| pub(crate) trait PathError { | |
| fn new<S, P: AsRef<Path>>(e: S, path: P) -> Self | |
| where | |
| S: ToString + Sized; | |
| } | |
| pub(crate) struct DocFS { | |
| sync_only: bool, | |
| errors: Option<Sender<String>>, | |
| pool: ThreadPool, | |
| } | |
| impl DocFS { | |
| pub(crate) fn new(errors: Sender<String>) -> DocFS { | |
| 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), | |
| ), | |
| } | |
| } | |
| pub(crate) fn set_sync_only(&mut self, sync_only: bool) { | |
| self.sync_only = sync_only; | |
| } | |
| pub(crate) fn close(&mut self) { | |
| self.errors = None; | |
| } | |
| pub(crate) fn create_dir_all<P: AsRef<Path>>(&self, path: P) -> io::Result<()> { | |
| // For now, dir creation isn't a huge time consideration, do it | |
| // synchronously, which avoids needing ordering between write() actions | |
| // and directory creation. | |
| fs::create_dir_all(path) | |
| } | |
| pub(crate) fn write<E>( | |
| &self, | |
| path: PathBuf, | |
| contents: impl 'static + Send + AsRef<[u8]>, | |
| ) -> Result<(), E> | |
| where | |
| E: PathError, | |
| { | |
| 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"); | |
| self.pool.execute(move || { | |
| fs::write(&path, contents).unwrap_or_else(|e| { | |
| sender.send(format!("\"{path}\": {e}", path = path.display())).unwrap_or_else( | |
| |_| panic!("failed to send error on \"{}\"", path.display()), | |
| ) | |
| }); | |
| }); | |
| } else { | |
| fs::write(&path, contents).map_err(|e| E::new(e, path))?; | |
| } | |
| Ok(()) | |
| } | |
| } | |
| impl Drop for DocFS { | |
| fn drop(&mut self) { | |
| self.pool.join(); | |
| } | |
| } |