スレッドで遊んでたらRubyが落ちた

gcgraph(http://github.com/miura1729/gcgraph/tree/master)を拡張してスレッド毎に今どこを実行しているかグラフ表示することをたくらんでいます。そのためのテストコードを書いてみました。

ruby -r threadprof.rb thread_test.rb

とするとこんな感じで1秒間に1回表示されます。

Thread List 
#<Thread:0x1002fe58> : thread_test.rb:9
#<Thread:0x100300c4> : thread_test.rb:7
#<Thread:0x1002f818> : thread_test.rb:7
#<Thread:0x1002fb74> : thread_test.rb:1
#<Thread:0x1002f048> : thread_test.rb:7
#<Thread:0x1002e198> : thread_test.rb:3
#<Thread:0x10030844> : Profiler Thread
#<Thread:0x1002e6ac> : thread_test.rb:7
#<Thread:0x1003e50c> : thread_test.rb:6
#<Thread:0x1002f458> : thread_test.rb:3
#<Thread:0x1002ebc0> : thread_test.rb:7
#<Thread:0x1002dc48> : thread_test.rb:5

ところが、たまにコアはいちゃいます。

[BUG] Segmentation fault
ruby 1.9.0 (2008-08-28 revision 15675) [i386-cygwin]

-- control frame ----------
c:0030 p:0078 s:0149 b:0148 l:001b1c d:001b1c METHOD thread_test.rb:9
c:0029 p:0070 s:0143 b:0142 l:000804 d:000804 METHOD thread_test.rb:7
c:0028 p:0070 s:0137 b:0136 l:000135 d:000135 METHOD thread_test.rb:7
c:0027 p:0053 s:0131 b:0131 l:000130 d:000130 METHOD thread_test.rb:7
c:0026 p:0053 s:0126 b:0126 l:000125 d:000125 METHOD thread_test.rb:7
c:0025 p:0053 s:0121 b:0121 l:000120 d:000120 METHOD thread_test.rb:7
c:0024 p:0053 s:0116 b:0116 l:000115 d:000115 METHOD thread_test.rb:7
c:0023 p:0053 s:0111 b:0111 l:000110 d:000110 METHOD thread_test.rb:7
c:0022 p:0053 s:0106 b:0106 l:000105 d:000105 METHOD thread_test.rb:7
c:0021 p:0053 s:0101 b:0101 l:000100 d:000100 METHOD thread_test.rb:7
c:0020 p:0070 s:0096 b:0095 l:000094 d:000094 METHOD thread_test.rb:7
c:0019 p:0053 s:0090 b:0090 l:000089 d:000089 METHOD thread_test.rb:7
c:0018 p:0053 s:0085 b:0085 l:000084 d:000084 METHOD thread_test.rb:7
c:0017 p:0070 s:0080 b:0079 l:000078 d:000078 METHOD thread_test.rb:7
c:0016 p:0053 s:0074 b:0074 l:000073 d:000073 METHOD thread_test.rb:7
c:0015 p:0053 s:0069 b:0069 l:000068 d:000068 METHOD thread_test.rb:7
c:0014 p:0053 s:0064 b:0064 l:000063 d:000063 METHOD thread_test.rb:7
c:0013 p:0070 s:0059 b:0058 l:000057 d:000057 METHOD thread_test.rb:7
c:0012 p:0053 s:0053 b:0053 l:000052 d:000052 METHOD thread_test.rb:7
c:0011 p:0053 s:0048 b:0048 l:000047 d:000047 METHOD thread_test.rb:7
c:0010 p:0053 s:0043 b:0043 l:000042 d:000042 METHOD thread_test.rb:7
c:0009 p:0053 s:0038 b:0038 l:000037 d:000037 METHOD thread_test.rb:7
c:0008 p:0053 s:0033 b:0033 l:000032 d:000032 METHOD thread_test.rb:7
c:0007 p:0070 s:0028 b:0027 l:000026 d:000026 METHOD thread_test.rb:7
c:0006 p:0070 s:0022 b:0021 l:000020 d:000020 METHOD thread_test.rb:7
c:0005 p:0070 s:0016 b:0015 l:000014 d:000014 METHOD thread_test.rb:7
c:0004 p:0053 s:0010 b:0010 l:000009 d:000009 METHOD thread_test.rb:7
c:0003 p:0021 s:0005 b:0004 l:001314 d:000003 BLOCK  thread_test.rb:13
c:0002 p:---- s:0004 b:0004 l:000003 d:000003 FINISH 
c:0001 p:---- s:0002 b:0002 l:000001 d:000001 TOP    
---------------------------

ruby-devに投げるべきですが、もうちょっとプログラムを切り出さないと顰蹙ですね。

とりあえず、落ちるけどプログラムを晒します。作っていて、Mutexとか条件変数とか全然理解していないことが分かりました。

本体のthreadprof.rbです

RubyVM::InstructionSequence.compile_option = {
  :trace_instruction => true,
  :specialized_instruction => false
}

Thread.new do
  while true do
    sleep(1)
    STDERR.print "\nThread List \n"
    profthread = Thread.current
    tinfo = {}
    tinfo[profthread] = "Profiler Thread"
    Thread.list.each do |th|
      if th != profthread then
        if th.status == "run" then
          tinfo[th] = nil
        else
          tinfo[th] = th.status
        end
      end
    end

    handler = lambda {|event, file, line, id, binding, klass|
      th = Thread.current
      if tinfo[th] == nil then
        tinfo[th] = "#{file}:#{line}"
      end
      if profthread.stop? then
        profthread.wakeup
      else
        Thread.pass
      end
    }
    set_trace_func handler

    tinfo.each do |th, value|
      while value == nil do
        Thread.stop
      end
    end
    set_trace_func nil
    
    tinfo.each do |th, value|
      STDERR.print "#{th} : #{value}\n"
    end
  end
end

サンプルプログラムのthread_test.rbです

def fib(x)
  if x < 2 then
    1
  else
    a = 1
    $a = x
    fib(x - 1) + fib(x - 2)
  end
end

(1..10).each do |i|
  Thread.new {
    p fib(i + 25)
  }
end
fib(40)