我正在使用Ruby。我试图弄清楚bundler的rake release
挂在git push
步骤上的原因,这里也有不完整的讨论。
我已经把它缩小到这行代码挂起:
`git push 2>&1`
我可以通过在IRB中运行同一行代码来重现这个问题。
神秘的是,底层的git push
实际上确实执行了,但由于某种原因,Ruby从未收到返回状态。它只是无限期地等待子进程。
检查进程列表显示子进程具有Z+
(僵尸?)状态:
UID PID PPID C STIME TTY TIME CMD USER PGID SESS JOBC STAT TT
501 23397 3757 0 1:44PM ttys001 0:00.54 irb mbrictson 23397 0 1 S+ s001
501 26035 23397 0 2:06PM ttys001 0:00.00 (sh) mbrictson 23397 0 1 Z+ s001
显然,git push
运行只是在我的shell中找到。只有当它通过Ruby使用backticks调用时,它才会挂起。
此外,这也很好:
system("git push 2>&1") # => true
而且这(即没有输出重定向)也很好!
`git push` # => "Everything up-to-date"
问题的一部分显然是我的~/.ssh/config
中的ControlMaster auto
。当执行git push
时,这导致在后台产生新的控制连接进程。也许%x(git push 2>&1)
正在等待此后台进程退出?如果我在SSH配置中禁用ControlMaster,这实际上解决了问题。
尽管如此,这还是困扰着我。我宁愿不必仅仅为了让Ruby的backticks操作符高兴而禁用ControlMaster。
有人能解释一下吗:
- 为什么
%x()
挂起而system()
不挂起 - 为什么删除
2>&1
会有所不同
这是在Mac OS X Yosemite上使用Ruby 2.2.0。
想好了:
为什么%x()挂起,而system()不挂起?
%x()等待完全读取子进程的输出;system()不关心输出。
根据此错误报告,OpenSSH中的ControlPersist
设置导致stderr在主连接的生存期内保持打开状态。在我的SSH配置中,我有ControlPersist 5m
,不出所料,在最终完成之前,%x()正好挂起了5分钟。
这不会影响system()
,因为系统不等待输出。
为什么删除2>&1有什么不同?
如上所述,SSH主连接会打开stderr。它显然关闭了stdout。由于stdout已关闭,%x(git push)
会立即结束,因为stdout上没有什么可等待的。将2>&1
添加到命令中时,会导致stderr重定向到stdout。由于stderr由主连接保持打开状态,这反过来又会导致stdout保持打开状态。%x
在stdout上等待并挂起。
不幸的是,OpenSSH的这种行为没有改变的迹象,因此除了禁用ControlPersist
之外,没有其他令人满意的解决方案。