Skip to content
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

Merge tempdir api #33

Merged
merged 5 commits into from
Feb 7, 2018
Merged

Merge tempdir api #33

merged 5 commits into from
Feb 7, 2018

Conversation

KodrAus
Copy link
Collaborator

@KodrAus KodrAus commented Sep 12, 2017

Combines the tempdir API, beefs out the docs, and refactors NamedTempFile a bit.

If you'd rather I didn't refactor the internal structure then I can rejig this PR to minimise file movement.

Some previous discussion/iteration on combining the APIs: KodrAus#1

cc: @Stebalien @alexcrichton

@KodrAus
Copy link
Collaborator Author

KodrAus commented Sep 12, 2017

@Stebalien you'll probably want to take a thorough look through the docs I've added, I might've gotten some security notices wrong.

@KodrAus
Copy link
Collaborator Author

KodrAus commented Sep 12, 2017

Support for 1.13.0 needs a quick change in remove_dir_all

@Stebalien
Copy link
Owner

I will review this ASAP but that probably won't be till the weekend.

@KodrAus
Copy link
Collaborator Author

KodrAus commented Sep 22, 2017

Friendly ping @Stebalien to take a look at this when you've got bandwidth. No pressure though.

We could move forward without that fix in remove_dir_all but it would mean bumping the minimum rust version supported.

@opilar
Copy link

opilar commented Sep 23, 2017

A little question: when tempdir and tempfile will merge apis, should we rename the crate to temp?

@KodrAus
Copy link
Collaborator Author

KodrAus commented Sep 24, 2017

@opilar the idea is to keep the tempfile crate name.

@Stebalien
Copy link
Owner

@KodrAus sorry, my family ended up visiting that weekend and now I'm traveling but I'll try to get to this in the next few days.

@Stebalien
Copy link
Owner

I'm still alive, FYI (just busy). Sorry.

@KodrAus
Copy link
Collaborator Author

KodrAus commented Oct 10, 2017

@Stebalien that's no problem :) We're not working on a tight timeline here so there's no hurry, no pressure. I just remembered there was a new version of remove_dir_all I could use.

@azriel91
Copy link

ping! I came across this when looking to write tests that use a temporary directory. Is this PR close to being merged, or shall I use tempdir?

@KodrAus
Copy link
Collaborator Author

KodrAus commented Oct 30, 2017

@azriel91 sorry I just got a chance to loop back here. I would use tempdir. We'll have a nice smooth upgrade path when the APIs are merged, but this PR is only one step of that process.

Copy link
Owner

@Stebalien Stebalien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow. A review? Yes! A review! Sorry, the line change count turned me off reviewing this but it really didn't take that long (lots of nice documentation). This looks really good.

Three main things:

  1. Misc doc fixes.
  2. Make tempdir functions consistent with tempfile functions.
  3. Reuse the builder and generalize it to directories (importantly, reusing the name logic).

Also, please rebase or allow me to push a rebased version. There are now a few merge conflicts.


At this point, I'd totally understand if you've moved on and can't work on this. If so, I can take this to completion.

src/lib.rs Outdated
//! its destructor doesn't run. This is because `tempfile()` relies on the OS to cleanup the
//! underlying file so the file while `NamedTempFile` relies on its destructor to do so.
//! `tempfile` will (almost) never fail to cleanup temporary resources but `TempDir` and `NamedTempFile` will if
//! their destructor doesn't run. This is because `tempfile` relies on the OS to cleanup the
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

distructors

src/lib.rs Outdated
//! underlying file so the file while `NamedTempFile` relies on its destructor to do so.
//! `tempfile` will (almost) never fail to cleanup temporary resources but `TempDir` and `NamedTempFile` will if
//! their destructor doesn't run. This is because `tempfile` relies on the OS to cleanup the
//! underlying file so the file while `TempDir` and `NamedTempFile` rely on their destructor to do so.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destructors

src/lib.rs Outdated

#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
html_favicon_url = "https://www.rust-lang.org/favicon.ico",
html_root_url = "https://docs.rs/tempdir/0.3.5")]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably remove this (or at least replace tempdir with tempfile).

