Подтвердить что ты не робот

Java.nio.file.Files.delete(путь пути) - случайный отказ рекурсивно удалить каталог с помощью SimpleFileVisitor

Попытка устранить случайный java.nio.file.DirectoryNotEmptyException в рекурсивном методе удаления, взятом из рекурсивно удалять каталоги в Java

Код (кредит для @TrevorRobinson):

static void removeRecursive(Path path) throws IOException {
    Files.walkFileTree(path, new SimpleFileVisitor<Path>() {

        final Logger logger = LoggerFactory.getLogger(this.getClass());
        @Override
        public FileVisitResult visitFile(Path file,
                BasicFileAttributes attrs) throws IOException {
            logger.warn("Deleting " + file.getFileName());
            Files.delete(file);
            logger.warn("DELETED " + file.getFileName());
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) {
            // try to delete the file anyway, even if its attributes could
            // not be read, since delete-only access is theoretically possible
            // I NEVER SEE THIS
            logger.warn("Delete file " + file + " failed", exc);
            try {
                Files.delete(file);
            } catch (IOException e) {
                logger.warn(
                    "Delete file " + file + " failed again", exc);
            }
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc)
                throws IOException {
            if (exc == null) {
                Files.delete(dir);
                return FileVisitResult.CONTINUE;
            }
            // directory iteration failed; propagate exception
            throw exc;
        }
    });
}

Вызов

try {
    removeRecursive(Paths.get(unzipDirPath));
} catch (IOException e) {
    String msg = "Failed to delete folder " + unzipDirPath;
    if (e instanceof java.nio.file.DirectoryNotEmptyException) {
        msg += ". Still contains : ";
        final File[] listFiles = Paths.get(unzipDirPath).toFile().listFiles();
        if (listFiles != null) for (File file : listFiles) {
            msg += file.getAbsolutePath() + "\n";
        }
    }
    log.error(msg, e);
}

Печать (один раз в 20/40 итерациях):

22:03:34.190 [http-bio-8080-exec-47] WARN  g.u.d.m.server.servlets.Controller$1 - Deleting batt
22:03:34.192 [http-bio-8080-exec-47] WARN  g.u.d.m.server.servlets.Controller$1 - DELETED batt
22:03:34.192 [http-bio-8080-exec-47] WARN  g.u.d.m.server.servlets.Controller$1 - Deleting wifi
22:03:34.193 [http-bio-8080-exec-47] WARN  g.u.d.m.server.servlets.Controller$1 - DELETED wifi
22:03:34.196 [http-bio-8080-exec-47] ERROR g.u.d.m.s.s.DataCollectionServlet - Failed to delete folder C:\yada\. Still contains : C:\yada\dir\wifi

java.nio.file.DirectoryNotEmptyException: C:\yada\dir
    at sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:265) ~[na:1.7.0_45]
    at sun.nio.fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:103) ~[na:1.7.0_45]
    at java.nio.file.Files.delete(Files.java:1077) ~[na:1.7.0_45]
    at gr.uoa.di.monitoring.server.servlets.Controller$1.postVisitDirectory(Controller.java:128) ~[Controller$1.class:na]
    at gr.uoa.di.monitoring.server.servlets.Controller$1.postVisitDirectory(Controller.java:1) ~[Controller$1.class:na]
    at java.nio.file.FileTreeWalker.walk(FileTreeWalker.java:224) ~[na:1.7.0_45]
    at java.nio.file.FileTreeWalker.walk(FileTreeWalker.java:199) ~[na:1.7.0_45]
    at java.nio.file.FileTreeWalker.walk(FileTreeWalker.java:69) ~[na:1.7.0_45]
    at java.nio.file.Files.walkFileTree(Files.java:2600) ~[na:1.7.0_45]
    at java.nio.file.Files.walkFileTree(Files.java:2633) ~[na:1.7.0_45]
    at gr.uoa.di.monitoring.server.servlets.Controller.removeRecursive(Controller.java:96) ~[Controller.class:na]
    at gr.uoa.di.monitoring.server.servlets.DataCollectionServlet.doPost(DataCollectionServlet.java:153) ~[DataCollectionServlet.class:na]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:641) [servlet-api.jar:na]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:722) [servlet-api.jar:na]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305) [catalina.jar:7.0.32]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.32]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222) [catalina.jar:7.0.32]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123) [catalina.jar:7.0.32]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472) [catalina.jar:7.0.32]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168) [catalina.jar:7.0.32]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99) [catalina.jar:7.0.32]
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:929) [catalina.jar:7.0.32]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) [catalina.jar:7.0.32]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407) [catalina.jar:7.0.32]
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1002) [tomcat-coyote.jar:7.0.32]
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:585) [tomcat-coyote.jar:7.0.32]
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310) [tomcat-coyote.jar:7.0.32]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_45]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_45]
    at java.lang.Thread.run(Thread.java:744) [na:1.7.0_45]

