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

std: Modify TempDir to not fail on drop. Closes #12628 #14215

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion src/libstd/io/buffered.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ impl<W: Writer> Writer for BufferedWriter<W> {
impl<W: Writer> Drop for BufferedWriter<W> {
fn drop(&mut self) {
if self.inner.is_some() {
// FIXME(#12628): should this error be ignored?
// dtors should not fail, so we ignore a failed flush
let _ = self.flush_buf();
}
}
Expand Down Expand Up @@ -370,6 +370,7 @@ mod test {
use io;
use prelude::*;
use super::*;
use super::super::{IoResult, EndOfFile};
use super::super::mem::{MemReader, MemWriter, BufReader};
use self::test::Bencher;
use str::StrSlice;
Expand Down Expand Up @@ -584,6 +585,24 @@ mod test {
assert_eq!(it.next(), None);
}

#[test]
#[should_fail]
fn dont_fail_in_drop_on_failed_flush() {
struct FailFlushWriter;

impl Writer for FailFlushWriter {
fn write(&mut self, _buf: &[u8]) -> IoResult<()> { Ok(()) }
fn flush(&mut self) -> IoResult<()> { Err(io::standard_error(EndOfFile)) }
}

let writer = FailFlushWriter;
let _writer = BufferedWriter::new(writer);

// Trigger failure. If writer fails *again* due to the flush
// error then the process will abort.
fail!();
}

#[bench]
fn bench_buffered_reader(b: &mut Bencher) {
b.iter(|| {
Expand Down
36 changes: 27 additions & 9 deletions src/libstd/io/tempfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,22 @@

//! Temporary files and directories

use io::fs;
use io::{fs, IoResult};
use io;
use iter::{Iterator, range};
use libc;
use ops::Drop;
use option::{Option, None, Some};
use os;
use path::{Path, GenericPath};
use result::{Ok, Err, ResultUnwrap};
use result::{Ok, Err};
use sync::atomics;

/// A wrapper for a path to temporary directory implementing automatic
/// scope-based deletion.
pub struct TempDir {
path: Option<Path>
path: Option<Path>,
disarmed: bool
}

impl TempDir {
Expand All @@ -48,7 +49,7 @@ impl TempDir {
let p = tmpdir.join(filename);
match fs::mkdir(&p, io::UserRWX) {
Err(..) => {}
Ok(()) => return Some(TempDir { path: Some(p) })
Ok(()) => return Some(TempDir { path: Some(p), disarmed: false })
}
}
None
Expand All @@ -75,15 +76,32 @@ impl TempDir {
pub fn path<'a>(&'a self) -> &'a Path {
self.path.get_ref()
}

/// Close and remove the temporary directory
///
/// Although `TempDir` removes the directory on drop, in the destructor
/// any errors are ignored. To detect errors cleaning up the temporary
/// directory, call `close` instead.
pub fn close(mut self) -> IoResult<()> {
self.cleanup_dir()
}

fn cleanup_dir(&mut self) -> IoResult<()> {
assert!(!self.disarmed);
self.disarmed = true;
match self.path {
Some(ref p) => {
fs::rmdir_recursive(p)
}
None => Ok(())
}
}
}

impl Drop for TempDir {
fn drop(&mut self) {
for path in self.path.iter() {
if path.exists() {
// FIXME: is failing the right thing to do?
fs::rmdir_recursive(path).unwrap();
}
if !self.disarmed {
let _ = self.cleanup_dir();
}
}
}
Expand Down
59 changes: 59 additions & 0 deletions src/test/run-pass/tempfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,50 @@ fn test_rm_tempdir() {
assert!(!path.exists());
}

fn test_rm_tempdir_close() {
let (tx, rx) = channel();
let f: proc():Send = proc() {
let tmp = TempDir::new("test_rm_tempdir").unwrap();
tx.send(tmp.path().clone());
tmp.close();
fail!("fail to unwind past `tmp`");
};
task::try(f);
let path = rx.recv();
assert!(!path.exists());

let tmp = TempDir::new("test_rm_tempdir").unwrap();
let path = tmp.path().clone();
let f: proc():Send = proc() {
let tmp = tmp;
tmp.close();
fail!("fail to unwind past `tmp`");
};
task::try(f);
assert!(!path.exists());

let path;
{
let f = proc() {
TempDir::new("test_rm_tempdir").unwrap()
};
let tmp = task::try(f).ok().expect("test_rm_tmdir");
path = tmp.path().clone();
assert!(path.exists());
tmp.close();
}
assert!(!path.exists());

let path;
{
let tmp = TempDir::new("test_rm_tempdir").unwrap();
path = tmp.unwrap();
}
assert!(path.exists());
fs::rmdir_recursive(&path);
assert!(!path.exists());
}

// Ideally these would be in std::os but then core would need
// to depend on std
fn recursive_mkdir_rel() {
Expand Down Expand Up @@ -130,6 +174,19 @@ pub fn test_rmdir_recursive_ok() {
assert!(!root.join("bar").join("blat").exists());
}

pub fn dont_double_fail() {
let r: Result<(), _> = task::try(proc() {
let tmpdir = TempDir::new("test").unwrap();
// Remove the temporary directory so that TempDir sees
// an error on drop
fs::rmdir(tmpdir.path());
// Trigger failure. If TempDir fails *again* due to the rmdir
// error then the process will abort.
fail!();
});
assert!(r.is_err());
}

fn in_tmpdir(f: ||) {
let tmpdir = TempDir::new("test").expect("can't make tmpdir");
assert!(os::change_dir(tmpdir.path()));
Expand All @@ -140,8 +197,10 @@ fn in_tmpdir(f: ||) {
pub fn main() {
in_tmpdir(test_tempdir);
in_tmpdir(test_rm_tempdir);
in_tmpdir(test_rm_tempdir_close);
in_tmpdir(recursive_mkdir_rel);
in_tmpdir(recursive_mkdir_dot);
in_tmpdir(recursive_mkdir_rel_2);
in_tmpdir(test_rmdir_recursive_ok);
in_tmpdir(dont_double_fail);
}