添付のスクリプトで、いくつかのプラットフォームで問題が発生する
ことが分かりました。
見られる現象は、さまざまで、次の三つが確認されています。
A. recursive malloc:
malloc/realloc/free が処理中に再入されてエラー。
ruby in realloc(): error: recursive call.
コアを吐く場所は毎回違うが、一例を挙げると
(gdb) where
#0 0x2817acc0 in kill () from /usr/lib/libc.so.5
#1 0x281cbe65 in abort () from /usr/lib/libc.so.5
#2 0x281ca9aa in isatty () from /usr/lib/libc.so.5
#3 0x281ca9d8 in isatty () from /usr/lib/libc.so.5
#4 0x281cbbae in realloc () from /usr/lib/libc.so.5
#5 0x8068cfd in ruby_xrealloc (ptr=0x819e000, size=12896) at gc.c:137
#6 0x806143d in rb_thread_save_context (th=0x816fc00) at eval.c:7075
#7 0x806227f in rb_thread_schedule () at eval.c:7510
#8 0x806321e in catch_timer (sig=26) at eval.c:8032
#9 0x280f961e in _thread_sig_wrapper () from /usr/lib/libc_r.so.5
#10 0x0 in ?? ()
(gdb) f 5
#5 0x8068cfd in ruby_xrealloc (ptr=0x819e000, size=12896) at gc.c:137
137 RUBY_CRITICAL(mem = realloc(ptr, size));
(gdb) list
132 rb_raise(rb_eArgError, "negative re-allocation size");
133 }
134 if (!ptr) return xmalloc(size);
135 if (size == 0) size = 1;
136 malloc_memories += size;
137 RUBY_CRITICAL(mem = realloc(ptr, size));
138 if (!mem) {
139 rb_gc();
140 RUBY_CRITICAL(mem = realloc(ptr, size));
141 if (!mem)
(gdb) f 6
#6 0x806143d in rb_thread_save_context (th=0x816fc00) at eval.c:7075
7075 REALLOC_N(th->stk_ptr, VALUE, len);
(gdb) list
7070 len = stack_length(&pos);
7071 th->stk_len = 0;
7072 th->stk_pos = (rb_gc_stack_start<pos)?rb_gc_stack_start
7073 :rb_gc_stack_start - len;
7074 if (len > th->stk_max) {
7075 REALLOC_N(th->stk_ptr, VALUE, len);
7076 th->stk_max = len;
7077 }
7078 th->stk_len = len;
7079 FLUSH_REGISTER_WINDOWS;
B. fork deadlock:
thread の中で fork したところでそのスレッドが固まる
(Ruby側からは検知できないアイドリング状態)
^Cを押すと
/usr/local/lib/ruby/1.6/thread.rb:178:in `stop': Interrupt
from /usr/local/lib/ruby/1.6/thread.rb:178:in `pop'
from /usr/local/lib/ruby/1.6/thread.rb:173:in `loop'
from /usr/local/lib/ruby/1.6/thread.rb:173:in `pop'
from /home/knu/www/thread_test.rb:60
from /home/knu/www/thread_test.rb:59:in `times'
from /home/knu/www/thread_test.rb:59
となる。
C. thread deadlock:
意図した処理は終わっていないのに、すべてのスレッドが
止まってしまい、Rubyがそれを検知して終了
deadlock 0x20000398008: 2:0 - /usr/lib/ruby/1.6/thread.rb:51
deadlock 0x200003bea50: 2:0 (main) - /usr/lib/ruby/1.6/thread.rb:163
deadlock 0x20000397b80: 2:0 - /usr/lib/ruby/1.6/thread.rb:51
/usr/lib/ruby/1.6/thread.rb:51:in `pop': Thread: deadlock (fatal)
from /usr/lib/ruby/1.6/thread.rb:170:in `loop'
from /usr/lib/ruby/1.6/thread.rb:170:in `pop'
from thread_test.rb:60
from thread_test.rb:59:in `times'
from thread_test.rb:59
各プラットフォームにて確認されている現象は、次の通りです。
FreeBSD/i386: (私)
A, B, C
Cygwin: (znz さんより)
B
Linux/alpha: (やまださんより)
C
Linux/i386, NetBSD/i386: (私)
正常終了
なお、 FreeBSD/i386 での A は、標準の malloc ライブラリでも、
dmalloc (http://dmalloc.com/) を使っても再入エラーが出るのは
同様でした。また、コンパイルおよびリンク時の pthread サポートの
有無にも関わりなく発生しています。
Ruby 自体は最新の 1.6 と最新の 1.7 で確認しています。
明らかに race condition によって起きているので、一発では再現
しないかもしれませんが、何度かやったり -j と -t のパラメータを
適当に調節することで(起こる環境では)再現できるはずです。
どうも、この問題は Thread と popen (pipe_open) の組み合わせで
起こっているようです。(*) の部分を IO::popen() にしても結果は
まったく同じですが、単に system() を呼び出すだけならば問題ない
みたいです。
というわけで、よろしくお願いします。
--
/
/__ __ Akinori.org / MUSHA.org
/ ) ) ) ) / FreeBSD.org / Ruby-lang.org
Akinori MUSHA aka / (_ / ( (__( @ iDaemons.org / and.or.jp
"We're only at home when we're on the run, on the wing, on the fly"
#!/usr/bin/env ruby
require 'thread'
require 'getopts'
getopts('g', 'j:4', 't:50')
if $OPT_g
GC.disable
end
# ntasks 個のデータを nhands 人で協力して処理する
nhands = $OPT_j.to_i
ntasks = $OPT_t.to_i
# 排他用
mutex = Mutex.new
# 完了した人は合図として queue に自分の番号を push するものとする
done = Queue.new
# データを生成
tasks = Array.new(ntasks)
s = 'aaaa'
for i in 0...ntasks
tasks[i] = s.dup
s.succ!
end
# 人を稼働
nhands.times { |handno|
Thread.start {
str = nil
# データが残っているうちは取り出して処理
loop {
# 有無のチェックから取り出しまではアトミック
mutex.synchronize {
if tasks.empty?
done.push handno
Thread.exit
end
str = tasks.shift
}
printf "%d: %3d:%3d: <== %s\n", handno, tasks.size, ntasks, str
# 大文字化 (*)
result = `echo #{str} | tr a-z A-Z`.chomp
# result = IO::popen("echo #{str} | tr a-z A-Z").read.chomp
# system("echo #{str} | tr a-z A-Z > /dev/null"); result = str.upcase
printf "%d: %3d:%3d: ==> %s\n", handno, tasks.size, ntasks, result
}
}
}
# 全員が終わるまで待つ
nhands.times { |i|
handno = done.pop
puts "#{handno}: done!"
}