Files.write заканчивается с пустым файлом при выходе из JVM - PullRequest
0 голосов
/ 31 октября 2018

Пройдя длинный путь, чтобы изолировать мою проблему, удалось определить ее в следующих тестах. Все работает нормально, когда потоки имеют достаточно времени для завершения, но с настройкой ниже я иногда получаю пустые файлы. Это происходит примерно в 1/15 раза, когда я запускаю тест. Кажется, проблема связана с записью, т. Е. Файл на самом деле пуст.

import lombok.Synchronized;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;

import static org.junit.jupiter.api.Assertions.assertEquals;

class Test{

    File dummyFile=new File("/tmp/foo.txt"); //initially "foo"
    String dummyText="foo";
    @Test
    void test() throws Exception{
        assertEquals(dummyText,read(dummyFile));
        spamAndTest();

        for(int i = 0; i< 100 ; i++){
            spamAndTestInThread(i);
        }
        Thread.sleep(100);
        assertEquals(dummyText,read(dummyFile));
    }

    @Synchronized
    public static String read(File f){


        Charset charset = Charset.forName("UTF-8");
        Path p = f.toPath();

        String ret=null;
        try {
            ret = new String(Files.readAllBytes(p),charset);
        } catch(Exception ex) {
            ExUtils.handle(ex);
            return null;
        }




        if(ret.trim().length()==0){

            throw new RuntimeException("WTF, empty file:" +p);

        }
        return ret;


    }

    private void spamAndTestInThread(int i) {
        Thread tt= new Thread("spam"){
            public void run(){
                for(int i = 0; i< 1000 ; i++){
                    save(dummyFile,dummyText);

                }
            }
        };
        tt.start();
    }


    @Synchronized
    private void save(File file, String data) {

        Charset charset = Charset.forName("UTF-8");

        try {
            Files.write(file.toPath(),data.getBytes(charset));
        } catch (IOException e) {
            e.printStackTrace()
        }

    }

}

Итак, все признаки указывают на то, что это вызвано ошибкой сохранения, когда JVM завершает работу. Я запускаю это на IntelliJ, но не думаю, что это так, поскольку оригинальная проблема также возникает в производстве, когда JVM завершается в неудачный момент. Кроме того, я не могу воспроизвести это, когда запущен только 1 поток.

Разве не должен Files.write обрабатывать завершения JVM из коробки? Если нет, то как с этим справиться?

Использование Oracle JDK 8 как локально, так и в продуктах.

РЕДАКТИРОВАТЬ:

Для пояснения, я не говорю о сбоях питания / сбоях JVM, но о случаях, когда отключение выполняется нормально. Также полезно отметить, что файл всегда полностью пуст, то есть он не умирает в середине записи.

EDIT2:

Я попробовал это с намного более длинной последовательностью и на этот раз заставил это обрезать середину и воспроизвел это только с 2 дополнительными нитями.

EDIT3:

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

Ответы [ 2 ]

0 голосов
/ 01 ноября 2018

удалось окончательно решить проблему, добавив следующий хук отключения

Thread tt= new Thread(""){
            public void run(){
                //System.out.println("los shutdown hook (fileutils)");
                Sleep.millis(100); //to give other hooks time to start saving
                shutdown=true;
                while(writing)
                    System.err.println("halting shutdown while write in progress");
            }
        };
        Runtime.getRuntime().addShutdownHook(tt);

и затем установка write = true в начале метода сохранения + write = false в конце его + if (shutdown) return;

0 голосов
/ 31 октября 2018

В вашем методе тестирования вы читаете файл, прежде чем писать в нем. Как вы гарантируете, что фиктивный файл всегда будет содержать текст "foo".

assertEquals(dummyText,read(dummyFile));
spamAndTest();

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

spamAndTest();
assertEquals(dummyText,read(dummyFile));

Вы уверены, что все потоки успешно выполнены в течение 100 мс?

Thread.sleep(100);
assertEquals(dummyText,read(dummyFile));

Может быть, поток просто обрезает файл и в это время assertEquals(dummyText,read(dummyFile)); выполнено.

...