记录一次由于waitfor()导致死锁的bug

在上周的开发中由于使用了proccess.waitfor()语句导致了进程死锁

问题描述:

在我开发的数据采集的模块,需要调用服务器上的一个python脚本来进行sql语法的转换所以我使用了java自带的方法

Process proc;
try {
proc = Runtime.getRuntime().exec(python filePath);
    LOGGER.info("py-mysql2pgsql is successful.....");


proc.waitFor();
LOGGER.info("waitFor ... ... ...");

return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}

 当时自测在我们的开发环境使用这段代码并没有什么问题,但是当项目发布到测试环境时出现了进程死锁的问题

    由于进程被锁住导致任务被挂起,不报错也不继续往下执行。

     这个问题让我花费了很长时间才锁定了bug,由于当时在开发环境并没有什么问题,所以也就没有想到是waifor

     导致了死锁,所以在代码中记录了很多的日志才找到。

 接下来看看具体的日志信息:

 

 可以看到项目的日志打印到

py-mysql2pgsql is successful..... 就停止不动了。

原因:

    在网上找了很多产生这种问题的原因,但是以下这种是我比较认同的。

    在调用Runtime.getRuntime().exec() 方法时会启动一个子进程来专门执行指定的linux命令,在子进程执行linux命令的同时,我们

   使用了 proccess.waitfor()命令使主进程挂起等待子进程执行完毕。但是子进程在执行时会产生一些日志信息【主进程可以使用 process.getInputStream()
   和 process.getErrorStream() 来获取子进程的打印信息和日志信息】。
子进程在执行的期间会不断向主进程发送执行信息,但是主进程已经使用waitFor挂起,导致主进程与子进程之间的缓冲区填满导致子进程也被
挂起,引起了死锁的问题。

   至于我当时在开发环境为什么没有遇到这个问题,现在还没有找到原因,目前判断可能是开发环境的硬件资源和内存缓冲区优于测试环境吧。后续会跟进这个问题。

解决方案:

    指导了问题所在,接下了就是解决问题。既然是因为主进程与子进程之间的缓冲区堵塞导致的死锁,那么我们可以创建新的线程来消费缓冲区的数据:

    具体代码如下:

    

 
 
Process proc;
try {
proc = Runtime.getRuntime().exec(python filePath);
LOGGER.info("py-mysql2pgsql is successful.....");
//在 exec方法和waitFor()方法之间加上消费缓冲区数据的方法
//之前说过子进程给主进程反回的数据分为 日志信息数据 process.getInputStream() 和 错误信息数据 process.getErrorStream()

consumeInputStream(proc.getInputStream());
consumeInputStream(proc.getErrorStream());
proc.waitFor();
LOGGER.info("waitFor ... ... ...");

return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

  

public static String consumeInputStream(InputStream stream){
    try {
        BufferedReader br = new BufferedReader(new InputStreamReader(stream));
        String s ;
        StringBuilder sb = new StringBuilder();
        while((s=br.readLine())!=null){
            System.out.println(s);
            sb.append(s);
        }
        return sb.toString();
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

  当然,你也可以重新开两个线程来消费 inputStream 和 errorStream.

    

 

 

猜你喜欢

转载自www.cnblogs.com/yooc1994/p/10746379.html