即使脚本执行完成,Java 也会挂起

标签 java shell process pid

我正在尝试从我的 java 代码中执行一个脚本,如下所示:

Process p = Runtime.getRuntime().exec(cmdarray, envp, dir); // cmdarray is a String array
// consisting details of the script and its arguments

final Thread err = new Thread(...); // Start reading error stream
err.start();
final Thread out = new Thread(...); // Start reading output stream
out.start();
p.waitFor();
// Close resources 

脚本执行结束(它的pid没有了),但是java卡在了进程的waitFor()方法上! 是的,我正在 2 个单独的线程中读取输出和错误流。是的,它们在最后加入(在 waitFor() 之后)。

脚本基本上安装了几个 RPM(比如 10 个左右)并配置它们。所以脚本运行了 60 多秒。

它看起来类似于以下内容:

#!/bin/sh

#exec 3>&1 >/var/log/some_log 2>&1

# If the above line is uncommented, Java recognizes that the 
# process is over and terminates fine.

tar xzf a-package-having-rpms.tar.gz
cd unpacked-folder
(sh installer-script.sh) #This installs 10 odd rpms in a subshell and configures them
cd ..
rm -rf unpacked-folder

exit 0

令人震惊的是,如果我将以下行放在脚本中(在顶部),Java 就会明白脚本已经结束并完美地终止进程。

exec 3>&1 > /var/log/some_log 2>&1

郑重声明,脚本不会生成任何输出。零字符!所以把 exec 语句放在那里是没有意义的!

但是,神奇的是,在脚本中放置 exec 语句使 java 工作! 为什么??

如何避免脚本中出现不合逻辑的 exec 语句?

如果您对 installer-script.sh 的样子感兴趣,那么:

#!/bin/sh

exec 3>&1 >>/var/log/another-log.log 2>&1
INSDIR=$PWD
RPMSDIR=$INSDIR/RPMS
cd $RPMSDIR
#
rpm -i java-3.7.5-1.x86_64.rpm
rpm -i --force perl-3.7.5-1.x86_64.rpm
rpm -i --nodeps mysql-libs-5.0.51a-1.vs.i386.rpm
rpm -i --nodeps mysql-5.0.51a-1.vs.i386.rpm
rpm -i --nodeps mysql-server-5.0.51a-1.vs.i386.rpm
rpm -i --nodeps perl-DBD-MySQL-3.0007-2.el5.i386.rpm
rpm -i --nodeps perl-XML-Parser-2.34-6.1.2.2.1.i386.rpm
.
.
.

现在,为什么 Java 需要第一个脚本中的 exec 命令才能知道进程结束? 我怎样才能避免那个 exec?,尤其是。因为第一个脚本不产生任何输出。

屏息等待答案!

最佳答案

我的猜测是,在通过 stdin/stdout/stderr 传递给它的管道被子进程关闭之前,Java 认为脚本不会结束。也就是说,stdin 上不再有活跃的读取进程,stdout/stderr 上不再有活跃的写入进程。

当您在管道上读取时,您不会收到文件结束指示,直到没有更多进程 打开管道以供输出。因此,如果一个进程 fork 并且新进程继承了一个打开的文件句柄,那么原始进程终止,仍然有一个打开文件的进程,读者仍然会等待。

与您正在编写的管道类似,在最后一个读取器关闭管道之前,您不会收到“管道损坏”信号。

当您的脚本 fork 继承标准输入/标准输出/标准错误的后台任务(如新安装的服务)时,通常会出现此问题。

通过使用 exec,您明确地打破了这些管道的继承链,以便后台进程不使用它们。

如果在 Linux 上,检查/proc/*/fd 是否有任何新服务,看看它们的标准输入/标准输出/标准错误是否与您的 Java 进程传递给您的脚本的管道相同。

当您运行/etc/init.d/xxx 脚本时,同样的情况经常发生:当您从命令行运行它们时,它们正常完成,但当您从某种监视器运行它们时,它们似乎挂起。

编辑:

您说安装程序脚本包含以下行:

exec 3>&1 >>/var/log/another-log.log 2>&1

第一项,3>&1,将标准输出克隆到文件描述符 3(参见 man bash 中的 Redirections)。据我所知,fd 3 没有特殊含义。然后它通过打开/var/log/another-log.log 替换 stdout 并通过克隆 stdout 替换 stderr。请参阅 bash 手册页的重定向部分

这意味着新的文件描述符 3 已打开以写入最初作为 STDOUT 传入的管道。期望成为系统服务守护进程的程序通常会关闭文件描述符 0 (STDIN)、1 (STDOUT) 和 2 (STDERR),但可能不会理会任何其他文件描述符。此外,既然 shell 已经打开了 FD-3,它会将打开的文件传递给它执行的任何命令,包括后台命令。

您知道安装程序打开 FD 3 是否有任何特殊原因吗?我的猜测是,如果您简单地从安装程序中删除“3>&1”项,您的问题就会得到解决。这将允许您从脚本中完全删除 exec

关于即使脚本执行完成,Java 也会挂起,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3476762/

相关文章:

java - 二进制/合格名称错误?以 : <any? >$ 开头

java - Eclipse IDE 在处理时失败,甚至使用 "JVM Terminated. Exit code=1"

java - 使用 run() 而不是线程的 start() 会发生什么?

bash - 为什么在反引号内执行的命令不会影响当前的 shell?

shell - 运行 Emacs shell 时可见的 ANSI 转义序列(这些不是 ANSI 颜色)

powershell - 检查哈希表是否包含值

java - 如何使用 JavaFX 在直线末端绘制箭头

bash - sed 到 grep 两个模式之间的字符串

java - 使用java时进程输入流有限制吗?

c++ - 在 VC++ 中执行一个进程并返回它的标准输出