system( "git push 2>&1" ) 工作正常,但 %x(git push 2>&1) 挂起。为什么?



我正在使用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之外,没有其他令人满意的解决方案。

最新更新