Почему StandardOpenOption.DELETE_ON_CLOSE не удаляет исходный файл FileChannel?

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

private void appendFile(Path destination, Path source) {

    try (FileChannel sourceChannel = FileChannel.open(source, StandardOpenOption.READ, StandardOpenOption.DELETE_ON_CLOSE);
         FileChannel destinationChannel = FileChannel.open(destination, StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
        destinationChannel.transferFrom(sourceChannel, destinationChannel.size(), sourceChannel.size());
    } catch (IOException ex) {
       // Do something with this exception
    }
}

Теперь запускаем на нем функциональный интеграционный тест и видим, что исходный файл не удаляется.

Может ли кто-нибудь помочь нам с этим?


person stijn van crombrugge    schedule 02.03.2020    source источник


Ответы (2)


Из документации по опции StandardOpenOption.DELETE_ON_CLOSE:

Когда присутствует этот параметр, реализация делает все возможное, чтобы удалить файл при закрытии с помощью соответствующего метода закрытия. Если метод close не вызывается, то предпринимаются все усилия, чтобы удалить файл, когда виртуальная машина Java завершает работу (либо обычно, как определено в спецификации языка Java, либо, где возможно, аварийно).

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

Так что это всего лишь все усилия, а не стопроцентная гарантия того, что он будет удален. Может быть, он все еще открыт каким-то другим писателем?

person user11153    schedule 02.03.2020
comment
Да действительно, я тоже читал ту документацию, она не гарантирует, что она закрыта. Тем не менее, я не понимаю, почему он не закрывается в этой простой ситуации. Нет, он не открыт другим писателем. - person stijn van crombrugge; 04.03.2020

У нас была аналогичная проблема примерно в 2015 году, после того как пользователь Win7/x64 регулярно обнаруживал оставшиеся файлы, которые не были удалены после завершения работы программы. После некоторых исследований и проб и ошибок мы обнаружили, что это происходит только с файлами, которые недавно были сопоставлены с памятью, и исправили это, избегая сопоставления памяти с файлами, которые мы хотели удалить в ближайшее время/позже.

FileChannelImpl.transferFromFileChannel память отображает источник для передачи. (Зависит от вашей JVM — я основываюсь на OpenJDK.) В то время как JVM очищает после копирования путем отмены сопоставления, тем самым делая недействительным созданное представление, ОС может отложить фактическую очистку до другого момента времени. Пока этого не произошло, у файла есть действующая (но недоступная) карта памяти, которая может помешать отсоединению.

Этот вопрос кажется связанным: Как правильно закрыть MappedByteBuffer?

Для справки: jdk11/sun.nio.ch.FileChannelImpl#transferFromFileChannel

private long transferFromFileChannel(FileChannelImpl src,
                                     long position, long count)
    throws IOException
{
    if (!src.readable)
        throw new NonReadableChannelException();
    synchronized (src.positionLock) {
        long pos = src.position();
        long max = Math.min(count, src.size() - pos);

        long remaining = max;
        long p = pos;
        while (remaining > 0L) {
            long size = Math.min(remaining, MAPPED_TRANSFER_SIZE);
            // ## Bug: Closing this channel will not terminate the write
            MappedByteBuffer bb = src.map(MapMode.READ_ONLY, p, size);
            try {
                long n = write(bb, position);
                assert n > 0;
                p += n;
                position += n;
                remaining -= n;
            } catch (IOException ioe) {
                // Only throw exception if no bytes have been written
                if (remaining == max)
                    throw ioe;
                break;
            } finally {
                unmap(bb);
            }
        }
        long nwritten = max - remaining;
        src.position(pos + nwritten);
        return nwritten;
    }
}
person JvR    schedule 28.04.2020