I am trying to implement "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? suggests renaming files is "atomic operation" (whatever "atomic" actually means). https://stackoverflow.com/a/20570968/65458 suggests writing tmp file and renaming is cross-platform and ensures final file either does not exist or can be processed by the other process.
So I tried to actually implement this approach. Below is the summary of my attempts. For the actual question -- jump to the bottom.
write methods
I tried various ways of writing and renaming file (content
and charset
are 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();
read methods
Now assume another thread (thread because I'm in tests, in real-life it could be another process) is executing 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 using "java.nio.file.Files
approach", everything is working fine.
If I run this code on Linux (out of scope of this question, I know), everything is working fine.
However, if i implement read with Guava or FileInputStream
, then with likelihood above 0.5% (0.005) the test fails with
java.io.FileNotFoundException: Process cannot access the file, because it is being used by another process
(Message translated by myself cause my windows is not English; Referring to "another process" is misleading, since it is normal for Windows to tell this even if this is the same process, a I verified with explicit blocking.)
Question
How to implement create-then-rename using Java on Windows so that final file appears atomically, i.e. either does not exist or can be read?
As I do have control over processes than will pick up the files, I cannot assume any particular reading method in use, or even that they are in Java. Therefore the solution should work with all read methods listed above.