| //! 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(); |
| } |
| } |