[ruby-trunk - Bug #8749][Open] Readline.readline stops STDOUT?

Issue #8749 has been reported by no6v (Nobuhiro IMAI).


Bug #8749: Readline.readline stops STDOUT?

Author: no6v (Nobuhiro IMAI)
Status: Open
Priority: Normal
Assignee:
Category: ext
Target version:
ruby -v: ruby 2.1.0dev (2013-08-06 trunk 42402) [x86_64-linux]
Backport: 1.9.3: UNKNOWN, 2.0.0: UNKNOWN

=begin
r42402 $B$G!“0J2<$N$h$&$J%9%/%j%W%H$N5sF0$,JQ$o$C$F$$$^$9!#(B
(Enter $B$r2!$9$+$I$&$+$O4X78$J$/$F!”(B)Readline.readline
$B$r<B9TCf$KI8=`=PNO$X$N=PNO$,=PMh$J$/$J$C$F$$$k$h$&$K8+$($^$9!#(B

$ cat rl.rb
require “readline”

th = Thread.new do
p Readline.readline("> ")
end

loop do
p :hi
sleep 2
break unless th.alive?
end
$ ruby -v rl.rb
ruby 2.1.0dev (2013-08-06 trunk 42401) [x86_64-linux]
:hi

:hi
:hi
:hi
:hi
# $B$7$P$i$/J|CV$7$F$3$3$G(B Enter
“”
$ ruby -v rl.rb
$ /tmp/ruby/bin/ruby -v /tmp/r.rb
ruby 2.1.0dev (2013-08-06 trunk 42402) [x86_64-linux]
:hi
# $B$7$P$i$/J|CV$7$F$3$3$G(B Enter
“”
$
=end

2013/8/7 no6v (Nobuhiro IMAI) [email protected]:

Bug #8749: Readline.readline stops STDOUT?
Bug #8749: Readline.readline stops STDOUT? - Ruby master - Ruby Issue Tracking System

r42402 $B$G!“0J2<$N$h$&$J%9%/%j%W%H$N5sF0$,JQ$o$C$F$$$^$9!#(B
(Enter $B$r2!$9$+$I$&$+$O4X78$J$/$F!”(B)Readline.readline
$B$r<B9TCf$KI8=`=PNO$X$N=PNO$,=PMh$J$/$J$C$F$$$k$h$&$K8+$($^$9!#(B

$B$&$%$`!#(BReadline.readline $B$,(B GVL
$B$r3NJ]$7$?$^$^%V%m%C%/$7$F$7$^$C$F!"(B
$B%a%$%s%9%l%C%I$,?J$^$J$/$J$C$F$$$k$h$&$G$9!#(B

$B$H$j$"$($:$b$H$N%3!<%I$r$J$k$Y$/J]B8$9$k$J$i$3$&$+$J$!!#(B

% svn diff --diff-cmd diff -x ‘-u -p’
Index: ext/readline/readline.c

— ext/readline/readline.c (revision 42429)
+++ ext/readline/readline.c (working copy)
@@ -127,6 +127,7 @@ static char **readline_attempted_complet
*/

static VALUE readline_instream;
+static int readline_infd = -1;
static VALUE readline_outstream;

#if defined HAVE_RL_GETC_FUNCTION
@@ -143,7 +144,7 @@ readline_getc(FILE *input)
VALUE c;
if (!readline_instream) return rl_getc(input);
GetOpenFile(readline_instream, ifp);

  • if (rl_instream != ifp->stdio_file) return rl_getc(input);
  • if (fileno(rl_instream) != readline_infd) return rl_getc(input);
    #if defined(_WIN32)
    {
    INPUT_RECORD ir;
    @@ -398,6 +399,7 @@ readline_readline(int argc, VALUE *argv,
    rl_instream = NULL;
    }
    readline_instream = Qfalse;
  •        readline_infd = -1;
           rb_raise(rb_eIOError, "closed stdin");
       }
    
    }
    @@ -467,6 +469,7 @@ clear_rl_instream(void)
    }
    }
    readline_instream = Qfalse;
  •    readline_infd = -1;
       rl_instream = NULL;
    
    }
    }
    @@ -504,6 +507,7 @@ readline_s_set_input(VALUE self, VALUE i
    }
    rl_instream = f;
    readline_instream = input;
  •    readline_infd = fd;
    
    }
    return input;
    }

$B$G$b!“(Breadline_getc $B$G$OC1$K(B GVL $B$r30$7$F(B rl_getc
$B$r8F$Y$P$$$$$N$G$O$J$$$+!”$H$$$&5$$b$7$^$9!#(B

2013$BG/(B8$B7n(B8$BF|(B 8:05 Tanaka A. [email protected]:

$B$G$b!“(Breadline_getc $B$G$OC1$K(B GVL $B$r30$7$F(B rl_getc
$B$r8F$Y$P$$$$$N$G$O$J$$$+!”$H$$$&5$$b$7$^$9!#(B

$B$3$&$9$k$J$i!"$3$&$G$9$+$M$'!#(B

% svn diff --diff-cmd diff -x ‘-u -p’
Index: ext/readline/readline.c

— ext/readline/readline.c (revision 42429)
+++ ext/readline/readline.c (working copy)
@@ -35,6 +35,7 @@

#include “ruby/ruby.h”
#include “ruby/io.h”
+#include “ruby/thread.h”

#ifdef HAVE_UNISTD_H
#include <unistd.h>
@@ -135,35 +136,35 @@ static VALUE readline_outstream;
#define rl_getc(f) EOF
#endif

-static int readline_getc(FILE *);
+struct getc_struct {

  • FILE *input;
  • int ret;
    +};

static int
-readline_getc(FILE *input)
+getc_body(FILE *input)
{

  • rb_io_t *ifp = 0;
  • VALUE c;
  • if (!readline_instream) return rl_getc(input);
  • GetOpenFile(readline_instream, ifp);
  • if (rl_instream != ifp->stdio_file) return rl_getc(input);
    #if defined(_WIN32)
  • int fd = fileno(input);
    {
    INPUT_RECORD ir;
    int n;
    static int prior_key = ‘0’;
    for (;:wink: {
    if (prior_key > 0xff) {
  •            prior_key = rl_getc(ifp->stdio_file);
    
  •            prior_key = rl_getc(input);
               return prior_key;
           }
    
  •        if (PeekConsoleInput((HANDLE)_get_osfhandle(ifp->fd),
    

&ir, 1, &n)) {

  •        if (PeekConsoleInput((HANDLE)_get_osfhandle(fd), &ir, 1, 
    

&n)) {
if (n == 1) {
if (ir.EventType == KEY_EVENT &&
ir.Event.KeyEvent.bKeyDown) {

  •                    prior_key = rl_getc(ifp->stdio_file);
    
  •                    prior_key = rl_getc(input);
                       return prior_key;
                   } else {
    

ReadConsoleInput((HANDLE)_get_osfhandle(ifp->fd), &ir, 1, &n);

  •                    ReadConsoleInput((HANDLE)_get_osfhandle(fd),
    

&ir, 1, &n);
}
} else {

  •                HANDLE h = (HANDLE)_get_osfhandle(ifp->fd);
    
  •                HANDLE h = (HANDLE)_get_osfhandle(fd);
                   rb_w32_wait_events(&h, 1, INFINITE);
               }
           } else {
    

@@ -172,21 +173,27 @@ readline_getc(FILE *input)
}
}
#endif

  • c = rb_io_getbyte(readline_instream);
  • if (NIL_P(c)) return EOF;
    -#ifdef ESC
  • if (c == INT2FIX(ESC) &&
  •    RL_ISSTATE(RL_STATE_ISEARCH) && /* isn't needed in other 
    

states? */

  •    rb_io_read_pending(ifp)) {
    
  •    int meta = 0;
    
  •    c = rb_io_getbyte(readline_instream);
    
  •    if (FIXNUM_P(c) && isascii(FIX2INT(c))) meta = 1;
    
  •    rb_io_ungetbyte(readline_instream, c);
    
  •    if (meta) rl_execute_next(ESC);
    
  •    return ESC;
    
  • }
    -#endif
  • return FIX2INT(c);
  • return rl_getc(input);
    +}

+static void *
+getc_func(void *data1)
+{

  • struct getc_struct *p = data1;
  • FILE *input = p->input;
  • p->ret = getc_body(input);
  • return NULL;
    +}

+static int readline_getc(FILE *);
+static int
+readline_getc(FILE *input)
+{

  • struct getc_struct data;
  • data.input = input;
  • data.ret = -1;
  • rb_thread_call_without_gvl(getc_func, &data, RUBY_UBF_IO, NULL);
  • return data.ret;
    }
    #elif defined HAVE_RL_EVENT_HOOK
    #define BUSY_WAIT 0

arton $B$5$s$,=q$$$?(B Windows
$BMQ$N%3!<%I$,I,MW$+$I$&$+$O$h$/$o$+$i$J$$$N$G$9$,!“(B
$B$$$A$*$&;D$7$F$”$j$^$9!#%F%9%H$O$7$F$$$^$;$s!#(B

Issue #8749 has been updated by akr (Akira T.).

File readline-release-gvl.patch added

何通か書いたメールが redmine に登録されていないので redmine で書きます。
(メールは (化けてますが) [ruby-trunk - Bug #8749][Open] Readline.readline stops STDOUT? - Development - Ruby-Forum で見れます。)

読み込みでブロックしている時に GVL を解放していなくて他のスレッドが動けないようです。

readline_getc は Ruby 1.8 時代の名残りか、
Ruby の IO 読み込み関数 (rb_io_getbyte) を使うことによって
読み込みでブロックしている最中も他のスレッドを動かせるようにしていますが、
それには IO オブジェクトが必要なため、IO オブジェクトがないときは無理だとか
中途半端です。

その判断のところが r42402 で変化してしまって、他のスレッドが動けなくなっていた
のですが、Ruby 1.9 以降では、GVL を外して読み込めばブロック中に
他のスレッドは動けるのでそうするのがいいんじゃないでしょうか。

readline-release-gvl.patch みたいなのはどうですかね。

なお、シグナルがきたときに
rb_thread_call_with_gvl 経由で rb_thread_check_ints を呼んでいますが、
rb_thread_interrupted を使うと設定した trap が即座には起動しなかったためです。
これは私の想定する rb_thread_interrupted の動作と違うんですが、私の理解と
rb_thread_interrupted のどちらが間違っているのかは分かりません。


Bug #8749: Readline.readline stops STDOUT?

Author: no6v (Nobuhiro IMAI)
Status: Open
Priority: Normal
Assignee:
Category: ext
Target version:
ruby -v: ruby 2.1.0dev (2013-08-06 trunk 42402) [x86_64-linux]
Backport: 1.9.3: UNKNOWN, 2.0.0: UNKNOWN

=begin
r42402 で、以下のようなスクリプトの挙動が変わっています。
(Enter を押すかどうかは関係なくて、)Readline.readline を実行中に標準出力への出力が出来なくなっているように見えます。

$ cat rl.rb
require “readline”

th = Thread.new do
p Readline.readline("> ")
end

loop do
p :hi
sleep 2
break unless th.alive?
end
$ ruby -v rl.rb
ruby 2.1.0dev (2013-08-06 trunk 42401) [x86_64-linux]
:hi

:hi
:hi
:hi
:hi
# しばらく放置してここで Enter
“”
$ ruby -v rl.rb
$ /tmp/ruby/bin/ruby -v /tmp/r.rb
ruby 2.1.0dev (2013-08-06 trunk 42402) [x86_64-linux]
:hi
# しばらく放置してここで Enter
“”
$
=end

Issue #8749 has been updated by kouji (Kouji T.).

Assignee set to kouji (Kouji T.)


Bug #8749: Readline.readline stops STDOUT?

Author: no6v (Nobuhiro IMAI)
Status: Open
Priority: Normal
Assignee: kouji (Kouji T.)
Category: ext
Target version:
ruby -v: ruby 2.1.0dev (2013-08-06 trunk 42402) [x86_64-linux]
Backport: 1.9.3: UNKNOWN, 2.0.0: UNKNOWN

=begin
r42402 で、以下のようなスクリプトの挙動が変わっています。
(Enter を押すかどうかは関係なくて、)Readline.readline を実行中に標準出力への出力が出来なくなっているように見えます。

$ cat rl.rb
require “readline”

th = Thread.new do
p Readline.readline("> ")
end

loop do
p :hi
sleep 2
break unless th.alive?
end
$ ruby -v rl.rb
ruby 2.1.0dev (2013-08-06 trunk 42401) [x86_64-linux]
:hi

:hi
:hi
:hi
:hi
# しばらく放置してここで Enter
“”
$ ruby -v rl.rb
$ /tmp/ruby/bin/ruby -v /tmp/r.rb
ruby 2.1.0dev (2013-08-06 trunk 42402) [x86_64-linux]
:hi
# しばらく放置してここで Enter
“”
$
=end

Issue #8749 has been updated by no6v (Nobuhiro IMAI).

読み込みでブロックしている時に GVL を解放していなくて他のスレッドが動けないようです。

ありがとうございます。出力じゃなくメインスレッド自体が止まっていたんですね。

その判断のところが r42402 で変化してしまって、他のスレッドが動けなくなっていた
のですが、Ruby 1.9 以降では、GVL を外して読み込めばブロック中に
他のスレッドは動けるのでそうするのがいいんじゃないでしょうか。

readline-release-gvl.patch みたいなのはどうですかね。

今回問題にした挙動は r42525 で一旦直って r42527 でまた壊れはしたものの
r42528 でまた直っているようです。これはたまたまでしょうか。

もちろん attachment:readline-release-gvl.patch を当てても動きます。
(コンパイル時に以下の警告が出ました)

compiling …/…/…/…/git/ruby/ext/readline/readline.c
…/…/…/…/git/ruby/ext/readline/readline.c: In function ‘check_ints’:
…/…/…/…/git/ruby/ext/readline/readline.c:148:1: warning: no return
statement in function returning non-void [-Wreturn-type]
}
^


Bug #8749: Readline.readline stops STDOUT?

Author: no6v (Nobuhiro IMAI)
Status: Open
Priority: Normal
Assignee: kouji (Kouji T.)
Category: ext
Target version:
ruby -v: ruby 2.1.0dev (2013-08-06 trunk 42402) [x86_64-linux]
Backport: 1.9.3: UNKNOWN, 2.0.0: UNKNOWN

=begin
r42402 で、以下のようなスクリプトの挙動が変わっています。
(Enter を押すかどうかは関係なくて、)Readline.readline を実行中に標準出力への出力が出来なくなっているように見えます。

$ cat rl.rb
require “readline”

th = Thread.new do
p Readline.readline("> ")
end

loop do
p :hi
sleep 2
break unless th.alive?
end
$ ruby -v rl.rb
ruby 2.1.0dev (2013-08-06 trunk 42401) [x86_64-linux]
:hi

:hi
:hi
:hi
:hi
# しばらく放置してここで Enter
“”
$ ruby -v rl.rb
$ /tmp/ruby/bin/ruby -v /tmp/r.rb
ruby 2.1.0dev (2013-08-06 trunk 42402) [x86_64-linux]
:hi
# しばらく放置してここで Enter
“”
$
=end

2013/8/8 Tanaka A. [email protected]:

2013$BG/(B8$B7n(B8$BF|(B 8:05 Tanaka A. [email protected]:

$B$G$b!“(Breadline_getc $B$G$OC1$K(B GVL $B$r30$7$F(B rl_getc
$B$r8F$Y$P$$$$$N$G$O$J$$$+!”$H$$$&5$$b$7$^$9!#(B

$B$3$&$9$k$J$i!"$3$&$G$9$+$M$'!#(B

$B$*$C$H!"$3$l$@$H(B ^C $B$,8z$-$^$;$s$G$7$?!#(B
$B$3$&$+$J!#(B

% svn diff --diff-cmd diff -x ‘-u -p’
Index: ext/readline/readline.c

— ext/readline/readline.c (revision 42432)
+++ ext/readline/readline.c (working copy)
@@ -35,6 +35,7 @@

#include “ruby/ruby.h”
#include “ruby/io.h”
+#include “ruby/thread.h”

#ifdef HAVE_UNISTD_H
#include <unistd.h>
@@ -135,15 +136,19 @@ static VALUE readline_outstream;
#define rl_getc(f) EOF
#endif

-static int readline_getc(FILE *);
+struct getc_struct {

  • FILE *input;
  • int ret;
    +};

static int
-readline_getc(FILE *input)
+getc_body(FILE *input)
{

  • rb_io_t *ifp = 0;
  • VALUE c;
  • if (!readline_instream) return rl_getc(input);
  • GetOpenFile(readline_instream, ifp);
  • if (rl_instream != ifp->stdio_file) return rl_getc(input);
  • int fd = fileno(input);
  • char ch;
  • ssize_t ss;
  • int ret;

#if defined(_WIN32)
{
INPUT_RECORD ir;
@@ -151,19 +156,19 @@ readline_getc(FILE *input)
static int prior_key = ‘0’;
for (;:wink: {
if (prior_key > 0xff) {

  •            prior_key = rl_getc(ifp->stdio_file);
    
  •            prior_key = rl_getc(input);
               return prior_key;
           }
    
  •        if (PeekConsoleInput((HANDLE)_get_osfhandle(ifp->fd),
    

&ir, 1, &n)) {

  •        if (PeekConsoleInput((HANDLE)_get_osfhandle(fd), &ir, 1, 
    

&n)) {
if (n == 1) {
if (ir.EventType == KEY_EVENT &&
ir.Event.KeyEvent.bKeyDown) {

  •                    prior_key = rl_getc(ifp->stdio_file);
    
  •                    prior_key = rl_getc(input);
                       return prior_key;
                   } else {
    

ReadConsoleInput((HANDLE)_get_osfhandle(ifp->fd), &ir, 1, &n);

  •                    ReadConsoleInput((HANDLE)_get_osfhandle(fd),
    

&ir, 1, &n);
}
} else {

  •                HANDLE h = (HANDLE)_get_osfhandle(ifp->fd);
    
  •                HANDLE h = (HANDLE)_get_osfhandle(fd);
                   rb_w32_wait_events(&h, 1, INFINITE);
               }
           } else {
    

@@ -172,21 +177,56 @@ readline_getc(FILE *input)
}
}
#endif

  • c = rb_io_getbyte(readline_instream);
  • if (NIL_P(c)) return EOF;
    -#ifdef ESC
  • if (c == INT2FIX(ESC) &&
  •    RL_ISSTATE(RL_STATE_ISEARCH) && /* isn't needed in other 
    

states? */

  •    rb_io_read_pending(ifp)) {
    
  •    int meta = 0;
    
  •    c = rb_io_getbyte(readline_instream);
    
  •    if (FIXNUM_P(c) && isascii(FIX2INT(c))) meta = 1;
    
  •    rb_io_ungetbyte(readline_instream, c);
    
  •    if (meta) rl_execute_next(ESC);
    
  •    return ESC;
    
  • }
  • again:
  • ss = read(fd, &ch, 1);
  • if (ss == 0)
  •    return EOF;
    
  • if (ss == -1) {
  •    if (errno == EINTR)
    
  •        return EOF;
    
  •    if (errno == EWOULDBLOCK || errno == EAGAIN) {
    

+#ifdef HAVE_POLL

  •        struct pollfd pfd;
    
  •        pfd.fd = fd;
    
  •        pfd.events = POLLIN;
    
  •        pfd.revents = 0;
    
  •        ret = poll(&pfd, 1, -1);
    
  •        if (ret < 0)
    
  •            return EOF;
    

+#else

  •        rb_fdset_t rfds;
    
  •        rb_fd_init(&rfds);
    
  •        rb_fd_set(fd, &rfds);
    
  •        ret = rb_fd_select(fd+1, &rfds, NULL, NULL, NULL);
    
  •        rb_fd_term(&rfds);
    
  •        if (ret < 0)
    
  •            return EOF;
    

#endif

  • return FIX2INT(c);
  •        goto again;
    
  •    }
    
  • }
  • return (unsigned char)ch;
    +}

+static void *
+getc_func(void *data1)
+{

  • struct getc_struct *p = data1;
  • FILE *input = p->input;
  • p->ret = getc_body(input);
  • return NULL;
    +}

+static int readline_getc(FILE *);
+static int
+readline_getc(FILE *input)
+{

  • struct getc_struct data;
  • data.input = input;
  • data.ret = -1;
  • rb_thread_call_without_gvl(getc_func, &data, RUBY_UBF_IO, NULL);
  • return data.ret;
    }
    #elif defined HAVE_RL_EVENT_HOOK
    #define BUSY_WAIT 0

Issue #8749 has been updated by akr (Akira T.).

この [ruby-dev:47033] [Bug #8749] と
[ruby-core:57951] [Bug #9040] の件をあわせて修正する変更を入れました。

パッチを出してから 2ヵ月くらい経っていますが反応が無く、
[ruby-core:57951] [Bug #9040] の件も見つかったし、
Ruby 2.1.0 のリリース直前に変更するのもよくないとおもうので。

問題があったら言ってください。


Bug #8749: Readline.readline stops STDOUT?

Author: no6v (Nobuhiro IMAI)
Status: Closed
Priority: Normal
Assignee: kouji (Kouji T.)
Category: ext
Target version:
ruby -v: ruby 2.1.0dev (2013-08-06 trunk 42402) [x86_64-linux]
Backport: 1.9.3: UNKNOWN, 2.0.0: UNKNOWN

=begin
r42402 で、以下のようなスクリプトの挙動が変わっています。
(Enter を押すかどうかは関係なくて、)Readline.readline を実行中に標準出力への出力が出来なくなっているように見えます。

$ cat rl.rb
require “readline”

th = Thread.new do
p Readline.readline("> ")
end

loop do
p :hi
sleep 2
break unless th.alive?
end
$ ruby -v rl.rb
ruby 2.1.0dev (2013-08-06 trunk 42401) [x86_64-linux]
:hi

:hi
:hi
:hi
:hi
# しばらく放置してここで Enter
“”
$ ruby -v rl.rb
$ /tmp/ruby/bin/ruby -v /tmp/r.rb
ruby 2.1.0dev (2013-08-06 trunk 42402) [x86_64-linux]
:hi
# しばらく放置してここで Enter
“”
$
=end

Issue #8749 has been updated by akr (Akira T.).

File readline-release-gvl-2.patch added

no6v (Nobuhiro IMAI) wrote:

今回問題にした挙動は r42525 で一旦直って r42527 でまた壊れはしたものの
r42528 でまた直っているようです。これはたまたまでしょうか。

readline_getc で、rl_getc を呼び出すか、rb_io_getbyte を呼び出すかという条件判断が問題で、
前者なら GVL を離さないので他のスレッドは動けず、後者なら動けます。

r42525 で直ったのは、ifp->stdio_file に代入したので、readline_getc 内の
if (rl_instream != ifp->stdio_file) return rl_getc(input);
という条件が成立しなくなったためでしょう。
しかし、[ruby-dev:47608] で述べたように、ifp->stdio_file に readline 側で
作った FILE 構造体を代入すると、IO#close で解放されてしまって、ひいては SEGV になる可能性が出てきます。

r42527 は if 文で上記の ifp->stdio_file への代入をガードしていますが、
[ruby-dev:47609] で述べたように、その条件は決して成り立たないので、実質、
r42525 の revert のようなものです。

r42528 は
if (rl_instream != ifp->stdio_file) return rl_getc(input);
という文を削除していて、そのため、rl_getc じゃなくて下のほうに制御が行って、
rb_io_getbyte を呼び出すようになったのでしょう。
ここで rb_io_getbyte は readline_instream という IO オブジェクトから読むので、
それが input という FILE 構造体と対応しているのかという疑問があるわけで、
だからこそ対応していなかったら input から読むという処理が入っているのだと思いますが、
どういう理由かは知りませんが、消してしまっているようです。

もちろん attachment:readline-release-gvl.patch を当てても動きます。
(コンパイル時に以下の警告が出ました)

compiling …/…/…/…/git/ruby/ext/readline/readline.c
…/…/…/…/git/ruby/ext/readline/readline.c: In function ‘check_ints’:
…/…/…/…/git/ruby/ext/readline/readline.c:148:1: warning: no return statement
in function returning non-void [-Wreturn-type]
}
^

おっと、return NULL; を忘れていました。
つけたのを readline-release-gvl-2.patch として添付します。


Bug #8749: Readline.readline stops STDOUT?

Author: no6v (Nobuhiro IMAI)
Status: Open
Priority: Normal
Assignee: kouji (Kouji T.)
Category: ext
Target version:
ruby -v: ruby 2.1.0dev (2013-08-06 trunk 42402) [x86_64-linux]
Backport: 1.9.3: UNKNOWN, 2.0.0: UNKNOWN

=begin
r42402 で、以下のようなスクリプトの挙動が変わっています。
(Enter を押すかどうかは関係なくて、)Readline.readline を実行中に標準出力への出力が出来なくなっているように見えます。

$ cat rl.rb
require “readline”

th = Thread.new do
p Readline.readline("> ")
end

loop do
p :hi
sleep 2
break unless th.alive?
end
$ ruby -v rl.rb
ruby 2.1.0dev (2013-08-06 trunk 42401) [x86_64-linux]
:hi

:hi
:hi
:hi
:hi
# しばらく放置してここで Enter
“”
$ ruby -v rl.rb
$ /tmp/ruby/bin/ruby -v /tmp/r.rb
ruby 2.1.0dev (2013-08-06 trunk 42402) [x86_64-linux]
:hi
# しばらく放置してここで Enter
“”
$
=end