我正在从Bash脚本调用名为spark-ec2的Python工具。
作为其工作的一部分,spark-ec2通过使用ssh
模块对系统subprocess
命令进行多次调用。
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()
步骤。这让我疯了。
发生了什么?
答案 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脚本转换成其他不会让你疯狂的东西......