subprocess.communicate()仅在从脚本运行时才会神秘地挂起

时间:2015-04-15 23:29:25

标签: python bash unix subprocess

我正在从Bash脚本调用名为spark-ec2的Python工具。

作为其工作的一部分,spark-ec2通过使用ssh模块对系统subprocess命令进行多次调用。

Here's an example

s = subprocess.Popen(
    ssh_command(opts) + ['-t', '-t', '-o', 'ConnectTimeout=3',
                         '%s@%s' % (opts.user, host), stringify_command('true')],
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT  # we pipe stderr through stdout to preserve output order
)
cmd_output = s.communicate()[0]  # [1] is stderr, which we redirected to stdout

出于某种原因,spark-ec2挂在调用communicate()的那一行。我不明白为什么。

为了记录,这里有一段摘录,展示了我如何调用spark-ec2:

# excerpt from script-that-calls-spark-ec2.sh

# snipped: load AWS keys and do other setup stuff

timeout 30m spark-ec2 launch "$CLUSTER_NAME" ...

# snipped: if timeout, report and exit

杀死我的是,当我单独调用spark-ec2时它工作正常,当我从这个Bash脚本复制并粘贴命令并以交互方式运行它们时,它们工作正常。

只有当我像这样执行整个脚本时才会这样做

$ ./script-that-calls-spark-ec2.sh

spark-ec2挂起communicate()步骤。这让我疯了。

发生了什么?

1 个答案:

答案 0 :(得分:2)

这是其中一件事,一旦我弄明白,让我惊恐万分地说出“哇......”。

在这种情况下,spark-ec2没有挂起,因为与使用subprocess.PIPE相关的一些死锁,如果spark-ec2使用Popen.wait() instead of Popen.communicate()则可能就是这种情况。

由于spark-ec2仅在一次调用整个Bash脚本时挂起这一事实所暗示的问题是由于某些事物的行为方式略有不同,这取决于它是否以交互方式调用。

在这种情况下,罪魁祸首是GNU coreutils实用程序timeout,它提供了一个名为--foreground的选项。

来自timeout手册页:

   --foreground

          when not running timeout directly from a shell prompt,

          allow  COMMAND  to  read  from  the TTY and get TTY signals; in this
          mode, children of COMMAND will not be timed out

如果没有此选项,Python的communicate()将无法读取subprocess.Popen()调用的SSH命令的输出。

这可能与SSH通过-t交换机分配TTY有关,但说实话,我并不完全理解它。

可以说的是,修改Bash脚本以使用--foreground这样的选项

timeout --foreground 30m spark-ec2 launch "$CLUSTER_NAME" ...

让一切按预期工作。

现在,如果我是你,我会考虑将这个Bash脚本转换成其他不会让你疯狂的东西......