JVM退出时,Files.write结尾为空文件

时间:2018-10-31 20:24:28

标签: java concurrency nio

走了一条漫长的路来隔离我的问题,并设法将其确定为以下测试。当线程有足够的时间完成所有工作时,一切正常,但是在下面的设置中,我偶尔会遇到空文件。这种情况发生在我运行测试的时间的约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崩溃,而是在正常运行shutdownhooks的情况。同样要注意的是,该文件始终始终为空,即不会在写入过程中死亡。

EDIT2:

我用更长的字符串尝试了一下,这次又把它切成中间,并仅用了2个额外的线程来重现了它。

EDIT3:

尝试添加关闭挂钩,是的,它们在测试后正在运行。因此,希望这些写入能够完成,但显然不会。

2 个答案:

答案 0 :(得分:0)

在测试方法中,您正在读取文件,然后再写入文件。如何确保虚拟文件将始终包含文本“ foo”。

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

您应该先在文件中写入文本,然后再读取if。检查更改此顺序是否可以解决您的问题。

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

您确定所有线程都能在100毫秒内成功执行吗?

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

线程可能会截断文件,然后  当时assertEquals(dummyText,read(dummyFile));被执行。

答案 1 :(得分:0)

设法通过添加以下关闭钩子最终解决了问题

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);

然后在保存方法的开始处设置Writing = True +在保存方法的末尾处设置write = false + if(shutdown)return;