Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-6437591

File.deleteOnExit() deletes files in the opposite order from Java 5.0

XMLWordPrintable

    • b91
    • x86
    • linux
    • Verified

      FULL PRODUCT VERSION :
      java version "1.6.0-rc"
      Java(TM) SE Runtime Environment (build 1.6.0-rc-b87)
      Java HotSpot(TM) Client VM (build 1.6.0-rc-b87, mixed mode, sharing)


      ADDITIONAL OS VERSION INFORMATION :
      I am using Ubuntu Linux 5.10 running kernel Linux DualCore 2.6.12-10-686-smp
      I suspect that this bug applies to all Unixes and perhaps Windows as well.

      A DESCRIPTION OF THE PROBLEM :
      In the java.io.File API, the semantics of the method

      public void deleteOnExit()

      are rather sketchy. For example, if "this" is a non-empty directory, what happens? Similarly, in what order are file deletion requests processed when the JVM exits.

      Since the API leaves the details of the contract for deleteOnExit() unspecfied, we have developed our file management utilities based on an inferred contract for this method as it behaves in Java 1.3, Java 1.4, and Java 5.0 across all major platforms (Linux, Windows, Mac OS X). According to this inferred contract, deleteOnExit will only delete a directory if it is empty (which is consistent with OS directory removal commands (rmdir on Unixes/Mac OS X, del on Windows). As a result, the order in which deleteOnExit requests are processed is crucial. All of the files within a directory must be deleted on exit before the directory itself is. In Java 1.3, 1.4 and 5.0, the requests are processed in reverse order. This ordering is convenient because it means that a temporary file tree can be constructed using File.createTempFile and a user provided createTempDirectory method (the absence of such a method in the java.io.File API is annoying) which calls deleteOnExit on the File object for the constructed directory. This protocol also affects the coding of our File utility method called deleteDirectoryOnExit(dir) that deletes the entire file subtree rooted at dir on exit.

      In Java 6.0, one of the unit tests for our FileOps utility class fails. Neither createTempDirectory or deleteDirectoryOnExit works correctly (as determined by the behavior of Java 1.3, 1.4, and 5.0 versions of the JVM).

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      The FileOps class given in the source code area of the form below is excepted from our file utilities class; it includes the two methods that work in Java 1.3, 1.4, and 5.0 but fail in Java 6.0.

      1. Set up a shell with java 5.0/6.0 on the PATH and junit.jar (version 3.8.1) and "." on the CLASSPATH. In an empty working directory ("."), place the three classes given below in separate files (with the specified names as dictated by Java file naming conventions), compile them using the command:

      java *.java

      They should compile without any errors.

      2. Execute the command.

      java junit.textui.TestRunner FileOpsTest

      The unit test FileOpsTest spawns another JVM in order to test that the file tree rooted at dir is deleted when the spawned JVM exits.


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      In Java 5.0, the unit test passes. Moreover, the temporary directory bound to tempDir in the test class is deleted when the the JVM running the unit test exits.

      In Java 6.0, I expected the same results, but the unit test fails because dir1 is not deleted. In addition, the temporary directory tempDir is not deleted on termination of the JVM running the unit test.
      ACTUAL -
      In Java 6.0:

      >java junit.textui.TestRunner FileOpsTest
      .Temporary directory is /tmp/DrJavaTestTempDir62367/dir1
      F
      Time: 0.107
      There was 1 failure:
      1) testDeleteDirectoryOnExit(FileOpsTest)junit.framework.AssertionFailedError: dir1 should be deleted
              at FileOpsTest.testDeleteDirectoryOnExit(FileOpsTest.java:39)
              at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
              at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
              at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

      FAILURES!!!
      Tests run: 1, Failures: 1, Errors: 0


      In Java 5.0:
      >java junit.textui.TestRunner FileOpsTest
      .Temporary directory is /tmp/DrJavaTestTempDir29897/dir1

      Time: 0.126

      OK (1 test)

      Note: I have no idea why there is a "." preceding "Temporary directory ..." in the one line printed to System.out. It does not appear if you use junit.swingui.TestRunner instead of
      unit.textui.TestRunner.

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      No error messages other than the JUnit test failure are generated.


      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      -----FileOps.java ------
      import java.io.File;
      import java.io.IOException;

      public abstract class FileOps {
        
        /** Create a new temporary directory. The directory will be deleted on exit, if it only contains temp files and temp
         * directories created after it. The parameters have the same meaning as in the method java.io.File.createTempFile.
         * This method utterly fails in Java 6.0 if any temporary files are created within the created directory.
         */
        public static File createTempDirectory(final String name, final File parent) throws IOException {
          File file = File.createTempFile(name, "", parent);
          file.delete();
          File dir = file; // reuse generated temporary file name
          boolean result = dir.mkdir();
          if (! result) throw new IOException("Could not create temporary directory " + dir);
          dir.deleteOnExit();
          return dir;
        }

        /** Instructs Java to recursively delete the given directory (dir) and its contents when the JVM exits.
         * @param dir File object representing directory to delete. If this file argument is not a directory, it will still
         * be deleted. <p>
         * The method works in Java 1.3, Java 1.4 and Java 5.0; it does not work in Java 6.0
         */
        public static void deleteDirectoryOnExit(final File dir) {
          // Delete this on exit. Delete on exit requests are processed in REVERSE order
          dir.deleteOnExit();
          
          // If it's a directory, visit its children. This recursive walk has to be done AFTER calling deleteOnExit
          // on the directory itself because Java deletes the files to be deleted on exit in reverse order.
          if (dir.isDirectory()) {
            File[] childFiles = dir.listFiles();
            if (childFiles != null) { // listFiles may return null if there's an IO error
              for (File f: childFiles) { deleteDirectoryOnExit(f); }
            }
          }
        }
      }
      ---End of FileOps.java------------

      The following two files define a JUnit (version 3.8.1) test that passes in Java 5.0 but fails in Java 6.0

      ----FileOpsTest----------
      import java.io.File;
      import java.io.IOException;
      import java.util.Arrays;
      import junit.framework.TestCase;

      /** This test class assumes that:
       * (i) this file, FileOps.java and DeleteOnExit.java all reside in the working directory;
       * (ii) the version 5.0 or 6.0 "java" command is on the shell's PATH; and
       * (iii) "." and junit.jar (version 3.8.1) are on the shell's CLASSPATH.
       *
       * Under the preceding assumptions, this test can be run from the command line if using the command:
       * java junit.textui.TestRunner FileOpsTest
       *
       * Under Java 5.0, it succeeds and the temporary directory (dir1) printed to the console is deleted.
       * Under Java 6.0, the test fails and the temporary directory printed to the console is not deleted.
       * If the definition of FileOps.deleteDirectoryOnExit is revised as described in FileOps.java, then
       * the test will pass under Java 6.0 but the temporary directory (dir1) will not be deleted.
       */
      public class FileOpsTest extends TestCase {

        /** Tests that FileOps.deleteDirectoryOnExit(dir) deletes the tree rooted at dir when the executing JVM exits. */
        public void testDeleteDirectoryOnExit() throws IOException, InterruptedException {
        
          File tempDir = FileOps.createTempDirectory("DrJavaTestTempDir", null);
          File dir1 = new File(tempDir, "dir1");
          dir1.mkdir();
          System.out.println("Temporary directory is " + dir1);
          File file1 = new File(dir1, "file1");
          file1.createNewFile(); // Should always succeed because dir1 was just created
          File dir2 = new File(dir1, "dir2");
          dir2.mkdir();
          File file2 = new File(dir2, "file2");
          file2.createNewFile();
          
          Process process = Runtime.getRuntime().exec("java DeleteOnExit " + dir1.getCanonicalPath());
          int status = process.waitFor();
          assertEquals("DeleteOnExit test should halt normally", 0, status);

          assertTrue("dir1 should be deleted", ! dir1.exists());
          assertTrue("file1 should be deleted", ! file1.exists());
          assertTrue("dir2 should be deleted", ! dir2.exists());
          assertTrue("file2 should be deleted", ! file2.exists());
          
          /* In Java 5.0 this test passes AND tempDir is deleted when this JVM exits. */
        }
      }
      ---End of FileOpsTest.java----------
      ---DeleteOnExit.java-------------------
      import java.io.File;

      class DeleteOnExit {
        public static void main(String[] args) {
          File dir = new File(args[0]);
          FileOps.deleteDirectoryOnExit(dir);
        }
      }
      ---End of DeleteOnExit.java----------
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      The two methods in our file utility are broken. Our open source software product (DrJava available at drjava.org) does not pass our unit tests under Java 6.0. Since the two broken methods are used primarily in unit testing code (to delete file subtrees created as part of unit tests), DrJava itself still runs on Java 6.0 without any apparent problems but our unit test suite breaks and leaves lot of "turd" files in /tmp.

      For this reason, we are still doing most of our development in Java 5.0 rather than Java 6.0.

      Release Regression From : 5.0u7
      The above release value was the last known release where this
      bug was not reproducible. Since then there has been a regression.

            chegar Chris Hegarty
            gmanwanisunw Girish Manwani (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: