Domanda

I have a simple java program that creates a series of temporary files stored in a local tmp directory. I have added a simple shutdown hook that walks through all files and deletes them, then deletes the tmp directory, before exiting the program. here is the code:

Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
    @Override
    public void run() {
        File tmpDir = new File("tmp/");
        for (File f : tmpDir.listFiles()) {
            f.delete();
        }
        tmpDir.delete();
    }
}));

My problem is that the thread that creates these files may not have terminated upon launch of the shutdown hook, and therefore, there may be a file created after listFiles() is called. this causes the tmp dir not to get deleted. I have come up with 2 hacks around this:

Hack # 1:

Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
    @Override
    public void run() {
        File tmpDir = new File("tmp/");
        while (!tmp.delete()){
                for (File f : tmpDir.listFiles()) {
                f.delete();
            }
        }
    }
}));

Hack # 2:

Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
    @Override
    public void run() {
        try{
            Thread.sleep(1000);
        } catch(InterruptedException e){
            e.printStackTrace();
        }
        File tmpDir = new File("tmp/");
        for (File f : tmpDir.listFiles()) {
            f.delete();
        }
            tmpDir.delete();
        }
}));

Neither is a particularly good solution. What would be ideal is to have the shutdown hook wait until all threads have terminated before continuing. Does anyone know if this can be done?

È stato utile?

Soluzione

Just keep track of all your running threads and then.join() them before shutting down the program.

This is an answer to the question title as the ewok has said he can't use .deleteOnExit()

Altri suggerimenti

What Tyler said, but with a little more detail:

  • Keep references to the threads where the shutdown hook can access them.
  • Have the shutdown hook call interrupt on the threads.
  • Review the code of the threads to make sure they actually respond to interruption (instead of eating the InterruptedException and blundering on, which is typical of a lot of code). An interrupt should prompt the thread to stop looping or blocking, wrap up unfinished business, and terminate.
  • For each thread where you don't want to proceed until it finishes, check whether the thread is alive and if so call join on it, setting a timeout in case it doesn't finish in a reasonable time, in which case you can decide whether to delete the file or not.

UPDATE: Tyler Heiks accurately pointed out that deleteOnExit() isn't a valid solution since the OP tried it and it did not work. I am providing an alternate solution. It is again indirect, but mainly because the original design using threads and a ShutdownHook is fatally flawed.

Use finally blocks to delete the temp files.

Relying on ShutdownHooks for resource management is a very bad idea and makes the code very difficult to compose or reuse in a larger system. It's an even worse idea to hand resources from thread to thread. Resources like files and streams are among the most dangerous things to share between threads. There is likely very little to gain from this and it would make far more sense for each thread to independently obtain temp files using the library's createTempFile methods and manage their use and deletion using try/finally.

The convention for dealing with the temporary files on the system is to treat them as block boxes where:

  1. location on disk is opaque (irrelevant to and not used directly by the program)
  2. filename is irrelevant
  3. filename is guaranteed to be mutually exclusive

The third above is very difficult to achieve if you hand-roll code to create and name temp files yourself. It is likely to be brittle and fail at the worst times (3AM pager anyone?).

The algorithm you present could delete files created by other processes that coincidentally share the same parent directory. That is unlikely to be a good thing for the stability of those other programs.

Here's the high-level process:

  1. Get Path with Files.createTempFile() (or with legacy pre-Java 7 code File with File.createTempFile())
  2. Use temp file however desired
  3. Delete file

This is similar to InputStream or other resources that need to be manually managed.

That general pattern for explicit resource management (when AutoCloseable and try-with-resources aren't available) is as follows.

Resource r = allocateResource();
try {
   useResource(r);
} finally {
   releaseResource(r);
}

In the case of Path it looks like this:

Path tempDir = Paths.get("tmp/);
try {
    Path p = Files.createTempFile(tempDir, "example", ".tmp");
    try {
       useTempFile(f);
    } finally {
       Files.delete(f);
    }
} finally {
    Files.delete(tempDir);
}

On pre-Java 7 legacy, the use with File looks like this:

File tempDir = new File("tmp/");
try {
    File f = File.createTempFile(tempDir, "example", ".tmp");
    try {
       useTempFile(f);
    } finally {
       if (!f.delete()) {
          handleFailureToDeleteTempFile(f);
       }
    }
} finally {
    if (!tempDir.delete()) {
        handleFailureToDeleteTempDir(tempDir);
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top