5

To create a new, unique filename, I use the following code:

File file = new File(name);
synchronized (sync) {
    int cnt = 0;
    while (file.exists()) {
        file = new File(name + " (" + (cnt++) + ")");
    }
    file.createNewFile();
}

Next, I use the file and delete it. When I do this in a multithreaded situation, I sometimes get exceptions on the file.createNewFile():

java.io.IOException: Access is denied
    at java.io.WinNTFileSystem.createFileExclusively(Native Method)
    at java.io.File.createNewFile(File.java:1012)

The following code reproduces the problem (most of the times):

final int runs = 1000;
final int threads = 5;
final String name = "c:\\temp\\files\\file";
final byte[] bytes = getSomeBytes();
final Object sync = new Object();

ExecutorService exec = Executors.newFixedThreadPool(threads);
for (int thread = 0; thread < threads; thread++) {
    final String id = "Runnable " + thread;
    exec.execute(new Runnable() {
        public void run() {
            for (int i = 0; i < runs; i++) {
                try {
                    File file = new File(name);
                    synchronized (sync) {
                        int cnt = 0;
                        while (file.exists()) {
                            file = new File(name + " (" + (cnt++) + ")");
                        }
                        file.createNewFile();
                    }

                    Files.write(file.toPath(), bytes);
                    file.delete();
                } catch (Exception ex) {
                    System.err.println(id + ": exception after " + i
                            + " runs: " + ex.getMessage());
                    ex.printStackTrace();
                    return;
                }
            }
            System.out.println(id + " finished fine");
        }
    });
}
exec.shutdown();
while (!exec.awaitTermination(1, TimeUnit.SECONDS));

The method getSomeBytes() just generates an amount of bytes, the actual content is not important:

byte[] getSomeBytes() throws UnsupportedEncodingException,
        IOException {
    byte[] alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWYZ1234567890\r\n"
            .getBytes("UTF-8");
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
        for (int i = 0; i < 100000; i++) {
            baos.write(alphabet);
        }
        baos.flush();
        return baos.toByteArray();
    }
}

When I execute this code, it sometimes goes well but most of the times, it generates some exceptions like the output below for instance:

Runnable 1: exception after 235 runs: Access is denied
java.io.IOException: Access is denied
    at java.io.WinNTFileSystem.createFileExclusively(Native Method)
    at java.io.File.createNewFile(File.java:1012)
    at test.CreateFilesTest$1.run(CreateFilesTest.java:36)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Runnable 4: exception after 316 runs: Access is denied
java.io.IOException: Access is denied
    at java.io.WinNTFileSystem.createFileExclusively(Native Method)
    at java.io.File.createNewFile(File.java:1012)
    at test.CreateFilesTest$1.run(CreateFilesTest.java:36)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Runnable 2: exception after 327 runs: Access is denied
java.io.IOException: Access is denied
    at java.io.WinNTFileSystem.createFileExclusively(Native Method)
    at java.io.File.createNewFile(File.java:1012)
    at test.CreateFilesTest$1.run(CreateFilesTest.java:36)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Runnable 3 finished fine
Runnable 0 finished fine

Any ideas? I have tested on a Windows 8 machine with java 1.7.0_45 and 1.8.0_31, both same results.

Not sure whether the problem is the same as in this question, but it can be. Using multiple threads in the same process seems to be part of the problem to my opinion but I can't be sure about that, it's faster reproduced however.

Community
  • 1
  • 1
Steven
  • 343
  • 2
  • 9
  • 2
    `File.createTempFile()` may be a cleaner approach here, regardless of whether the file is actually intended to be temporary. – Sneftel Sep 15 '15 at 10:10
  • @Sneftel: I agree, however, the name of the file does matter so I can't use File.createTempFile here – Steven Sep 15 '15 at 11:47

2 Answers2

5

Seems that on Windows platform createNewFile may randomly fail if the file with the same name was just deleted even on single-threaded application. See this question for details. To fix your issue, you may try to ignore IOException from the createNewFile and continue. Something like this:

synchronized (sync) {
    int cnt = 0;
    while (true) {
        try {
            if(file.createNewFile())
                break;
        } catch (IOException e) {
            // continue;
        }
        file = new File(name + " (" + (cnt++) + ")");
    }
}

Note that you don't need to check file.exists() call as createNewFile() conveniently returns whether it created the file successfully.

Note that if you control all the temporary files you create and don't care about exact file name, usually there's no need to lock. You may just use global AtomicLong to get the next file name or append a thread ID to the file name.

Community
  • 1
  • 1
Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
  • Since the problem is probably caused by an external factor, I guess I'm gonna try/catch myself out of the problem in this case – Steven Sep 15 '15 at 12:08
0

Your loop isn't fail-safe. There's a timing-window problem. It should be more like this:

while (!file.createNewFile()) {
        file = new File(name + " (" + (cnt++) + ")");
    }
user207421
  • 305,947
  • 44
  • 307
  • 483
  • 1
    Could you please be a little more verbose on what is "timing-window" in this synchronized section? Also your fixed code fails just in the same way as OPs code. – Tagir Valeev Sep 15 '15 at 10:30
  • @TagirValeev There is a timing window between `exists()` and `createNewFile()` during which the file can be created by another thread. There is also a failure to check the result of `createNewFile()`. If the two-line loop I have posted here fails, there must be a platform problem with this method not working as advertised. There is in fact no difference between this loop and the code in your answer othe than the catch block. How do you explain that? – user207421 Sep 15 '15 at 10:39
  • 1
    Please note that all the threads are synchronized on the same monitor, thus another thread cannot do this. – Tagir Valeev Sep 15 '15 at 10:40
  • @TagirValeev So another *process* can create it. There's still a timing window problem. – user207421 Sep 15 '15 at 10:44