添付のスクリプトで、いくつかのプラットフォームで問題が発生する
ことが分かりました。

 見られる現象は、さまざまで、次の三つが確認されています。

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!" }