[#44925] [Backport93 - Backport #5702][Open] backport r33935 — Yusuke Endoh <mame@...>

19 messages 2011/12/03

[#44940] Re: [ruby-cvs:41134] naruse:r33956 (trunk): Comment out tests which fails with GDBM-DBM compat mode. — Tanaka Akira <akr@...>

2011/12/6 <naruse@ruby-lang.org>:

9 messages 2011/12/05
[#44941] Re: [ruby-cvs:41134] naruse:r33956 (trunk): Comment out tests which fails with GDBM-DBM compat mode. — KOSAKI Motohiro <kosaki.motohiro@...> 2011/12/05

2011年12月5日16:56 Tanaka Akira <akr@fsij.org>:

[#44942] Re: [ruby-cvs:41134] naruse:r33956 (trunk): Comment out tests which fails with GDBM-DBM compat mode. — KOSAKI Motohiro <kosaki.motohiro@...> 2011/12/05

> おかしいな。gdbmは勝手にcreateフラグを立ててしまうので当該2つの

[#44985] [ruby-trunk - Bug #5757][Open] main threadがreadやselectで待っていると、^C でなかなか死なない — Yui NARUSE <naruse@...>

12 messages 2011/12/13

[#45021] [ruby-trunk - Bug #5786][Open] LoadError: cannot load such file -- openssl — Kazuhiro NISHIYAMA <redmine@...>

11 messages 2011/12/21

[#45057] [ruby-trunk - Feature #5820][Assigned] Merge Onigmo to Ruby 2.0 — Yui NARUSE <naruse@...>

21 messages 2011/12/28

[ruby-dev:45014] [ruby-trunk - Bug #5768] TestRequire#test_race_exceptionで競合するケースがまだある

From: Yui NARUSE <naruse@...>
Date: 2011-12-20 07:55:40 UTC
List: ruby-dev #45014
Issue #5768 has been updated by Yui NARUSE.


Nobuyoshi Nakada wrote:
>  rb_barrier_release/rb_barrier_destroyが呼ばれた時点ではmutexがロックさ
>  れていることが前提ですので、後半のBarrier部分の変更だけで充分な気がしま
>  す。

その前提は成立しません。

このテストにおいて、t1 は t2.stop? == true を待ってから、例外によって require を抜けます。
言い換えると、t2 が rb_barrier_wait 内の rb_mutex_lock 内の native_cond_wait に到達するのを待ってから、
例外によって require を例外を抜けたいと思っています。
最低限のラインとしては、mutex->cond_waiting++ されてから抜けないといけません。

さて、t2 における rb_mutex_lock ですが、
thread.c の 3481行目に th->status = THREAD_STOPPED_FOREVER という行があります。
これ以降、Thread#stop?はtrueを返すようになりますが、まだ GVL にとって他のスレッドは動けないので大丈夫です。
そこから10行ほど下ると、GVL_UNLOCK_BEGIN();という行があります。
ここから他のスレッドが動けるようになります、まだこのスレッドは STOP してないのに。
t1 がこの状況で動くと、例外を投げて require を抜け、load_unlock が呼ばれます。
つまりこの時点で前提は崩れます。
そして rb_barrier_release を呼び、まだ 0 のままのmutex->cond_waiting を見て Qfalse を返すため、
loading_tbl から当該パスは削除されてしまいます。
一方、t1 はそれと平行して lock_funcに入り、やっとmutex->cond_waiting++します。
30行ほど下って、native_cond_waitで待ちますが、上記のケースの場合この時にはもう手遅れになっています。

まとめると、GVL_UNLOCK_BEGINから、mutex->cond_waiting++の間に、他のスレッドが動いて、
mutex->cond_waitingを見ると、0のままであり、待ってる人がいないと思うので、
待ち人用の何かを破壊したりすると、待ってた人が発狂する。
具体的には、load.c の load_unlock の rb_barrier_destroy/rb_barrier_destroy と、st_delete あたり、というわけです。

>  > diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb
>  > index 9186a6f..f1d8d12 100644
>  > --- a/test/ruby/test_require.rb
>  > +++ b/test/ruby/test_require.rb
>  > @@ -350,9 +350,18 @@ class TestRequire < Test::Unit::TestCase
>  >      path = tmp.path
>  >      tmp.print <<-EOS
>  >  TestRequire.scratch << :pre
>  > -Thread.pass until t2 = TestRequire.scratch[1]
>  > +TestRequire.scratch << Thread.current
>  > +Thread.pass until t2 = TestRequire.scratch[2]
>  >  Thread.pass until t2.stop?
>  > -open(__FILE__, "w") {|f| f.puts "TestRequire.scratch << :post"}
>  > +open(__FILE__, "w") do |f|
>  > +  f.puts "t1, t2 = TestRequire.scratch[1, 2]"
>  > +  f.puts "if Thread.current == t2"
>  > +  f.puts "  TestRequire.scratch << :post"
>  > +  f.puts "  until t1.stop?"
>  > +  f.puts "    Thread.pass"
>  > +  f.puts "  end"
>  > +  f.puts "end"
>  > +end
>  >  raise "con1"
>  
>  この変更ではt2でロードされることを想定していますが、最初のrequireが例外
>  で中断したあとにt1,t2のどちらが実際にロードすることになるかは不定のはず
>  です。

あぁ、なるほど。なんで :post が含まれないケースがあるのかなぁと思っていました。
----------------------------------------
Bug #5768: TestRequire#test_race_exceptionで競合するケースがまだある
https://bugs.ruby-lang.org/issues/5768

Author: Yui NARUSE
Status: Assigned
Priority: Normal
Assignee: Motohiro KOSAKI
Category: core
Target version: 2.0.0
ruby -v: -


まだrequireで競合するケースが残っています。
現在のテストだと確率的にしか起きませんが、以下の通り変更すると確実に起きるようになります。

diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb
index 9186a6f..262a5ef 100644
--- a/test/ruby/test_require.rb
+++ b/test/ruby/test_require.rb
@@ -352,7 +352,7 @@ class TestRequire < Test::Unit::TestCase
 TestRequire.scratch << :pre
 Thread.pass until t2 = TestRequire.scratch[1]
 Thread.pass until t2.stop?
-open(__FILE__, "w") {|f| f.puts "TestRequire.scratch << :post"}
+open(__FILE__, "w") {|f| f.puts "TestRequire.scratch << :post"; f.puts "t1,t2=TestRequire.scratch[1, 2];if Thread.current == t2; Thread.pass until t1.stopped?; end"}
 raise "con1"
     EOS
     tmp.close
@@ -364,6 +364,7 @@ raise "con1"
     t2_res = nil
 
     t1 = Thread.new do
+      scratch << t1
       begin
         require(path)
       rescue RuntimeError
@@ -389,8 +390,8 @@ raise "con1"
     assert_nothing_raised(ThreadError, bug5754) {t1.join}
     assert_nothing_raised(ThreadError, bug5754) {t2.join}
 
-    assert_equal(true, (t1_res ^ t2_res), bug5754)
-    assert_equal([:pre, t2, :post, :t2, :t1], scratch, bug5754)
+    assert_equal(true, (t1_res ^ t2_res), bug5754 + " t1:#{t1_res} t2:#{t2_res}")
+    assert_equal([:pre, t1, t2, :post, :t2, :t1], scratch, bug5754)
   ensure
     tmp.close(true) if tmp
   end


-- 
http://redmine.ruby-lang.org

In This Thread