-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix race condition in fs::create_dir_all #39799
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @aturon (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
Thanks for the PR! Could this actually lift the implementation in the compiler which I believe tries to minimize syscalls in the "normal" case. Also yeah you can just add a test with a few threads hammering at creating directories and just make sure they don't fail. |
There's still room for a race condition if the directory is created after |
@alexcrichton How to "lift it"? Move from librustc to libstd and then use that new location instead in librustc code (or just call directly from the old librustc name)? @seppo0010 : I don't understand. If another thread called |
@dpc sorry, my case is quite weird, let me write it down a more detailed version. Let's say
I'm not saying it's a bug, but it might be unexpected, and I don't even know what may be better in that case. |
@dpc yeah we can just copy the code and then update the compiler to call libstd (as libstd would handle this race) |
src/libstd/fs.rs
Outdated
@@ -1769,11 +1769,20 @@ impl DirBuilder { | |||
} | |||
|
|||
fn create_dir_all(&self, path: &Path) -> io::Result<()> { | |||
use io::{ErrorKind}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this import get moved up to the top of the file with the other imports?
ping @dpc, thoughts on updating this? |
I agree with all feedback, and will get to it, probably on a weekend. If anyone would like you to pick it up, that's also fine with me. |
Thanks @dpc! |
bc9df80
to
9865187
Compare
Ooops. Sorry. I wasn't able to compile anything on current Also, seems like |
src/libstd/fs.rs
Outdated
Err(e) => return Err(e), | ||
} | ||
match path.parent() { | ||
Some(p) => try!(create_dir_all(p)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this call self.create_dir_all
to ensure reuse of configuration in self
? (if we add it in the future)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure.
Looking good to me, thanks! Could you also add a test for specifically this use case? |
Done |
Looks good! I think Travis has a tidy error though (r=me otherwise) |
@dpc |
@petrochenkov @dpc oh not yet, we'll need to wait for a new release. That's compiled against the stage0 libstd which doesn't have this fix. |
@bors: r+ |
📌 Commit 4e2114f has been approved by |
⌛ Testing commit 4e2114f with merge e1be448... |
💔 Test failed - status-appveyor |
@bors: retry
|
⌛ Testing commit 4e2114f with merge 4d01a7d... |
Fix race condition in fs::create_dir_all The code would crash if the directory was created after create_dir_all checked whether the directory already existed. This was contrary to the documentation which claimed to create the directory if it doesn't exist, implying (but not stating) that there would not be a failure due to the directory existing.
This seems to fail on Windows: https://ci.appveyor.com/project/rust-lang/rust/build/1.0.2436/job/ph8mhjt83f837xdl @bors r- |
I don't understand what happened, and I don't have Windows to debug. If one of the threads have failed, I would expect Could someone with Windows help? |
src/libstd/fs.rs
Outdated
@@ -2261,11 +2282,41 @@ mod tests { | |||
} | |||
|
|||
#[test] | |||
fn concurrent_recursive_mkdir() { | |||
for _ in 0..50 { | |||
let mut dir = tmpdir().join("a"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the main problem here is this use of tmpdir()
. It deletes the path it returns when it goes out of scope and because it's not assigned to a variable that's immediately. See here. Changing this to the following should fix that:
let tmpdir = tmpdir();
let mut dir = tmpdir.join("a");
src/libstd/fs.rs
Outdated
@@ -2261,11 +2282,41 @@ mod tests { | |||
} | |||
|
|||
#[test] | |||
fn concurrent_recursive_mkdir() { | |||
for _ in 0..50 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this test really need to be run 50 times? It fails the first time for me on Windows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's because it relies on there being a race condition between the threads, but race conditions don't occur very often. By doing the test multiple times, it is much more likely to hit the race condition.
src/libstd/fs.rs
Outdated
fn concurrent_recursive_mkdir() { | ||
for _ in 0..50 { | ||
let mut dir = tmpdir().join("a"); | ||
for _ in 0..100 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like you reduced the number of times this test is run rather than the the length of the path 😉.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh dear. 🤦
Increase lifetime of `tmpdir`, and really change the length of test path.
@bors: r+ |
📌 Commit 088696b has been approved by |
Fix race condition in fs::create_dir_all The code would crash if the directory was created after create_dir_all checked whether the directory already existed. This was contrary to the documentation which claimed to create the directory if it doesn't exist, implying (but not stating) that there would not be a failure due to the directory existing.
☀️ Test successful - status-appveyor, status-travis |
@dpc Thank you for fixing one of my least favorite Rust annoyances! The path to merge was a bit rock, but it's greatly appreciated. |
@emk You're welcome. I'm very happy about it myself. First, I got on contributor's list. Second, it was a torn for me too. :) |
This is a minor tweak to handle some problems which I've encountered several times now. The only real excuse for this change is that it's unobstrusive and has natural semantics. This patch modifies --filename to support two use cases: xsv partition --filename {}/cities.csv state . all-cities.csv xsv partition --filename {}/monuments.csv state . all-monuments.csv xsv partition --filename {}/parks.csv state . all-parks.csv Above, we want to partition our records by state into separate directories, but we have multiple kinds of data. xsv partition --filename {}/$(uuidgen).csv . input1.csv xsv partition --filename {}/$(uuidgen).csv . input2.csv Above, we're running multiple (possibly parallel) copies of xsv and we want to parition the data into multiple directories without a filename clash. There's one limitation to the implementation: We might theoretically hit the `create_dir_all` race condition recently fixed by rust-lang/rust#39799. I'm planning to supply a final patch which works around this race condition in stable Rust.
See rust-lang/rust#39799 for details.
rustdoc: Use `create_dir_all` to create output directory Currently rustdoc will fail if passed `-o foo/doc` if the `foo` directory doesn't exist. Also remove unneeded `mkdir` as `create_dir_all` can now handle concurrent invocations since #39799.
rustbuild: Replace create_dir_racy with create_dir_all `create_dir_all` has since been fixed (in #39799) so no need for `create_dir_racy`.
as std::fs::create_dir_all is now threadsafe rust-lang/rust#39799
The code would crash if the directory was created after create_dir_all
checked whether the directory already existed. This was contrary to
the documentation which claimed to create the directory if it doesn't
exist, implying (but not stating) that there would not be a failure
due to the directory existing.