src/dir.rs Outdated
///
/// [`TempDir`]: struct.TempDir.html
/// [resource-leaking]: struct.TempDir.html#resource-leaking
pub fn tempdir(prefix: &str) -> io::Result<TempDir> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer it if this used the same temporary file builder system as NamedTempFile. As a matter of fact, we can even reuse the same builder (if we generalize it). I'd change the create and create_in methods on the builder to file and file_in and add directory and directory_in methods.

src/dir.rs Outdated
/// # Ok(())
/// # }
/// ```
pub fn new(prefix: &str) -> io::Result<TempDir> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here (I wouldn't include the prefix option).

src/dir.rs Outdated
/// # Ok(())
/// # }
/// ```
pub fn new_in<P: AsRef<Path>>(tmpdir: P, prefix: &str) -> io::Result<TempDir> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same (no prefix option, use the builder). That should reduce code duplication.

src/dir.rs Outdated
let cur_dir = env::current_dir()?;
storage = cur_dir.join(tmpdir);
tmpdir = &storage;
// return TempDir::new_in(&cur_dir.join(tmpdir), prefix);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commented out code?

&mut self.inner_mut().file
}

/// Convert the temporary file into a `std::fs::File`.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should note that the inner file will be deleted.

@KodrAus
Copy link
Collaborator Author

KodrAus commented Jan 8, 2018

Thanks for the thorough review @Stebalien! I'll rebase and work through the changes over the next week 👍

@KodrAus
Copy link
Collaborator Author

KodrAus commented Jan 15, 2018

I've pushed up some changes based on the feedback because having code only living on my local machine makes me nervous...

I haven't updated the doc tests yet, and the behaviour of TempDir is now subtly different with respect to leading .s in the directory name. I think it would be good to think a bit about what we might want to do with the Builder in the future, and whether we're painting ourselves into any corners by sharing it between temporary files and directories.

@Stebalien
Copy link
Owner

Stebalien commented Jan 18, 2018

I'll try to take a look at it this weekend (and not just put this off for a few months this time...).

Note: I'm not entirely against changing the default for tempfile if it's a problem for tempdir. IIRC, we never made any guarantees (unless the user explicitly specified the prefix).

@KodrAus
Copy link
Collaborator Author

KodrAus commented Jan 18, 2018

No problem! I'll try get the tests and docs updated before then.

Copy link
Contributor

@jasonwhite jasonwhite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took it upon myself to review this. I'd like to get this merged because my PR is blocked on this one.

I noticed some of the new files have trailing white space. Would be nice to remove that. (Sorry, I'm a little pedantic about this because my editor shows it in bright red.)

src/dir.rs Outdated
/// receiving a signal like `SIGINT`, then the temporary directory
/// will not be deleted.
///
// # Examples
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing a triple slash (///) here.

let result = remove_dir_all(self.path());

// Prevent the Drop impl from removing the dir a second time.
self.path = None;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the entire reason for wrapping the path in an Option is to prevent Drop from attempting to delete the directory a second time, then you should be able to eliminate the need for Option with this:

pub fn close(mut self) -> io::Result<()> {
    // Replace the path with an empty one to avoid a memory leak by mem::forget.
    let path = std::mem::replace(self.path, PathBuf::new());
    std::mem::forget(self); // Don't call drop
    remove_dir_all(&path)
}

The same would have to be done to into_path() of course.

This is cleaner than using an Option in my opinion.

Copy link
Contributor

@jasonwhite jasonwhite Jan 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another problem to consider is what to do if close() fails. The caller might wish to implement a retry if, for example, they've created a temporary directory on a network file system that might have intermittent failures. (I encounter this problem a lot with network shares under Windows.)

However, it looks like you want API compatibility with the tempdir crate, so you probably can't solve this problem.

Edit: It looks like this problem has already been talked to death in rust-lang/api-guidelines#61. Unfortunately, it seems the conclusion is that the failure should only be reported and not handled by the program. Retries of file operations on spotty Windows network shares have solved a lot of problems for me in the past.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The caller might wish to implement a retry if, for example, they've created a temporary directory on a network file system that might have intermittent failures.

...

It looks like this problem has already been talked to death in

Also, in posix, one should generally not retry a close. The correct way to handle such errors is to catch them with an fsync first.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Stebalien Are you talking about close(2) or remove_dir_all? I agree about close(2), but deleting files over a network can have intermittent failures where retrying is the right thing to do.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. I was thinking about close. You're right, this is a bit different. However, I kind of feel that this is, really, the best API. We could put the path into the error but that'll make this less ergonomic.

Cargo.toml Outdated
version = "2.2.0"
authors = ["Steven Allen <steven@stebalien.com>"]
description = "Securely create temporary files."
version = "2.1.6"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a package version number regression here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah well spotted!

src/lib.rs Outdated

#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
html_favicon_url = "https://www.rust-lang.org/favicon.ico",
html_root_url = "https://docs.rs/tempfile/2.1.6")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This version number should match the one in Cargo.toml (once the one in there is fixed).

Copy link
Owner

@Stebalien Stebalien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, nice job with the builder. However, I think I may have given you the wrong impression in a few of my comments regarding the helper functions.

src/lib.rs Outdated
/// ```
///
/// [`NamedTempFile::new`]: struct.NamedTempFile.html#method.new
pub fn named_tempfile(&self) -> io::Result<NamedTempFile> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kind of a nit... this is a really long name. I'd rather just tempfile or, even better, file and directory (the fact that they're temporary is, IMO, unambiguous).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a fair point 👍

