Skip to content

Commit 2c01bb8

Browse files
authored
Auto merge of #35718 - michaelwoerister:incr-comp-dir-locking, r=alexcrichton
Implement synchronization scheme for incr. comp. directory This PR implements a copy-on-write-based synchronization scheme for the incremental compilation cache directory. For technical details, see the documentation at the beginning of `rustc_incremental/persist/fs.rs`. The PR contains unit tests for some functions but for testing whether the scheme properly handles races, a more elaborate test setup would be needed. It would probably involve a small tool that allows to manipulate the incremental compilation directory in a controlled way and then letting a compiler instance run against directories in different states. I don't know if it's worth the trouble of adding another test category to `compiletest`, but I'd be happy to do so. Fixes #32754 Fixes #34957
2 parents 7a187c3 + bcd2f90 commit 2c01bb8

File tree

21 files changed

+1459
-178
lines changed

21 files changed

+1459
-178
lines changed

mk/crates.mk

+3-4
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ DEPS_rustc := syntax fmt_macros flate arena serialize getopts rbml \
112112
rustc_const_math syntax_pos rustc_errors
113113
DEPS_rustc_back := std syntax flate log libc
114114
DEPS_rustc_borrowck := rustc log graphviz syntax syntax_pos rustc_errors rustc_mir
115-
DEPS_rustc_data_structures := std log serialize
115+
DEPS_rustc_data_structures := std log serialize libc
116116
DEPS_rustc_driver := arena flate getopts graphviz libc rustc rustc_back rustc_borrowck \
117117
rustc_typeck rustc_mir rustc_resolve log syntax serialize rustc_llvm \
118118
rustc_trans rustc_privacy rustc_lint rustc_plugin \
@@ -137,9 +137,8 @@ DEPS_rustc_save_analysis := rustc log syntax syntax_pos serialize
137137
DEPS_rustc_typeck := rustc syntax syntax_pos rustc_platform_intrinsics rustc_const_math \
138138
rustc_const_eval rustc_errors
139139

140-
DEPS_rustdoc := rustc rustc_driver native:hoedown serialize getopts \
141-
test rustc_lint rustc_const_eval syntax_pos
142-
140+
DEPS_rustdoc := rustc rustc_driver native:hoedown serialize getopts test \
141+
rustc_lint rustc_const_eval syntax_pos rustc_data_structures
143142

144143
TOOL_DEPS_compiletest := test getopts log serialize
145144
TOOL_DEPS_rustdoc := rustdoc

src/librustc/hir/svh.rs

+15
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
1818
use std::fmt;
1919
use std::hash::{Hash, Hasher};
20+
use serialize::{Encodable, Decodable, Encoder, Decoder};
2021

2122
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
2223
pub struct Svh {
@@ -51,3 +52,17 @@ impl fmt::Display for Svh {
5152
f.pad(&self.to_string())
5253
}
5354
}
55+
56+
impl Encodable for Svh {
57+
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
58+
s.emit_u64(self.as_u64().to_le())
59+
}
60+
}
61+
62+
impl Decodable for Svh {
63+
fn decode<D: Decoder>(d: &mut D) -> Result<Svh, D::Error> {
64+
d.read_u64()
65+
.map(u64::from_le)
66+
.map(Svh::new)
67+
}
68+
}

src/librustc/session/mod.rs

+100-1
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ use syntax::feature_gate::AttributeType;
3333
use syntax_pos::{Span, MultiSpan};
3434

3535
use rustc_back::target::Target;
36+
use rustc_data_structures::flock;
3637
use llvm;
3738

3839
use std::path::{Path, PathBuf};
39-
use std::cell::{Cell, RefCell};
40+
use std::cell::{self, Cell, RefCell};
4041
use std::collections::{HashMap, HashSet};
4142
use std::env;
4243
use std::ffi::CString;
@@ -101,6 +102,8 @@ pub struct Session {
101102
/// macro name and defintion span in the source crate.
102103
pub imported_macro_spans: RefCell<HashMap<Span, (String, Span)>>,
103104

105+
incr_comp_session: RefCell<IncrCompSession>,
106+
104107
next_node_id: Cell<ast::NodeId>,
105108
}
106109

