【问题标题】:Java hangs even though script's execution is completed即使脚本的执行完成,Java 也会挂起
【发布时间】:2011-03-29 10:51:34
【问题描述】:

我正在尝试从我的 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 命令才能知道进程已经结束? 我怎样才能避免那个执行?,尤其是。因为第一个脚本不会产生任何输出。

屏住呼吸等待答案!

【问题讨论】:

  • 尝试制作一个简短的完整示例程序来展示您的问题。如果人们看到(甚至可能尝试运行)代码,他们可能会发现错误。
  • 脚本真的很短吗?也许脚本终止得太快了?你能发布一些脚本的代码吗?你能发布运行脚本的Java代码吗?你能把它简化为一个带有 main 的简单可执行类吗?
  • 按照@Gilles 和@Romain 的建议编辑了描述。期待最好的!

标签: java shell process pid


【解决方案1】:

如果您在 Linux/Unix 上运行,请尝试消耗 stdout/stderr。

上次我不得不从 Java 运行外部脚本时,我最终不得不检查操作系统并仅在 Windows 上附加输出吃线程。

【讨论】:

【解决方案2】:

我也会抽标准输入。泵送所有三个标准流是个好主意。我无法解释,但我之前遇到过类似的进程挂起问题,问题在于缺少标准输入泵。

【讨论】:

  • 但我仍然很困惑,关于 exec 如何帮助 Java 识别进程是否结束!
【解决方案3】:

我没有在 Linux 中尝试过,但我在 Windows 中遇到了这个问题。我想我必须在流终止之前关闭流程的标准。

【讨论】:

    【解决方案4】:

    您只是不能从 Java 执行脚本并等待它。您必须执行该脚本的解释器。 cmdarray 应该以“bash”而不是“myscript.bash”开头。

    【讨论】:

    • 如果脚本具有可执行权限并具有适当的#!,则直接执行脚本可以正常工作。指示解释器的行(例如 /bin/sh)。
    • 嗯?在 *nix 中,脚本是一等公民,其行为类似于其他进程。
    【解决方案5】:

    一个可能的原因是安装软件包会启动后台进程,这些进程使您的一个或两个管道(stdout/stderr)保持打开状态。因此,您的线程不会终止。这意味着这些后台进程没有正确地守护进程。一个合适的守护进程在完成初始化后用 /dev/null 或一个日志文件替换它原来的 stdin/stdout/stderr。

    【讨论】:

      【解决方案6】:

      我的猜测是,Java 认为脚本不会结束,直到它通过 stdin/stdout/stderr 传递给它的管道被子进程关闭。也就是说,stdin 上不再有活动的 reader 进程,stdout/stderr 上不再有活动的 writer 进程。

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

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

      当您的脚本分叉继承 stdin/stdout/stderr 的后台任务(如新安装的服务)时,通常会出现此问题。

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

      如果在 Linux 上,检查 /proc/*/fd 是否有任何新服务,并查看它们的 stdin/stdout/stderr 是否与您的 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

      【讨论】:

      • 谢谢@Adrian。这似乎是原因。一方面,我很想将此标记为正确答案,另一方面,我不确定。我不合逻辑的 exec 语句仍然存在!我会等待一段时间以获得更多答案。
      • 我添加了更多关于 exec 语句的内容。
      【解决方案7】:

      听起来您已经理解了问题的一部分,但您仍然没有完全了解“大局”。

      1. 是的,可以从 Java 程序调用 shell 脚本。简单,事实上!

      2. 是的,通常情况下,您应该能够看到打印到 stderr 或 stdout 的任何内容。

      3. 是的,您应该能够将 stderr 和/或 stdout 重定向到文件。

      4. 您的问题显然是您没有看到您期望看到的,当您期望看到它时。这是正常的,这是意料之中的......而且,在更大的计划中,这是非常可取的。

      我认为,核心问题是“I/O 缓冲”。

      缓冲是件好事。

      我强烈建议您: 1. 创建一个简单的单线程命令行 Java 测试程序,该程序调用一个 shell 脚本。

      1. 用线程详细说明测试程序。 根据计划的结构,您可能会也可能不会获得“预期结果”。

      2. 用 C 程序(或其他 Java 程序)替换 shell 脚本

      3. 实验,看看你学到了什么。

      祝你好运 - 请不要直接跳到第一个看似合理的结论。总是测试你的理论。

      PS: 并重复我的口头禅:“I/O 缓冲很好”;-)

      PPS:

      问:到目前为止,这个问题只被浏览了 38 次!

      A:“你必须学会​​耐心,年轻的蚱蜢”;-)

      问:这对我来说有点关键!

      答:“你也必须学会谦逊”;-)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-05-17
        • 1970-01-01
        • 1970-01-01
        • 2012-02-18
        相关资源
        最近更新 更多