英文:
Linux zombies processes left unterminated with Java ProcessBuilder
问题
我有一个使用Java Spring REST API的控制器,该控制器使用ProcessBuilder类运行Linux命令。
命令是一个生成的'find'命令。
问题是,经过几天的使用后,我在托管服务器上发现了许多未终止的进程。我不知道为什么它们仍然存在而没有结束或销毁。(我使用ps -ef命令进行了检查)
这是我的runCmd函数:
public static final BufferedReader runCmd(String cmd) throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("bash", "-c", cmd);
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()));
int ret = process.waitFor();
return output;
}
有没有办法确保没有多余的进程残留?
更新
问题仅来自具有非常大的输出流(标准输出)的命令。感谢@DuncG的提示。
由于这个输出很重要,我不能忽略它。我必须找到一种方法来消耗它。
有关如何使用可运行线程(Runnable Threads)来实现的任何想法吗?
谢谢。
英文:
I have a java Spring REST API with a controller that runs a linux command with the ProcessBuilder class
. The command is a generated 'find' command
The problem is that I found a lot of unterminated processes in the hosting server after fiew days of use. I don't know why they still there and not ended or destroyed . (I checked with a ps -ef command)
Here is my runCmd function:
public static final BufferedReader runCmd(String cmd) throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("bash", "-c", cmd);
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()));
int ret = process.waitFor();
return output;
}
Is there a way to make sur that there is no more process left behind ?
UPDATE
The problem comes only from commands with a very large output stream (std output) Thanks for the hint @DuncG
As this output is important, I can't ignore it. I have to find a way to consume it.
Any Idea on how to do it with Runnable Threads ?
Thanks
答案1
得分: 2
你的命令是否生成了大量的输出?僵尸进程的原因可能仅是因为 cmd 已将许多输出写入了标准输出 (STDOUT),并且流在 BufferedReader 中被阻塞。
你可以通过添加重定向到 null 来测试是否是这种情况 - 只需在 cmd 末尾添加 " > /dev/null"。这会丢弃子进程的输出,意味着 BufferedReader 不会充满未读数据 / 阻塞子进程。
processBuilder.command("bash", "-c", cmd + " > /dev/null");
如果这解决了僵尸进程问题,你可以恢复重定向,并使 ProcessBuilder
重定向输出到文件(在调用 start()
之前),或者你需要添加一个线程来消耗生成的 IO。
Path tmpdir = Path.of(System.getProperty("java.io.tmpdir"));
Path out = tmpdir.resolve("stdout.log");
processBuilder.redirectErrorStream(true);
processBuilder.redirectOutput(out.toFile());
最后,你应该将输出文件返回给调用者进行检查,或者可以返回 Files.newBufferedReader(out)
。
如果不使用上述的文件重定向方式,这将使用线程将输出捕获到内存缓冲区中。请注意,如果不将 ERR 重定向到 OUT,则还需要为 STDERR 进行重复操作:
Process p = pb.start();
ByteArrayOutputStream stdout = new ByteArrayOutputStream(8192);
new Thread(() -> copy(p.getInputStream(), stdout), "STDOUT").start();
int rc = p.waitFor();
byte[] sour = stdout.toByteArray()
使用方法:
private static void copy(InputStream in, OutputStream buf)
{
try(var autoClose = in; var autoClose2 = buf)
{
in.transferTo(buf);
}
catch(IOException io)
{
throw new UncheckedIOException(io);
}
}
英文:
Are your commands generating a lot of output? The cause of the zombies may be simply that cmd has written a lot of output the STDOUT and the stream is blocking in the BufferedReader.
You can test if this the case by adding redirect to null - just append " > /dev/null" the end of cmd. This discards the sub-process output and means the BufferedReader is not full of unread data / blocking the sub-process.
processBuilder.command("bash", "-c", cmd + " > /dev/null");
If that fixes the zombie problem you can revert the redirect and make ProcessBuilder
redirect output to files (before calling start()
), or you'll need to add a thread to consume the IO as it is generated.
Path tmpdir = Path.of(System.getProperty("java.io.tmpdir"));
Path out = tmpdir.resolve("stdout.log");
processBuilder.redirectErrorStream(true);
processBuilder.redirectOutput(out.toFile());
At the end you should return the out file for caller to check, or could return Files.newBufferedReader(out)
.
If you don't use the redirect to file as above, this will store using thread to capture the output into memory buffer. Note you'd need to duplicate for STDERR too if not redirecting ERR->OUT:
Process p = pb.start();
ByteArrayOutputStream stdout = new ByteArrayOutputStream(8192);
new Thread(() -> copy(p.getInputStream(), stdout), "STDOUT").start();
int rc = p.waitFor();
byte[] sour = stdout.toByteArray()
Using method:
private static void copy(InputStream in, OutputStream buf)
{
try(var autoClose = in; var autoClose2 = buf)
{
in.transferTo(buf);
}
catch(IOException io)
{
throw new UncheckedIOException(io);
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论