@@ -331,6 +334,76 @@ impl Session {
331334
&self.opts.search_paths,
332335
kind)
333336
}
337+
338+
pub fn init_incr_comp_session(&self,
339+
session_dir: PathBuf,
340+
lock_file: flock::Lock) {
341+
let mut incr_comp_session = self.incr_comp_session.borrow_mut();
342+
343+
if let IncrCompSession::NotInitialized = *incr_comp_session { } else {
344+
bug!("Trying to initialize IncrCompSession `{:?}`", *incr_comp_session)
345+
}
346+
347+
*incr_comp_session = IncrCompSession::Active {
348+
session_directory: session_dir,
349+
lock_file: lock_file,
350+
};
351+
}
352+
353+
pub fn finalize_incr_comp_session(&self, new_directory_path: PathBuf) {
354+
let mut incr_comp_session = self.incr_comp_session.borrow_mut();
355+
356+
if let IncrCompSession::Active { .. } = *incr_comp_session { } else {
357+
bug!("Trying to finalize IncrCompSession `{:?}`", *incr_comp_session)
358+
}
359+
360+
// Note: This will also drop the lock file, thus unlocking the directory
361+
*incr_comp_session = IncrCompSession::Finalized {
362+
session_directory: new_directory_path,
363+
};
364+
}
365+
366+
pub fn mark_incr_comp_session_as_invalid(&self) {
367+
let mut incr_comp_session = self.incr_comp_session.borrow_mut();
368+
369+
let session_directory = match *incr_comp_session {
370+
IncrCompSession::Active { ref session_directory, .. } => {
371+
session_directory.clone()
372+
}
373+
_ => bug!("Trying to invalidate IncrCompSession `{:?}`",
374+
*incr_comp_session),
375+
};
376+
377+
// Note: This will also drop the lock file, thus unlocking the directory
378+
*incr_comp_session = IncrCompSession::InvalidBecauseOfErrors {
379+
session_directory: session_directory
380+
};
381+
}
382+
383+
pub fn incr_comp_session_dir(&self) -> cell::Ref<PathBuf> {
384+
let incr_comp_session = self.incr_comp_session.borrow();
385+
cell::Ref::map(incr_comp_session, |incr_comp_session| {
386+
match *incr_comp_session {
387+
IncrCompSession::NotInitialized => {
388+
bug!("Trying to get session directory from IncrCompSession `{:?}`",
389+
*incr_comp_session)
390+
}
391+
IncrCompSession::Active { ref session_directory, .. } |
392+
IncrCompSession::Finalized { ref session_directory } |
393+
IncrCompSession::InvalidBecauseOfErrors { ref session_directory } => {
394+
session_directory
395+
}
396+
}
397+
})
398+
}
399+
400+
pub fn incr_comp_session_dir_opt(&self) -> Option<cell::Ref<PathBuf>> {
401+
if self.opts.incremental.is_some() {
402+
Some(self.incr_comp_session_dir())
403+
} else {
404+
None
405+
}
406+
}
334407
}
335408

336409
pub fn build_session(sopts: config::Options,
@@ -446,13 +519,39 @@ pub fn build_session_(sopts: config::Options,
446519
injected_panic_runtime: Cell::new(None),
447520
available_macros: RefCell::new(HashSet::new()),
448521
imported_macro_spans: RefCell::new(HashMap::new()),
522+
incr_comp_session: RefCell::new(IncrCompSession::NotInitialized),
449523
};
450524

451525
init_llvm(&sess);
452526

453527
sess
454528
}
455529

530+
/// Holds data on the current incremental compilation session, if there is one.
531+
#[derive(Debug)]
532+
pub enum IncrCompSession {
533+
// This is the state the session will be in until the incr. comp. dir is
534+
// needed.
535+
NotInitialized,
536+
// This is the state during which the session directory is private and can
537+
// be modified.
538+
Active {
539+
session_directory: PathBuf,
540+
lock_file: flock::Lock,
541+
},
542+
// This is the state after the session directory has been finalized. In this
543+
// state, the contents of the directory must not be modified any more.
544+
Finalized {
545+
session_directory: PathBuf,
546+
},
547+
// This is an error state that is reached when some compilation error has
548+
// occurred. It indicates that the contents of the session directory must
549+
// not be used, since they might be invalid.
550+
InvalidBecauseOfErrors {
551+
session_directory: PathBuf,
552+
}
553+
}
554+
456555
fn init_llvm(sess: &Session) {
457556
unsafe {
458557
// Before we touch LLVM, make sure that multithreading is enabled.

src/librustc/util/fs.rs

+38-3
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,49 @@ pub fn fix_windows_verbatim_for_gcc(p: &Path) -> PathBuf {
5656
}
5757
}
5858

59+
pub enum LinkOrCopy {
60+
Link,
61+
Copy
62+
}
63+
5964
/// Copy `p` into `q`, preferring to use hard-linking if possible. If
6065
/// `q` already exists, it is removed first.
61-
pub fn link_or_copy<P: AsRef<Path>, Q: AsRef<Path>>(p: P, q: Q) -> io::Result<()> {
66+
/// The result indicates which of the two operations has been performed.
67+
pub fn link_or_copy<P: AsRef<Path>, Q: AsRef<Path>>(p: P, q: Q) -> io::Result<LinkOrCopy> {
6268
let p = p.as_ref();
6369
let q = q.as_ref();
6470
if q.exists() {
6571
try!(fs::remove_file(&q));
6672
}
67-
fs::hard_link(p, q)
68-
.or_else(|_| fs::copy(p, q).map(|_| ()))
73+
74+
match fs::hard_link(p, q) {
75+
Ok(()) => Ok(LinkOrCopy::Link),
76+
Err(_) => {
77+
match fs::copy(p, q) {
78+
Ok(_) => Ok(LinkOrCopy::Copy),
79+
Err(e) => Err(e)
80+
}
81+
}
82+
}
83+
}
84+
85+
// Like std::fs::create_dir_all, except handles concurrent calls among multiple
86+
// threads or processes.
87+
pub fn create_dir_racy(path: &Path) -> io::Result<()> {
88+
match fs::create_dir(path) {
89+
Ok(()) => return Ok(()),
90+
Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => return Ok(()),
91+
Err(ref e) if e.kind() == io::ErrorKind::NotFound => {}
92+
Err(e) => return Err(e),
93+
}
94+
match path.parent() {
95+
Some(p) => try!(create_dir_racy(p)),
96+
None => return Err(io::Error::new(io::ErrorKind::Other,
97+
"failed to create whole tree")),
98+
}
99+
match fs::create_dir(path) {
100+
Ok(()) => Ok(()),
101+
Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => Ok(()),
102+
Err(e) => Err(e),
103+
}
69104
}

0 commit comments

Comments
 (0)