From 3c283d4f47a54a65b2e6766d951c2bf49ac0341c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joosep=20Orasm=C3=A4e?= Date: Sun, 6 Sep 2026 12:26:59 +0300 Subject: [PATCH 1/3] Copy metadata workproduct when session dir rename fails --- compiler/rustc_incremental/src/persist/fs.rs | 35 ++++++++++++++++++- .../rustc_incremental/src/persist/fs/tests.rs | 15 ++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_incremental/src/persist/fs.rs b/compiler/rustc_incremental/src/persist/fs.rs index 7493653987a91..5decbcbc09af0 100644 --- a/compiler/rustc_incremental/src/persist/fs.rs +++ b/compiler/rustc_incremental/src/persist/fs.rs @@ -129,6 +129,7 @@ const DEP_GRAPH_FILENAME: &str = "dep-graph.bin"; const STAGING_DEP_GRAPH_FILENAME: &str = "dep-graph.part.bin"; const WORK_PRODUCTS_FILENAME: &str = "work-products.bin"; const QUERY_CACHE_FILENAME: &str = "query-cache.bin"; +const METADATA_WORK_PRODUCT_FILENAME: &str = "metadata.rmeta"; // We encode integers using the following base, so they are shorter than decimal // or hexadecimal numbers (we want short file and directory names). Since these @@ -332,7 +333,27 @@ pub fn finalize_session_directory( let new_path = incr_comp_session_dir.parent().unwrap().join(&*sub_dir_name); debug!("finalize_session_directory() - new path: {}", new_path.display()); - match rename_path_with_retry(&*incr_comp_session_dir, &new_path, 3) { + let result = std_fs::rename(&*incr_comp_session_dir, &new_path).or_else(|e| { + if e.kind() != ErrorKind::PermissionDenied { + return Err(e); + } + + // On ReFS, renaming a directory that contains a hard link to the metadata workproduct file + // can fail if it is being used by another process (such as another rustc instance). + // As a fallback, we try to replace the hard link with a copy, which should allow the + // rename to succeed. + // See https://github.com/rust-lang/rust/issues/151181 + if let Err(err) = replace_hard_link_with_copy(&in_incr_comp_dir_sess( + &incr_comp_session, + METADATA_WORK_PRODUCT_FILENAME, + )) { + debug!("finalize_session_directory() - error replacing hard link with copy: {}", err); + } + + rename_path_with_retry(&*incr_comp_session_dir, &new_path, 3) + }); + + match result { Ok(_) => { debug!("finalize_session_directory() - directory renamed successfully"); } @@ -893,3 +914,15 @@ fn rename_path_with_retry(from: &Path, to: &Path, mut retries_left: usize) -> st } } } + +/// Turns a hard link of the file at `path` into a copy. +fn replace_hard_link_with_copy(path: &Path) -> std::io::Result<()> { + let tmp_name = path.with_added_extension("tmp"); + + // In case a stale temporary file was linked from a previous failed attempt. + safe_remove_file(&tmp_name)?; + + std_fs::copy(path, &tmp_name).and_then(|_| std_fs::rename(&tmp_name, path)).inspect_err(|_| { + let _ = safe_remove_file(&tmp_name); + }) +} diff --git a/compiler/rustc_incremental/src/persist/fs/tests.rs b/compiler/rustc_incremental/src/persist/fs/tests.rs index 644b8187621c9..3652656b7c48f 100644 --- a/compiler/rustc_incremental/src/persist/fs/tests.rs +++ b/compiler/rustc_incremental/src/persist/fs/tests.rs @@ -75,3 +75,18 @@ fn test_find_source_directory_in_iter() { None ); } + +#[test] +fn test_replace_hard_link_with_copy_unshares_hard_link() { + let dir = rustc_fs_util::TempDirBuilder::new().tempdir_in(std::env::temp_dir()).unwrap(); + let file = dir.path().join("file"); + let link = dir.path().join("link"); + std_fs::write(&file, b"original").unwrap(); + std_fs::hard_link(&file, &link).unwrap(); + + replace_hard_link_with_copy(&link).unwrap(); + + std_fs::write(&file, b"changed").unwrap(); + assert_eq!(std_fs::read(&link).unwrap(), b"original"); + assert!(!link.with_added_extension("tmp").exists()); +} From 26e7e774b191c43e96beb27840d613400fd5b8db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joosep=20Orasm=C3=A4e?= Date: Wed, 9 Sep 2026 21:24:31 +0300 Subject: [PATCH 2/3] Gate finalize_session_directory rename fallbacks to windows --- compiler/rustc_incremental/src/persist/fs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_incremental/src/persist/fs.rs b/compiler/rustc_incremental/src/persist/fs.rs index 5decbcbc09af0..70045f6a0fcff 100644 --- a/compiler/rustc_incremental/src/persist/fs.rs +++ b/compiler/rustc_incremental/src/persist/fs.rs @@ -334,7 +334,7 @@ pub fn finalize_session_directory( debug!("finalize_session_directory() - new path: {}", new_path.display()); let result = std_fs::rename(&*incr_comp_session_dir, &new_path).or_else(|e| { - if e.kind() != ErrorKind::PermissionDenied { + if !cfg!(windows) || e.kind() != ErrorKind::PermissionDenied { return Err(e); } From 0bae8cff69147bd419d03251ec8bc3a63823c676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joosep=20Orasm=C3=A4e?= Date: Tue, 15 Sep 2026 21:05:27 +0300 Subject: [PATCH 3/3] Move metadata work product name into a constant --- compiler/rustc_incremental/src/persist/fs.rs | 9 +++++++-- compiler/rustc_interface/src/queries.rs | 6 +++--- compiler/rustc_metadata/src/rmeta/encoder.rs | 9 +++++---- compiler/rustc_middle/src/dep_graph/graph.rs | 7 +++++++ 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_incremental/src/persist/fs.rs b/compiler/rustc_incremental/src/persist/fs.rs index 70045f6a0fcff..a6394e905865d 100644 --- a/compiler/rustc_incremental/src/persist/fs.rs +++ b/compiler/rustc_incremental/src/persist/fs.rs @@ -115,6 +115,8 @@ use rustc_data_structures::svh::Svh; use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_data_structures::{base_n, flock}; use rustc_fs_util::{LinkOrCopy, link_or_copy, try_canonicalize}; +use rustc_middle::dep_graph::WorkProduct; +use rustc_session::config::OutputType; use rustc_session::{IncrCompSession, Session, StableCrateId}; use rustc_span::{Symbol, bug}; use tracing::debug; @@ -129,7 +131,6 @@ const DEP_GRAPH_FILENAME: &str = "dep-graph.bin"; const STAGING_DEP_GRAPH_FILENAME: &str = "dep-graph.part.bin"; const WORK_PRODUCTS_FILENAME: &str = "work-products.bin"; const QUERY_CACHE_FILENAME: &str = "query-cache.bin"; -const METADATA_WORK_PRODUCT_FILENAME: &str = "metadata.rmeta"; // We encode integers using the following base, so they are shorter than decimal // or hexadecimal numbers (we want short file and directory names). Since these @@ -345,7 +346,11 @@ pub fn finalize_session_directory( // See https://github.com/rust-lang/rust/issues/151181 if let Err(err) = replace_hard_link_with_copy(&in_incr_comp_dir_sess( &incr_comp_session, - METADATA_WORK_PRODUCT_FILENAME, + &format!( + "{}.{}", + WorkProduct::METADATA_WORKPRODUCT_CGU_NAME, + OutputType::Metadata.extension() + ), )) { debug!("finalize_session_directory() - error replacing hard link with copy: {}", err); } diff --git a/compiler/rustc_interface/src/queries.rs b/compiler/rustc_interface/src/queries.rs index 51e26d4ba5044..2f196c5e5d609 100644 --- a/compiler/rustc_interface/src/queries.rs +++ b/compiler/rustc_interface/src/queries.rs @@ -7,7 +7,7 @@ use rustc_data_structures::svh::Svh; use rustc_errors::timings::TimingSection; use rustc_hir::def_id::LOCAL_CRATE; use rustc_metadata::EncodedMetadata; -use rustc_middle::dep_graph::{DepGraph, WorkProductMap}; +use rustc_middle::dep_graph::{DepGraph, WorkProduct, WorkProductMap}; use rustc_middle::ty::TyCtxt; use rustc_session::config::{self, OutputFilenames, OutputType}; use rustc_session::{IncrCompSession, Session}; @@ -99,8 +99,8 @@ impl Linker { let (id, product) = rustc_incremental::copy_cgu_workproduct_to_incr_comp_cache_dir( sess, incr_comp_session.as_ref().unwrap(), - "metadata", - &[("rmeta", path)], + WorkProduct::METADATA_WORKPRODUCT_CGU_NAME, + &[(OutputType::Metadata.extension(), path)], &[], ); work_products.insert(id, product); diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 12c8fac8cc2eb..8481db9d3c523 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -18,7 +18,7 @@ use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE, LocalDefId, LocalDefIdSet}; use rustc_hir::definitions::DefPathData; use rustc_hir::find_attr; use rustc_hir_pretty::id_to_string; -use rustc_middle::dep_graph::WorkProductId; +use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::middle::dependency_format::Linkage; use rustc_middle::mir::interpret; use rustc_middle::query::Providers; @@ -28,7 +28,7 @@ use rustc_middle::ty::codec::TyEncoder; use rustc_middle::ty::fast_reject::{self, TreatParams}; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder, opaque}; use rustc_session::config::mitigation_coverage::DeniedPartialMitigation; -use rustc_session::config::{OptLevel, TargetModifier}; +use rustc_session::config::{OptLevel, OutputType, TargetModifier}; use rustc_span::def_id::CRATE_MOD_ID; use rustc_span::hygiene::HygieneEncodeContext; use rustc_span::{ @@ -2502,11 +2502,12 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { // If the metadata dep-node is green, try to reuse the saved work product. if tcx.dep_graph.is_fully_enabled() - && let work_product_id = WorkProductId::from_cgu_name("metadata") + && let work_product_id = + WorkProductId::from_cgu_name(WorkProduct::METADATA_WORKPRODUCT_CGU_NAME) && let Some(work_product) = tcx.dep_graph.previous_work_product(&work_product_id) && tcx.dep_graph.try_mark_green(tcx, &dep_node).is_some() { - let saved_path = &work_product.saved_files["rmeta"]; + let saved_path = &work_product.saved_files[OutputType::Metadata.extension()]; let incr_comp_session_dir = &tcx.incr_comp_session.unwrap().session_directory; let source_file_in_incr_dir = &incr_comp_session_dir.join(saved_path); debug!("copying preexisting metadata from {source_file_in_incr_dir:?} to {path:?}"); diff --git a/compiler/rustc_middle/src/dep_graph/graph.rs b/compiler/rustc_middle/src/dep_graph/graph.rs index a4165d793069d..dcb5775f20595 100644 --- a/compiler/rustc_middle/src/dep_graph/graph.rs +++ b/compiler/rustc_middle/src/dep_graph/graph.rs @@ -1143,6 +1143,13 @@ pub struct WorkProduct { pub saved_files: UnordMap, } +impl WorkProduct { + /// The metadata work product is not produced by any CGU and thus its + /// name cannot be derived from `CodegenUnit`. Both the writers and readers + /// of the metadata work product use this constant to agree on the name. + pub const METADATA_WORKPRODUCT_CGU_NAME: &str = "metadata"; +} + pub type WorkProductMap = UnordMap; // Index type for `DepNodeData`'s edges.