Обратите внимание, что wifi сообщается как удаленный - что еще более странно, что иногда я получаю:

Не удалось удалить папку C:\yada. Все еще содержит: C:\yada\dir

java.nio.file.DirectoryNotEmptyException: C:\yada\dir

Я склоняюсь к выводу, что иногда удаление происходит слишком долго - другими словами, проблема заключается в том, что java.nio.file.Files.delete(Path path) не блокирует (поэтому C:\yada\dir все еще содержит файлы, когда приходит время, которые иногда удаляются время, когда я ставлю его). Итак, как мне это сделать?

Также: есть java.nio.file.Files.delete(Path path), чтобы его выбрасывать? Состояние docs:

В некоторых операционных системах может быть невозможно удалить файл, когда он открыт и используется этой виртуальной машиной Java или другими программами.

Кажется, что в этом случае не требуется исключение. Требуется ли java.nio.file.Files.delete(Path path) бросить?

4b9b3361

Ответ 1

У меня была такая же проблема, и выяснилось, что проблема была вызвана незакрытым файлом каталогов в другом месте кода для того же каталога, из которого я удалял файлы. Объект потока, возвращаемый:

Files.list(Path)

должен быть закрыт, поэтому при использовании этого метода обязательно используйте конструкцию try-with-resources в своем коде.

Так что я не думаю, что удаление занимает слишком много времени, я попробовал подождать, прежде чем повторять попытку удаления каталога без везения. Скорее всего, ваша собственная программа заблокировала этот ресурс. Результатом является то, что вызов удаления на нем не завершен, хотя он возвращается успешно (похоже, что Windows в конечном итоге удалит файл после того, как ваша собственная программа освободит его), но тогда, конечно, содержащаяся директория не может быть удалена, поскольку это действительно еще не пустой.

Ответ 2

Я знаю, что это очень старый поток, но у меня была такая же проблема, и мне потребовалось некоторое время, чтобы исправить это. Я думаю, что это неправильное поведение вызвано проблемой синхронизации (похоже, что это происходит только в Windows), поэтому я помещаю паузу в метод postVisitDirectory. Это сработало, и вот что я, наконец, придумал:

Метод, который выполняет удаление без метаданных DirectoryNotEmptyException:

private boolean isDeleted(Path dir) throws IOException {
    boolean deleted = false;
    try {
        Files.delete(dir);
        deleted = true;
    } catch (DirectoryNotEmptyException e) {
    // happens sometimes if Windows is too slow to remove children of a directory
    deleted = false;
    }
    return deleted;
}

и его использование в цикле:

public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
    if (e == null) {
        int maxTries = 5;
        int count = 0;
        boolean deleted = false;
        do {
            if ((deleted = this.isDeleted(dir))) {
                break;
            } else {
                // wait a bit and try again
                count++;
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e1) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        } while (count < maxTries);
        // gone?
        if (!deleted) {
            throw new DirectoryNotEmptyException(dir.toString());
        }
        // go ahead
        return FileVisitResult.CONTINUE;
    }
    throw e;
}

Andy

Ответ 3

Вы можете временно сохранить имя файла, который вы сейчас итерируете в переменной, и выполнить try-catch на DirectoryNotEmptyException. Когда возникает это исключение, поймайте его и выбросите свое собственное исключение, которое указывает файл.

try {
 Files.delete(file);
} catch (DirectoryNotEmptyException  e) {
 throw new MySpecificException(file.getFileName());
}

class MySpecificException extends Exception {
 public MySpecificException() { }

 public MySpecificException(string filename) {
  super(filename);
 }  
}

Получение имени файла можно сделать с помощью e.getMessage();

Я предполагаю, что Files.delete() продолжает удаление файлов в каталоге, когда он сталкивается с файлом, который не может быть удален. Если это так, мой метод все еще работает: просто верните список всех файлов в каталоге вместо имени файла в каталоге. Он должен содержать только файлы, которые нельзя удалить, и у вас все еще есть ваше решение.

Ответ 4

Расширение моего комментария выше.

Когда у Java возникает проблема, она бросает Exception. Исключения, как и все другие типы, могут быть унаследованы. API укажет, какие проверенные исключения выполняются каждым методом. Методы в java.io и java.nio обычно будут бросать IOException или один из них. Если вы хотите создать метод, который скажет вам, почему операция файла, в этом случае удаление, не удалось, вы можете сделать что-то вроде этого:

@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
       //You don't need to try to delete the file again since this method was called
       //because the deletion failed. Instead...
       if (exc instanceof NoSuchFileException) {
            System.out.println("Could not find the file: " + file);
       } else if (exc instanceof DirectoryNotEmptyException) {
            System.out.println("The directory [+ " file + "] was not empty.");
       } else {
            System.out.println("Could not delete file [" + file
                + "]. There was a problem communicating with the file system");
       }

       return FileVisitResult.CONTINUE;
}

Вы можете изменить фактический ответ программы так, как вам нужно, но это должно дать вам общую идею.