(РЕДАКТИРОВАТЬ: вопрос - см. Также «Как вы можете помочь») будет, если другие могут воспроизвести ошибку, если известно (ссылка.), Какие обходные пути рекомендуются.)
Цель: Создать крошечный временный файл, затем удалить файл с окончательным именем файла, затем переименовать временный файл в окончательный.(Стандартный подход safesave-подхода. Вы его используете, right .)
Проблема: Сбой после многих итераций цикла тестирования.Files.move()
throws FileAlreadyExistsException
, тесты вместо использования file.renameTo()
иногда возвращают false.Файл действительно не переименован тогда.Мои тесты дают мне понять, что частота отказов не зависит от подхода.Я явно создал тестовый класс - см. Ниже.Проблема определенно не в моем коде.
В в очень редких случаях Files.move () выдает FileSystemException
, говоря: «Процесс не может получить доступ к файлу, потому что он используется другимprocess. ", как будто файл еще не был закрыт после записи, но используется Files.write, так что" не может быть ".При работе со сторонними библиотеками у меня иногда возникали проблемы, схожие с этим, и я использовал System.gc()
, чтобы надежно их обойти, поэтому я также добавил эту функцию в тестовый класс, но это исключение произошло толькоодин раз за все тесты.Это было без System.gc, поэтому я не знаю, поможет ли gc.
Почему это ошибка Java: FileAlreadyExistsException
не применяется, так как destination.exists ()возвращает false как раз перед вызовом, а также сразу после того, как сгенерировано исключение.Даже если причина не в Java, это ошибка Java, которая утверждает, что файл действительно уже существует, когда Java также утверждает, что он не существует.
Когда это происходит не происходит:
, если цель переименования отличается на каждой итерации.
если файл просто бесконечно переименовывается туда и обратно.
если возникает проблема, подождите 1 мс и повторите попытку.Но в одном редком случае счетчик этого цикла повторения достиг 24.
Воспроизвести: [Создать файл X, переименовать его в Y, удалить его.] в цикле, никогда не меняя Y.
Гипотеза: Переименование Java запутывается из-за кэширования (по крайней мере, на этой платформе), а в файле file.exists () Java, вероятно, используется другой подход.
Как вы можете помочь: Пожалуйста, попробуйте воспроизвести ошибку и сообщить об этом на нижеописанных и других платформах.Также, пожалуйста, сообщите сообществу разработчиков Java.Я уже нашел https://bugs.openjdk.java.net/browse/JDK-8150700 но это довольно скудно.Я также не проинформирован о процессе, у меня нет учетной записи, и я уже потратил кучу времени, должен вернуться к кодированию.Пожалуйста, помогите, чтобы мы исправили эту ошибку.
Воспроизведено до сих пор:
- В комментариях Луис Г. говорит, что подтвердил проблему в Windows 10 Home (сборка 1803), используяИсполняемый файл минимального тестового класса, использующий версию javac "1.8.0_121", версию java "1.8.0_171".
Среда тестирования: Стандартная версия Oracle Java для скачивания 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, версия 1803 (сборка ОС 17134.228)
Intel (R) Core (TM) i3-2120 CPU@ 3,30 ГГц
Результат теста класса без проблем (потому что имя цели каждый раз отличается):
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.
Результат теста класса с описанными проблемами,надежно с тестами 0x10000:
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.
МИНИМАЛЬНЫЙ ИСПЫТАТЕЛЬНЫЙ КЛАСС
Вероятность того, что вы протестируете это на своем компьютере, выше, если вам не нужнопроверьте много кода, поэтому вот минимальная версия:
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();
}
}
}
FULL TEST CLASS
Тестовый класс имеет код для всех случаев.Вы должны выбрать (не) комментируя.Таким образом, код продемонстрирует возникновение проблемы, но без сбоев, поскольку он просто повторяет переименование.Использование System.gc (что замедляет тестирование) в настоящее время отключено.
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;
}
}