src/lib.rs Outdated
dir = &storage;
}

for _ in 0..::NUM_RETRIES {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to refactor this out into a helper function if possible.

fn create_helper<T, F: Fn(PathBuf) -> io::Result<T>>(base: &Path, f: F) -> io::Result<T> { /* ... */ }

// ...

create_helper(path, dir::create)

src/dir.rs Outdated

Err(Error::new(ErrorKind::AlreadyExists,
"too many temporary directories already exist"))
pub fn new() -> io::Result<TempDir> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there may have been a miscommunication here. I still think providing a new_in is worth it for the convenience, I just wanted it to use the builder internally (code duplication) and have neither new nor new_in take a prefix argument (mirror NamedTempFile).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, I did misunderstand, I'll restore those methods 👍

src/file/mod.rs Outdated
/// ```
///
/// [`NamedTempFile::new`]: #method.new
pub fn new_in<P: AsRef<Path>>(dir: P) -> io::Result<NamedTempFile> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, I'd keep the new_in.

src/dir.rs Outdated
@@ -355,3 +233,8 @@ impl Drop for TempDir {
}
}
}

// pub(crate)
pub fn create(path: PathBuf) -> io::Result<TempDir> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was wrong with tempdir, tempdir_in, named_tempfile, and named_tempfile_in? This may also have been a miscommunication on my part. I meant that one should use the builder when specifying custom options, not that we shouldn't have these convenience methods (they are quite convenient).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that was another misunderstanding, I'll restore these too.

}
}

// pub(crate)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't actually pub(crate). This module isn't exported (as far as I can tell).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right, it's just a note that this method is accessible throughout the crate, but not public since our min Rust version doesn't support pub(restricted).

let result = remove_dir_all(self.path());

// Prevent the Drop impl from removing the dir a second time.
self.path = None;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. I was thinking about close. You're right, this is a bit different. However, I kind of feel that this is, really, the best API. We could put the path into the error but that'll make this less ergonomic.

@KodrAus
Copy link
Collaborator Author

KodrAus commented Jan 27, 2018

Thanks @jasonwhite and @Stebalien! I'll work through this round of feedback over this week.

@Stebalien
Copy link
Owner

Awesome! Thanks for putting this together.

@KodrAus
Copy link
Collaborator Author

KodrAus commented Feb 6, 2018

@Stebalien @jasonwhite this should be ready for another look now.

Copy link
Contributor

@jasonwhite jasonwhite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

@Stebalien Stebalien merged commit 30d74f7 into Stebalien:master Feb 7, 2018
@Stebalien
Copy link
Owner

🎉

@KodrAus thanks so much for both implementing this and then bearing with me on this long and drawn out CR.

@jasonwhite thanks for pitching in and waiting for this CR to complete.

@KodrAus
Copy link
Collaborator Author

KodrAus commented Feb 7, 2018

Thanks all!

I'll get the ball rolling on deprecating the tempdir crate. Looks like we've got an open design question in tempdir we might want to look at here but otherwise everything looks good on that end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants