From: Akinori MUSHA Date: 2001-04-07T21:10:04+09:00 Subject: [ruby-dev:12795] recursive malloc / fork deadlock / thread deadlock  添付のスクリプトで、いくつかのプラットフォームで問題が発生する ことが分かりました。  見られる現象は、さまざまで、次の三つが確認されています。 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 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!" }