(EDIT: The question - see also "How you can help" - would be if others can reproduce the bug, if it is known (Link.), what workarounds are recommended.)
Goal: Create a tiny temp file, then delete file with final filename, then rename temp to final. (Standard safesave-approach. You do use it, right.)
Problem: Fails after many test loop iterations. Files.move() throws FileAlreadyExistsException, tests instead using file.renameTo() return false sometimes. The file is indeed not renamed then. My tests make me feel that the failure frequency is independent of approach. I explicitly created a test class - see below. The problem is definitely not my code.
In very rare cases, Files.move() throws a FileSystemException saying "The process cannot access the file because it is being used by another process.", as if the file had not yet been closed after writing, but Files.write is used, so that "can't" be the case. While working with 3rd party libraries, I have sometimes had problems akin to this and used System.gc() to reliably work around them, that's why I added this feature to the test class, too, but this Exception occurred only once during all tests. That was without System.gc, so I don't know if gc would help.
为什么它是Java错误: FileAlreadyExistsException不适用,因为destination.exists()在调用之前以及在抛出异常之后立即返回false.即使原因是Java之外,这是一个Java漏洞声称该文件不存在时,Java还声称,它并没有存在.
当它没有发生时:
如果重命名目标在每次迭代中都不同.
如果文件只是无休止地来回重命名.
如果问题发生,等待1毫秒再试一次.但在一个罕见的情况下,这个重试循环的计数器达到了24.
要重现: [在一个循环中创建一个文件X,将其重命名为Y,将其删除.],永远不要更改Y.
Conjecture: Java's renaming gets confused by caching (at least on this platform), and Java's file.exists() probably uses a different approach.
How you can help: Please try to reproduce the bug and report in, on the below described and other platforms. Also, please inform the Java dev community. I already found https://bugs.openjdk.java.net/browse/JDK-8150700 but it's rather bare-bones. I am also not informed about the process, don't have an account, and have invested a ton of time already myself, have to get back to coding. Please help so we get this bug squashed.
Reproduced so far:
Test environment: Standard Oracle Java release download JDK8 144. Java -version:
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
Windows 10 Pro, Version 1803 (OS Build 17134.228)
Intel(R) Core(TM) i3-2120 CPU @ 3.30 GHz
Test class result with no problems (because target name different each time):
Doing test withOUT forced Garbage Collection.
Completed create-rename-delete attempt 0x0
Completed create-rename-delete attempt 0x100
...
Completed create-rename-delete attempt 0xfe00
Completed create-rename-delete attempt 0xff00
No fatal problems.
Run Code Online (Sandbox Code Playgroud)
Test class result with the described problems, reliably with 0x10000 tests:
Doing test withOUT forced Garbage Collection.
Completed create-rename-delete attempt 0x0
Completed create-rename-delete attempt 0x100
...
Completed create-rename-delete attempt 0x3900
Files.move() attempt 1 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Completed create-rename-delete attempt 0x3a00
...
Completed create-rename-delete attempt 0x4700
Files.move() attempt 1 of 4096 failed: java.nio.file.FileSystemException: renameBugDemoFile.dat -> renameBugDemoFile.ren: The process cannot access the file because it is being used by another process.
Completed create-rename-delete attempt 0x4800
...
Completed create-rename-delete attempt 0x7700
Files.move() attempt 1 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 2 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Completed create-rename-delete attempt 0x7800
...
Completed create-rename-delete attempt 0x9900
Files.move() attempt 1 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 2 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Completed create-rename-delete attempt 0x9a00
Files.move() attempt 1 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 2 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Completed create-rename-delete attempt 0x9b00
Files.move() attempt 1 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 2 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Completed create-rename-delete attempt 0x9c00
...
Completed create-rename-delete attempt 0xa300
Files.move() attempt 1 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 2 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 3 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 4 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 5 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 6 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 7 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 8 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 9 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 10 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 11 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 12 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 13 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 14 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 15 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 16 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 17 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 18 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 19 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 20 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Files.move() attempt 21 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Completed create-rename-delete attempt 0xa400
Files.move() attempt 1 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Completed create-rename-delete attempt 0xa500
Completed create-rename-delete attempt 0xa600
Completed create-rename-delete attempt 0xa700
Completed create-rename-delete attempt 0xa800
Files.move() attempt 1 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Completed create-rename-delete attempt 0xa900
Completed create-rename-delete attempt 0xaa00
Completed create-rename-delete attempt 0xab00
Completed create-rename-delete attempt 0xac00
Files.move() attempt 1 of 4096 failed: java.nio.file.FileAlreadyExistsException: renameBugDemoFile.dat -> renameBugDemoFile.ren
Completed create-rename-delete attempt 0xad00
...
Completed create-rename-delete attempt 0xfe00
Completed create-rename-delete attempt 0xff00
No fatal problems.
Run Code Online (Sandbox Code Playgroud)
The likelihood that you test this on your computer is higher if you don't have to check lots of code, so here's a minimal version:
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
final public class FileRenameProblemDemoMinimal {
public static void main(final String[] args) {
final File demofile = new File("renameBugDemoFile.dat");
final File demofileAfterRenaming = new File("renameBugDemoFile.ren");
demofile.delete();
demofileAfterRenaming.delete();
for (int i = 0; i < 0x10000; i++) {
createDemoFile(demofile);
// System.gc();
if (!renameUsingFilesMoveAndBruteForce(demofile, demofileAfterRenaming)) {
System.err.println("q.e.d.: FAILURE! (Attempt was 0x" + Integer.toHexString(i) + ")");
System.exit(-1);
} else {
if (!demofileAfterRenaming.delete()) {
throw new Error();
} else {
if (demofileAfterRenaming.exists()) {
throw new Error();
}
}
if (i % 0x100 == 0) {
System.err.println("Completed create-rename-delete attempt 0x" + Integer.toHexString(i));
}
}
}
System.err.println("No fatal problems.");
System.exit(0);
}
private static boolean renameUsingFilesMoveAndBruteForce(final File source, final File destination) {
if (destination.exists()) {
throw new Error("destination already exists: " + destination);
}
final int amountOfRetriesWith1MsPause = 0x1000;
int i = 0;
while (i <= amountOfRetriesWith1MsPause) {
i++;
try {
Files.move(source.toPath(), destination.toPath()); // , StandardCopyOption.REPLACE_EXISTING); // WOULD NOT MAKE A DIFFERENCE!
return true;
} catch (IOException e) {
if (destination.exists()) {
throw new Error();
}
System.err.println("Files.move() attempt " + i + " of " + amountOfRetriesWith1MsPause + " failed: " + e.toString());
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return false;
}
private static void createDemoFile(final File file) {
final byte[] bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
try {
Files.write(file.toPath(), bytes);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Run Code Online (Sandbox Code Playgroud)
The test class has code for all cases. You have to choose via (un)commenting. As is, the code will demonstrate the problem happening but without failing, because it just retries the renaming. System.gc use (which slows the test down) is currently off.
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
final public class FileRenameProblemDemo {
final private static File DEMOFILE = new File("renameBugDemoFile.dat");
final private static File DEMOFILE_AFTER_RENAMING = new File("renameBugDemoFile.ren");
final private static int AMOUNT_OF_MAIN_ITERATIONS = 0x10000;
final private static int AMOUNT_OF_RETRIES_WITH_1MS_PAUSE = 0x1000;
public static void main(final String[] args) {
deleteFileOptionallyButWithExtremeParanoia(DEMOFILE);
deleteFileOptionallyButWithExtremeParanoia(DEMOFILE_AFTER_RENAMING);
// runTestOnlyWithRenames(); // <-- No problems at all.
runTestWithCreateAndRenames(false); // <-- Reliable problems. Sometimes it only takes a few iterations, often a few thousand.
System.err.println("No fatal problems.");
System.exit(0);
}
private static File createDifferentFileName(final int i) {
return DEMOFILE_AFTER_RENAMING;
// return new File(String.valueOf(i) + "_" + DEMOFILE_AFTER_RENAMING.getName());
}
private static void runTestOnlyWithRenames() {
createDemoFile(DEMOFILE);
for (int i = 0; i < AMOUNT_OF_MAIN_ITERATIONS; i++) {
// if (!renameUsingFilesMove(DEMOFILE, DEMOFILE_AFTER_RENAMING)) {
if (!renameUsingRename(DEMOFILE, DEMOFILE_AFTER_RENAMING)) {
// if (!renameUsingFilesMoveAndBruteForce(DEMOFILE, DEMOFILE_AFTER_RENAMING)) {
// if (!renameUsingRenameAndBruteForce(DEMOFILE, DEMOFILE_AFTER_RENAMING)) {
System.err.println("q.e.d.: FAILURE! (Attempt was 0x" + Integer.toHexString(i) + ")");
System.exit(-1);
} else {
// if (!renameUsingFilesMove(DEMOFILE_AFTER_RENAMING, DEMOFILE)) {
// if (!renameUsingRename(DEMOFILE_AFTER_RENAMING, DEMOFILE)) {
if (!renameUsingFilesMoveAndBruteForce(DEMOFILE_AFTER_RENAMING, DEMOFILE)) {
// if (!renameUsingRenameAndBruteForce(DEMOFILE_AFTER_RENAMING, DEMOFILE)) {
throw new Error(); // We're confident that this approach never fails and want to keep the code short.
}
if (i % 0x100 == 0) {
// System.err.println("Completed back-and-forth renaming attempt " + Integer.toHexString(i));
System.err.println("Completed rename-back&forth attempt 0x" + Integer.toHexString(i));
}
}
}
}
private static void runTestWithCreateAndRenames(final boolean forceGarbageCollection) {
if (forceGarbageCollection) {
System.err.println("Doing test WITH forced Garbage Collection.\n");
} else {
System.err.println("Doing test withOUT forced Garbage Collection.\n");
}
for (int i = 0; i < AMOUNT_OF_MAIN_ITERATIONS; i++) {
createDemoFile(DEMOFILE);
if (forceGarbageCollection) {
System.gc(); // Because in a different project, I had experienced consistent move-problems (Consistent = different than here.) which were consistently solved by running GC after closing after read access.
}
final File demoFileAfterRenaming = createDifferentFileName(i);
// if (!renameUsingFilesMove(DEMOFILE, demoFileAfterRenaming)) {
// if (!renameUsingRename(DEMOFILE, demoFileAfterRenaming)) {
if (!renameUsingFilesMoveAndBruteForce(DEMOFILE, demoFileAfterRenaming)) {
// if (!renameUsingRenameAndBruteForce(DEMOFILE, demoFileAfterRenaming)) {
System.err.println("q.e.d.: FAILURE! (Attempt was 0x" + Integer.toHexString(i) + ")");
System.exit(-1);
} else {
if (!demoFileAfterRenaming.delete()) {
throw new Error();
} else {
// Deletion supposedly successful.
if (demoFileAfterRenaming.exists()) {
throw new Error();
}
}
if (i % 0x100 == 0) {
System.err.println("Completed create-rename-delete attempt 0x" + Integer.toHexString(i));
}
}
}
}
private static boolean renameUsingFilesMove(final File source, final File destination) {
if (destination.exists()) {
throw new Error();
}
try {
Files.move(source.toPath(), destination.toPath());
// Files.move(source.toPath(), destination.toPath(), StandardCopyOption.REPLACE_EXISTING); // DOESN'T MAKE A DIFFERENCE!
} catch (IOException e) {
if (destination.exists()) {
throw new Error();
}
System.err.println("Files.move() attempt one of one failed: " + toStringIntlForThrowables(e));
return false;
}
return true;
}
private static boolean renameUsingRename(final File source, final File destination) {
if (destination.exists()) {
throw new Error();
}
if (source.renameTo(destination)) {
if (source.exists()) {
throw new Error();
}
if (!destination.exists()) {
throw new Error();
}
return true;
} else {
if (!source.exists()) {
throw new Error();
}
if (destination.exists()) {
throw new Error();
}
System.err.println("renameTo() attempt one of one failed.");
return false;
}
}
private static boolean renameUsingFilesMoveAndBruteForce(final File source, final File destination) {
if (destination.exists()) {
throw new Error("destination already exists: " + destination);
}
int i = 0;
while (i <= AMOUNT_OF_RETRIES_WITH_1MS_PAUSE) {
i++;
try {
Files.move(source.toPath(), destination.toPath());
return true;
// Files.move(source.toPath(), destination.toPath(), StandardCopyOption.REPLACE_EXISTING); // DOESN'T MAKE A DIFFERENCE!
} catch (IOException e) {
if (destination.exists()) {
throw new Error();
}
System.err.println("Files.move() attempt " + i + " of " + AMOUNT_OF_RETRIES_WITH_1MS_PAUSE + " failed: " + toStringIntlForThrowables(e));
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return false;
}
private static boolean renameUsingRenameAndBruteForce(final File source, final File destination) {
if (destination.exists()) {
throw new Error();
}
int i = 0;
while (i <= AMOUNT_OF_RETRIES_WITH_1MS_PAUSE) {
i++;
if (source.renameTo(destination)) {
if (source.exists()) {
throw new Error();
}
if (!destination.exists()) {
throw new Error();
}
return true;
} else {
if (!source.exists()) {
throw new Error();
}
if (destination.exists()) {
throw new Error();
}
System.err.println("renameTo() attempt " + i + " of " + AMOUNT_OF_RETRIES_WITH_1MS_PAUSE + " failed.");
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return false;
}
private static void deleteFileOptionallyButWithExtremeParanoia(final File file) {
if (file.exists()) {
if (file.isFile()) {
if (!file.delete()) {
throw new Error();
}
if (file.exists()) {
throw new Error();
}
} else {
throw new Error();
}
}
}
private static void createDemoFile(final File file) {
final byte[] bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
try {
Files.write(file.toPath(), bytes);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @param t the exception (Null would cause the return of null.)
* @return the same as exception.toString except with getMessage() instead of getLocalizedMessage(), because a log
* file with e.g. Danish error text (CUSTOMER SYSTEM SETTINGS.) doesn't help the developer.
*/
public static String toStringIntlForThrowables(final Throwable t) {
if (t == null) {
return null;
}
final String className = t.getClass().getName();
final String message = t.getMessage();
return (message != null) ? (className + ": " + message) : className;
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
190 次 |
| 最近记录: |