Ruby文件IO停滞问题

4
以下 Ruby 代码可以产生所有预期的输出,但无法正确退出。在完成 each_byte 循环之前,它会挂起 - 消耗 100% 的 CPU - 直到进程被杀死。
f = File.new(ARGV.shift)
i = 0
f.each_byte {printf("%08X\n", f.pos - 1) if (i += 1) % 16 == 1}
f.close

我尝试了很多种不同的方式来设计循环(将f.pos替换为i或反之亦然),它们都能正常工作!只有这种方法会导致其挂起,我完全不知道是为什么。
有什么想法吗?
2个回答

3

好的,由于运行测试代码不需要任何外部Ruby库,我可以在我的机器上编译1.9版本而无需安装它,并运行测试程序。

以下是我的观察:

  1. Ruby似乎“挂起”(您无法中断它,它也不会自己退出)。
  2. top显示Ruby以100%的CPU运行。
  3. strace一旦进入100%CPU模式,就不会输出任何内容。

从这里可以明显看出Ruby陷入了无限循环。查看io.c中的each_byte,并在可疑位置添加printf,可以发现我们卡在了哪里:

static VALUE
rb_io_each_byte(VALUE io)
{
    rb_io_t *fptr;
    char *p, *e;

    RETURN_ENUMERATOR(io, 0, 0);
    GetOpenFile(io, fptr);

    for (;;) {
        p = fptr->rbuf+fptr->rbuf_off;
        e = p + fptr->rbuf_len;

        printf("UH OH: %d < %d\n", p, e);  /* INFINITE LOOP ALERT */

        while (p < e) {
            fptr->rbuf_off++;
            fptr->rbuf_len--;
            rb_yield(INT2FIX(*p & 0xff));
            p++;
            errno = 0;
        }
        rb_io_check_byte_readable(fptr);
        READ_CHECK(fptr);
        if (io_fillbuf(fptr) < 0) {
            break;
        }
    }
    return io;
}

在我的计算机上,它打印出这个:
UH OH: 0 < 0
UH OH: 137343104 < 137351296
UH OH: 137343119 < 137343104
UH OH: 137343119 < 137343104
UH OH: 137343119 < 137343104
...ad infinitum...

137343119并不小于137343104,这意味着我们停止进入while循环(这将产生该块)。

当您运行代码以避免挂起时,会得到以下结果:

UH OH: 0 < 0
UH OH: 137341560 < 137349752
UH OH: 137341560 < 137349752
UH OH: 137341560 < 137349752
UH OH: 137341560 < 137349752
....

137341560小于137349752。

无论如何,目前我只能提供这些信息。仍然不知道为什么会发生这种情况。但是现在我们至少知道了发生了什么。编写该代码的人可能可以立即解释为什么会发生这种情况。

总之,我仍然认为lseek调用会以某种方式破坏Ruby的内部文件指针,并且上面的循环因此而出现问题。

编辑

以下是修复方法:

io.c中的flush_before_seek更改为以下内容:

static rb_io_t *
flush_before_seek(rb_io_t *fptr)
{
  int wbuf_len = fptr->wbuf_len;

  if (io_fflush(fptr) < 0)
      rb_sys_fail(0);

    if (wbuf_len != 0)
      io_unread(fptr);

    errno = 0;
    return fptr;
}

我添加的是对 wbuf_len != 0 的检查,这样我们就不会不必要地执行 io_unread。在 each_byte 循环中调用 io_unread 是导致问题混乱的原因。跳过未读取使事情正常工作,并且所有针对 make test 的测试仍然通过。
无论如何,这不是一个合适的修复方法,因为 f.pos 存在一些根本性的思考错误。它只是一个解决方法......但它仍然解决了上述问题。 :-/

0

我猜这可能是 Ruby 1.9.2 中的某种 bug,因为在 1.8.7 中它可以正常工作。

卡住了:

f.each_byte do
  i += 1
  if (i % 16 == 1)
    puts "%08X\n" % (f.pos - 1)
  end
end

不需要 if 就能工作:

f.each_byte do
  i += 1
  # if (i % 16 == 1)
    puts "%08X\n" % (f.pos - 1)
  # end
end

可以使用 if,也可以不用 puts

f.each_byte do
  i += 1
  if (i % 16 == 1)
    # puts "%08X\n" % (f.pos - 1)
  end
end

如果有另一个puts,则与ifputs一起使用:

f.each_byte do
  i += 1
  if (i % 16 == 1)
    puts "%08X\n" % (f.pos - 1)
  end
  puts "%08X\n" % (f.pos - 1)
end

它也会让 Ruby 卡死,无法被中断,这通常是某些内部问题完全崩溃的迹象。


但是 f.each_byte { if((i += 1) % 16 == 1) then puts f.pos - 1; else puts f.pos - 1; end } 完成得非常好。 - mu is too short
1
刚刚查看了Ruby源代码。调用pos会执行各种有趣的操作,包括fflushlseek。不确定它如何与同时读取字节的循环配合。 - Casper
@Casper:f.each_byte {puts "%08X\n" % (f.pos - 1) if f.pos % 16 == 1}(完全不使用i)也不会挂起。有很多重构循环的方法,使得pos仍然在其中被调用,并且脚本能够正确终止。 - Max
1
@Max @mu - 对不起,伙计们,我没有安装1.9版本,也不想搞乱我的美丽Ruby环境(甚至不用rvm)。但是如果我有1.9版本,我会首先在锁定的版本上运行strace!这将很快告诉你它是否进入了某个无限循环,或者是否存在类似解释器死锁之类的问题。有人愿意尝试一下,让我们看看结果如何? - Casper
1
@Casper:有趣。我用strace运行了几个不同版本的脚本。它们都从read文件开始,然后_llseek回到开头再次开始_llseek。我唯一能看到的明显区别是挂起的版本通过16字节增量寻找,而我在最后一条评论中发布的版本逐字节寻找。我觉得这很奇怪,因为从逻辑上讲,两个版本都应该以单字节增量寻找。也许Ruby试图简化循环,从而引入了错误。 - Max
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接