Ruby File IO Hang - PullRequest
       6

Ruby File IO Hang

4 голосов
/ 28 июля 2011

Следующий код Ruby создает все ожидаемые выходные данные, но не завершается должным образом. Перед завершением цикла each_byte он зависает - потребляя 100% ресурсов ЦП, - пока процесс не будет остановлен.

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 голосов
/ 29 июля 2011

Хорошо, поскольку для запуска тестового кода не требуются внешние библиотеки ruby, я могу скомпилировать 1.9 на своем компьютере без его установки и запустить тестовую программу.

Вот что я вижу:

  1. Кажется, что Ruby "зависает" (вы не можете его прервать, и он не выходит сам по себе).
  2. top показывает, что ruby ​​работает на 100% CPU
  3. strace не показывает вывод, когда он переходит в режим 100% CPU.

Из этого очевидно, что Ruby заходит в бесконечный цикл.И, взглянув на each_byte в io.c и добавив 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, и из-за этого вышеприведенный цикл становится бесполезным.*

И вот исправление:

Измените flush_before_seek в io.c, чтобы оно выглядело так:

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 без необходимости.Вызов io_unread в цикле each_byte - вот что запутывает.Пропуск непрочитанного заставляет все работать, и все тесты для make test все еще проходят.

Во всяком случае ... это не совсем корректно, так как с f.pos произошла фундаментальная ошибка.Это просто обходной путь ... но он решает вышеуказанную проблему, тем не менее: - /

0 голосов
/ 28 июля 2011

Держу пари, что это какая-то ошибка в Ruby 1.9.2, потому что она работает нормально в 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

Работает с if и puts, если есть еще puts:

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

Он так же сильно заедает Руби, что его невозможно прервать, так что обычно это признак того, что что-то внутреннее полностью сломано.

...