记录一次由于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.