From: Yusuke ENDOH Date: 2008-05-28T21:42:48+09:00 Subject: [ruby-dev:34874] Invalid read may occur when raising an exception to a thread that waits for mutex 遠藤です。 以下のようにするとハングすることがあります。 $ ./ruby -ve ' N = 10 m = [Mutex.new] m.first.lock t = Thread.new do begin m.first.lock # (1) rescue loop { } # (3) end end sleep 1 t.raise # (2) m.clear sleep 1 GC.start # (4) t.raise # (5) ' ruby 1.9.0 (2008-05-28 revision 16675) [i686-linux] ^Z 流れはこんな感じです。 - (1) でスレッド t の unblock_function が lock_interrupt に なり、mutex の解放待ちに入る。 - (2) でスレッド t の unblock_function (= lock_interrupt) が 呼ばれる。 - スレッド t の unblock_function はクリアされるべきだが、 クリアされないままスレッド t の実行は (3) に移る。 - (4) で mutex を free する (ただし環境依存) 。 - (5) でスレッド t の unblock_function (= lock_interrupt) が 呼ばれるが、lock_interrupt の中で解放済みの mutex に触る。 実際、valgrind にかけると、lock_interrupt 内で Invalid read しているのがわかります。 ==15890== Invalid read of size 4 ==15890== at 0x811AA87: lock_interrupt (thread.c:2439) ==15890== by 0x81181D9: rb_thread_interrupt (thread.c:222) ==15890== by 0x8119284: rb_thread_ready (thread.c:864) ==15890== by 0x81192EE: rb_thread_raise (thread.c:884) ==15890== by 0x811941F: thread_raise_m (thread.c:960) ==15890== by 0x810A59B: call_cfunc (vm_insnhelper.c:286) ==15890== by 0x8114F32: vm_call_cfunc (vm_insnhelper.c:376) ==15890== by 0x8114A7C: vm_call_method (vm_insnhelper.c:508) ==15890== by 0x8110F8B: vm_eval (insns.def:1048) ==15890== by 0x8115135: vm_eval_body (vm.c:1022) ==15890== by 0x811575C: rb_iseq_eval (vm.c:1226) ==15890== by 0x805A0FB: ruby_exec_node (eval.c:221) ==15890== Address 0x44B6F04 is 76 bytes inside a block of size 80 free'd ==15890== at 0x401CFA5: free (vg_replace_malloc.c:233) ==15890== by 0x805EA4E: ruby_xfree (gc.c:425) ==15890== by 0x811A899: mutex_free (thread.c:2340) ==15890== by 0x80606C4: obj_free (gc.c:1498) ==15890== by 0x8060214: gc_sweep (gc.c:1371) ==15890== by 0x8060B0C: garbage_collect (gc.c:1704) ==15890== by 0x80614AF: rb_gc (gc.c:2119) ==15890== by 0x8060B6B: rb_gc_start (gc.c:1751) ==15890== by 0x810A5B1: call_cfunc (vm_insnhelper.c:289) ==15890== by 0x8114F32: vm_call_cfunc (vm_insnhelper.c:376) ==15890== by 0x8114A7C: vm_call_method (vm_insnhelper.c:508) ==15890== by 0x8110F8B: vm_eval (insns.def:1048) unblock_function がクリアされないのは set_unblock_function 内で RUBY_VM_CHECK_INTS をしていて、そこから longjmp するせいです。 この RUBY_VM_CHECK_INTS は必要なんでしょうか。 Index: thread.c =================================================================== --- thread.c (revision 16676) +++ thread.c (working copy) @@ -197,19 +197,11 @@ set_unblock_function(rb_thread_t *th, rb_unblock_function_t *func, void *arg, rb_unblock_function_t **oldfunc, void **oldarg) { - check_ints: - RUBY_VM_CHECK_INTS(); /* check signal or so */ native_mutex_lock(&th->interrupt_lock); - if (th->interrupt_flag) { - native_mutex_unlock(&th->interrupt_lock); - goto check_ints; - } - else { - if (oldfunc) *oldfunc = th->unblock_function; - if (oldarg) *oldarg = th->unblock_function_arg; - th->unblock_function = func; - th->unblock_function_arg = arg; - } + if (oldfunc) *oldfunc = th->unblock_function; + if (oldarg) *oldarg = th->unblock_function_arg; + th->unblock_function = func; + th->unblock_function_arg = arg; native_mutex_unlock(&th->interrupt_lock); } -- Yusuke ENDOH