【问题标题】:STDIN.read_nonblock messes up other programs after Ruby exitsRuby 退出后,STDIN.read_nonblock 会弄乱其他程序
【发布时间】:2019-12-01 21:33:13
【问题描述】:

我正在用 Ruby 编写一个交互式终端程序,它有时会运行 STDIN.read_nonblock(256) 以刷新来自用户的任何缓冲输入。

运行我的 Ruby 程序后,如果我在同一个终端和 shell 中运行 git add -p(另一个交互式程序),那么 git 会发生故障:它不会花任何时间等待用户输入,而是只显示它的所有提示立即退出。

这是一个 shell 会话,展示了我如何使用 Ubuntu 18.04、Ruby 2.6.3 和 git 2.17.1 重现此问题:

$ mkdir testrepo && cd testrepo && git init .
Initialized empty Git repository in /home/david/tmp/testrepo/.git/
$ touch foo.txt
$ git add foo.txt
$ echo hi > foo.txt
$ git add -p  # works fine
diff --git a/foo.txt b/foo.txt
index e69de29..45b983b 100644
--- a/foo.txt
+++ b/foo.txt
@@ -0,0 +1 @@
+hi
Stage this hunk [y,n,q,a,d,e,?]? q

$ ruby -v -e 'STDIN.read_nonblock(256)'
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
Traceback (most recent call last):
    2: from -e:1:in `<main>'
    1: from <internal:prelude>:73:in `read_nonblock'
<internal:prelude>:73:in `__read_nonblock': Resource temporarily unavailable - read would block (IO::EAGAINWaitReadable)
$ git add -p  # bad: exits before waiting for input
diff --git a/foo.txt b/foo.txt
index e69de29..45b983b 100644
--- a/foo.txt
+++ b/foo.txt
@@ -0,0 +1 @@
+hi
Stage this hunk [y,n,q,a,d,e,?]? 
$ git --version
git version 2.17.1

我可以在我的 Ruby 程序中添加任何解决方法来防止这种情况发生吗? (我也有兴趣了解可能发生的事情。)

顺便说一句,我注意到一个混乱的终端可以通过运行bash 来启动一个新的shell,然后按Ctrl+D 退出那个新的shell。

【问题讨论】:

    标签: ruby linux shell terminal


    【解决方案1】:

    当您使用STDIN.read_nonblock 时,Ruby 使用fcntl 系统调用在其标准输入文件描述符(2) 上设置O_NONBLOCK 标志,并且它永远不会将其更改回来。该描述符显然与其他进程共享,例如您的 shell。

    不幸的是,git 似乎没有关闭这个标志,所以当它试图从终端读取用户输入并且标准输入仍处于非阻塞模式时,它会得到不好的结果。

    我不确定这是 Git 的错误还是 Ruby 的错误,但您可以通过创建一个指向标准输入的 新文件描述符 并使用它在 Ruby 代码中轻松修复它对于所有非阻塞读取:

    $stdin_nonblock ||= (File.open('/dev/stdin') rescue STDIN)
    $stdin_nonblock.read_nonblock(256)
    

    警告:不幸的是,您并不总是有打开/dev/stdin 的权限(如果您在以su 开头的shell 中运行,则通常没有权限)。这就是我在代码中添加rescue STDIN 的原因。我怀疑dup 将是一个更可靠的系统调用,而不是open,如果它可以工作的话。

    【讨论】:

    • 这是 Ruby 的“错误”,因为它设置了阻塞标志。运行read_nonblock 后,许多程序都会失败。只需在命令行上尝试一个简单的cat,它也会失败。 STDIN 不应该是 O_NONBLOCK。
    【解决方案2】:

    除了您自己的答案之外,还有一种方法可以将描述符上的 O_NONBLOCK 重置为阻塞。这也解决了这个问题:

    require 'fcntl'
    
    flags = STDIN.fcntl(Fcntl::F_GETFL, 0)
    STDIN.fcntl(Fcntl::F_SETFL, flags & ~Fcntl::O_NONBLOCK)
    

    您必须在 ensure 块中运行此代码,因为 read_nonblock 在任何情况下都不会重置标志。

    【讨论】:

      【解决方案3】:

      作为使用fcntl 进行位旋转的替代方法,您可以使用io/nonblock 清除O_NONBLOCK

      require 'io/nonblock'
      $stdin.nonblock {$stdin.read_nonblock(256)}
      puts $stdin.nonblock? # false
      

      io/nonblockRuby source tree 的一部分,因此它应该始终可用。

      【讨论】:

        猜你喜欢
        • 2023-01-13
        • 2011-01-13
        • 2018-12-13
        • 2017-09-27
        • 1970-01-01
        • 2016-05-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多