I am trying to do "write temporary file and rename" using Java on Windows correctly.
How to atomically rename a file in Java, even if the dest file already exists? assumes that renaming the files is an “atomic operation” (whatever “atomic” it actually means), https://stackoverflow.com/a/168161/ offers to write a tmp file, and the renaming is cross-platform and ensures that the final the file either does not exist or can be processed by another process.
So, I tried to implement this approach. The following is a summary of my attempts. For the actual question - jump to the bottom.
recording methods
I tried various ways to write and rename the file ( content and charset - String and charset respectively):
Using java.nio.file.Files :
Files.copy(new ByteArrayInputStream(content.getBytes(charset)), tmpFile); Files.move(tmpFile, finalFile, StandardCopyOption.ATOMIC_MOVE);
Using Guava (14) and java.io.File :
com.google.common.io.Files.write(content, tmpFile, charset); tmpFile.renameTo(finalFile);
Or even more obscure approaches:
try (OutputStream os = new FileOutputStream(tmpFile); Writer writer = new OutputStreamWriter(os, charset)) { writer.write(content); } Runtime.getRuntime().exec( new String[] { "cmd.exe", "/C", "move " + tmpFile + " " + finalFile }).waitFor();
reading methods
Now suppose that another thread (the thread, because I'm in the tests, in real life, it may be a different process) runs one of the following versions of code:
With common function:
void waitUntilExists() throws InterruptedException { while (!java.nio.file.Files.exists(finalFile)) { NANOSECONDS.sleep(1); } }
Using java.nio.file.Files :
waitUntilExists(); return new String(Files.readAllBytes(finalFile), charset);
Using Guava (14):
waitUntilExists(); return new String(com.google.common.io.Files.toByteArray(finalFile.toFile()), charset);
Or even more obscure approaches:
waitUntilExists(); StringBuilder sb = new StringBuilder(); try (InputStream is = new FileInputStream(finalFile.toFile())) { byte[] buf = new byte[8192]; int n; while ((n = is.read(buf)) > 0) { sb.append(new String(buf, 0, n, charset)); } } return sb.toString();
results
If I read using the java.nio.file.Files approach, everything works fine.
If I run this code on Linux (outside the scope of this question, I know), everything works fine.
However, if I implement reading using Guava or FileInputStream , then with a probability above 0.5% (0.005) the test fails with
java.io.FileNotFoundException: the process cannot access the file because it is being used by another process
(The message translated by me because my windows are not English; the link to the “other process” is misleading, because for Windows this is normal, even if it is the same process, I checked with an explicit lock.)
Question
How to implement create-then-rename using Java on Windows so that the final file is displayed atomically, i.e. does not exist or cannot be read?
Since I have control over the processes than selecting files, I cannot accept any specific reading method, even if it is in Java. Therefore, the solution should work with all the reading methods listed above.