ruby-dev (Japanese) list archive (unofficial mirror)
 help / color / mirror / Atom feed
* [ruby-dev:45002] [ruby-trunk - Bug #5768][Assigned] TestRequire#test_race_exceptionで競合するケースがまだある
@ 2011-12-17  1:48 Yui NARUSE
  2011-12-19  1:58 ` [ruby-dev:45006] [ruby-trunk - Bug #5768] TestRequire#test_race_exceptionで競合するケースがまだある Tomoyuki Chikanaga
                   ` (5 more replies)
  0 siblings, 6 replies; 8+ messages in thread
From: Yui NARUSE @ 2011-12-17  1:48 UTC (permalink / raw
  To: ruby developers list


Issue #5768 has been reported by Yui NARUSE.

----------------------------------------
Bug #5768: TestRequire#test_race_exceptionで競合するケースがまだある
https://bugs.ruby-lang.org/issues/5768

Author: Yui NARUSE
Status: Assigned
Priority: Normal
Assignee: Nobuyoshi Nakada
Category: 
Target version: 
ruby -v: ruby 2.0.0dev (2011-12-16 trunk 34058) [x86_64-freebsd9.0]


まだ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

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [ruby-dev:45006] [ruby-trunk - Bug #5768] TestRequire#test_race_exceptionで競合するケースがまだある
  2011-12-17  1:48 [ruby-dev:45002] [ruby-trunk - Bug #5768][Assigned] TestRequire#test_race_exceptionで競合するケースがまだある Yui NARUSE
@ 2011-12-19  1:58 ` Tomoyuki Chikanaga
  2011-12-20  4:43 ` [ruby-dev:45011] " Yui NARUSE
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Tomoyuki Chikanaga @ 2011-12-19  1:58 UTC (permalink / raw
  To: ruby developers list


Issue #5768 has been updated by Tomoyuki Chikanaga.


bug5754 .rb の中で t2 が Thread.current になっているようだったのと、scratch の配列の順序が
違っているようだったので少し変更してみました。

diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb
index 9186a6f..02f7efe 100644
--- a/test/ruby/test_require.rb
+++ b/test/ruby/test_require.rb
@@ -349,10 +349,11 @@ class TestRequire < Test::Unit::TestCase
     tmp = Tempfile.new(%w"bug5754 .rb")
     path = tmp.path
     tmp.print <<-EOS
+Thread.pass until TestRequire.scratch[1]
 TestRequire.scratch << :pre
-Thread.pass until t2 = TestRequire.scratch[1]
+t2 = TestRequire.scratch[0,2].reject{|t| t == Thread.current}[0]
 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[0, 2];if Thread.current == t2; Thread.pass until t1.stopped?; end"}
 raise "con1"
     EOS
     tmp.close
@@ -364,6 +365,8 @@ raise "con1"
     t2_res = nil

     t1 = Thread.new do
+      Thread.pass until t1
+      scratch << t1
       begin
         require(path)
       rescue RuntimeError
@@ -376,6 +379,7 @@ raise "con1"
     end

     t2 = Thread.new do
+      Thread.pass until t2
       Thread.pass until scratch[0]
       begin
         scratch << t2
@@ -389,8 +393,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([t1, t2, :pre, :post, :t2, :t1], scratch, bug5754)
   ensure
     tmp.close(true) if tmp
   end

この状態で test_race_exception を実行すると以下のように ThreadError が(require のところで)発生するようになりました。

  1) Failure:
test_race_exception(TestRequire) [/home/nagachika/opt/ruby-trunk/src/ruby/test/ruby/test_require.rb:386]:
[ruby-core:41618].
Exception raised:
<#<ThreadError: Attempt to unlock a mutex which is locked by another thread>>.

とり急ぎご報告まで。
----------------------------------------
Bug #5768: TestRequire#test_race_exceptionで競合するケースがまだある
https://bugs.ruby-lang.org/issues/5768

Author: Yui NARUSE
Status: Assigned
Priority: Normal
Assignee: Nobuyoshi Nakada
Category: 
Target version: 
ruby -v: ruby 2.0.0dev (2011-12-16 trunk 34058) [x86_64-freebsd9.0]


まだ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

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [ruby-dev:45011] [ruby-trunk - Bug #5768] TestRequire#test_race_exceptionで競合するケースがまだある
  2011-12-17  1:48 [ruby-dev:45002] [ruby-trunk - Bug #5768][Assigned] TestRequire#test_race_exceptionで競合するケースがまだある Yui NARUSE
  2011-12-19  1:58 ` [ruby-dev:45006] [ruby-trunk - Bug #5768] TestRequire#test_race_exceptionで競合するケースがまだある Tomoyuki Chikanaga
@ 2011-12-20  4:43 ` Yui NARUSE
  2011-12-20  6:57   ` [ruby-dev:45013] " Nobuyoshi Nakada
  2011-12-20  5:10 ` [ruby-dev:45012] " Motohiro KOSAKI
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 8+ messages in thread
From: Yui NARUSE @ 2011-12-20  4:43 UTC (permalink / raw
  To: ruby developers list


Issue #5768 has been updated by Yui NARUSE.


追いかけた結果、どうも lock に用いている rb_barrier_release/rb_barrier_destroy や rb_mutex_lock のマルチスレッド対応がいまいちだったようです。
前者は rb_mutex_unlock を外した直後から、待っていた他のスレッドが動き始めるのでもはや mutex->cond_waiting は古い情報となってしまう点、
後者は GVL_UNLOCK_BEGIN() した直後から、メインのスレッドが動いてしまう可能性があるのがダメですね。

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"
     EOS
     tmp.close
@@ -368,7 +377,6 @@ raise "con1"
         require(path)
       rescue RuntimeError
       end
-
       t1_res = require(path)
 
       Thread.pass until fin
@@ -376,7 +384,7 @@ raise "con1"
     end
 
     t2 = Thread.new do
-      Thread.pass until scratch[0]
+      Thread.pass until scratch[1]
       begin
         scratch << t2
         t2_res = require(path)
@@ -389,8 +397,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
diff --git a/thread.c b/thread.c
index ced10c2..c46653b 100644
--- a/thread.c
+++ b/thread.c
@@ -3409,7 +3409,6 @@ lock_func(rb_thread_t *th, rb_mutex_t *mutex, int timeout_ms)
     int interrupted = 0;
     int err = 0;
 
-    mutex->cond_waiting++;
     for (;;) {
 	if (!mutex->th) {
 	    mutex->th = th;
@@ -3438,7 +3437,6 @@ lock_func(rb_thread_t *th, rb_mutex_t *mutex, int timeout_ms)
 	    err = 0;
 	}
     }
-    mutex->cond_waiting--;
 
     return interrupted;
 }
@@ -3493,10 +3491,12 @@ rb_mutex_lock(VALUE self)
 	    if (vm_living_thread_num(th->vm) == th->vm->sleeper) {
 		timeout_ms = 100;
 	    }
+	    mutex->cond_waiting++;
 	    GVL_UNLOCK_BEGIN();
 	    interrupted = lock_func(th, mutex, timeout_ms);
 	    native_mutex_unlock(&mutex->lock);
 	    GVL_UNLOCK_END();
+	    mutex->cond_waiting--;
 
 	    reset_unblock_function(th, &oldubf);
 
@@ -3727,9 +3727,11 @@ rb_barrier_release(VALUE self)
 {
     VALUE mutex = GetBarrierPtr(self);
     rb_mutex_t *m;
-    rb_mutex_unlock(mutex);
+    int waiting;
     GetMutexPtr(mutex, m);
-    return m->cond_waiting > 0 ? Qtrue : Qfalse;
+    waiting = m->cond_waiting;
+    rb_mutex_unlock(mutex);
+    return waiting > 0 ? Qtrue : Qfalse;
 }
 
 /*
@@ -3740,10 +3742,12 @@ rb_barrier_destroy(VALUE self)
 {
     VALUE mutex = GetBarrierPtr(self);
     rb_mutex_t *m;
+    int waiting;
     DATA_PTR(self) = 0;
-    rb_mutex_unlock(mutex);
     GetMutexPtr(mutex, m);
-    return m->cond_waiting > 0 ? Qtrue : Qfalse;
+    waiting = m->cond_waiting;
+    rb_mutex_unlock(mutex);
+    return waiting > 0 ? Qtrue : Qfalse;
 }
 
 int
----------------------------------------
Bug #5768: TestRequire#test_race_exceptionで競合するケースがまだある
https://bugs.ruby-lang.org/issues/5768

Author: Yui NARUSE
Status: Assigned
Priority: Normal
Assignee: Nobuyoshi Nakada
Category: 
Target version: 
ruby -v: ruby 2.0.0dev (2011-12-16 trunk 34058) [x86_64-freebsd9.0]


まだ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

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [ruby-dev:45012] [ruby-trunk - Bug #5768] TestRequire#test_race_exceptionで競合するケースがまだある
  2011-12-17  1:48 [ruby-dev:45002] [ruby-trunk - Bug #5768][Assigned] TestRequire#test_race_exceptionで競合するケースがまだある Yui NARUSE
  2011-12-19  1:58 ` [ruby-dev:45006] [ruby-trunk - Bug #5768] TestRequire#test_race_exceptionで競合するケースがまだある Tomoyuki Chikanaga
  2011-12-20  4:43 ` [ruby-dev:45011] " Yui NARUSE
@ 2011-12-20  5:10 ` Motohiro KOSAKI
  2011-12-20  7:55 ` [ruby-dev:45014] " Yui NARUSE
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Motohiro KOSAKI @ 2011-12-20  5:10 UTC (permalink / raw
  To: ruby developers list


Issue #5768 has been updated by Motohiro KOSAKI.

Category set to core
Assignee changed from Nobuyoshi Nakada to Motohiro KOSAKI
Target version set to 2.0.0

lock_funcやrb_mutex_lockを変更するのは反対なので、いったん預かります

----------------------------------------
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: ruby 2.0.0dev (2011-12-16 trunk 34058) [x86_64-freebsd9.0]


まだ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

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [ruby-dev:45013] Re: [ruby-trunk - Bug #5768] TestRequire#test_race_exceptionで競合するケースがまだある
  2011-12-20  4:43 ` [ruby-dev:45011] " Yui NARUSE
@ 2011-12-20  6:57   ` Nobuyoshi Nakada
  0 siblings, 0 replies; 8+ messages in thread
From: Nobuyoshi Nakada @ 2011-12-20  6:57 UTC (permalink / raw
  To: ruby developers list

なかだです。

(11/12/20 13:43), Yui NARUSE wrote:
> 追いかけた結果、どうも lock に用いている rb_barrier_release/rb_barrier_destroy や rb_mutex_lock のマルチスレッド対応がいまいちだったようです。
> 前者は rb_mutex_unlock を外した直後から、待っていた他のスレッドが動き始めるのでもはや mutex->cond_waiting は古い情報となってしまう点、
> 後者は GVL_UNLOCK_BEGIN() した直後から、メインのスレッドが動いてしまう可能性があるのがダメですね。

たしかに、rb_mutex_lockがGVLを先に開放していることを見落としていました。
rb_barrier_release/rb_barrier_destroyが呼ばれた時点ではmutexがロックさ
れていることが前提ですので、後半のBarrier部分の変更だけで充分な気がしま
す。

> 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のどちらが実際にロードすることになるかは不定のはず
です。

-- 
--- 僕の前にBugはない。
--- 僕の後ろにBugはできる。
    中田 伸悦

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [ruby-dev:45014] [ruby-trunk - Bug #5768] TestRequire#test_race_exceptionで競合するケースがまだある
  2011-12-17  1:48 [ruby-dev:45002] [ruby-trunk - Bug #5768][Assigned] TestRequire#test_race_exceptionで競合するケースがまだある Yui NARUSE
                   ` (2 preceding siblings ...)
  2011-12-20  5:10 ` [ruby-dev:45012] " Motohiro KOSAKI
@ 2011-12-20  7:55 ` Yui NARUSE
  2012-01-05  1:58 ` [ruby-dev:45091] " Yui NARUSE
  2012-11-26 11:05 ` [ruby-dev:46630] [ruby-trunk - Bug #5768][Closed] TestRequire#test_race_exceptionで競合するケースがまだある kosaki (Motohiro KOSAKI)
  5 siblings, 0 replies; 8+ messages in thread
From: Yui NARUSE @ 2011-12-20  7:55 UTC (permalink / raw
  To: ruby developers list


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

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [ruby-dev:45091] [ruby-trunk - Bug #5768] TestRequire#test_race_exceptionで競合するケースがまだある
  2011-12-17  1:48 [ruby-dev:45002] [ruby-trunk - Bug #5768][Assigned] TestRequire#test_race_exceptionで競合するケースがまだある Yui NARUSE
                   ` (3 preceding siblings ...)
  2011-12-20  7:55 ` [ruby-dev:45014] " Yui NARUSE
@ 2012-01-05  1:58 ` Yui NARUSE
  2012-11-26 11:05 ` [ruby-dev:46630] [ruby-trunk - Bug #5768][Closed] TestRequire#test_race_exceptionで競合するケースがまだある kosaki (Motohiro KOSAKI)
  5 siblings, 0 replies; 8+ messages in thread
From: Yui NARUSE @ 2012-01-05  1:58 UTC (permalink / raw
  To: ruby developers list


Issue #5768 has been updated by Yui NARUSE.


とりあえず r34163 で mutex->cond_waiting を用いるのをやめ、RBASIC()->flags に待っているスレッドの数を保存するように変更しました。
これで、問題自体は直ったはずですし、現在まで再現していません。
他の方法で実装し直したいと仰っていたので、小崎さんにアサインされたままにはしておきます。
----------------------------------------
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

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [ruby-dev:46630] [ruby-trunk - Bug #5768][Closed] TestRequire#test_race_exceptionで競合するケースがまだある
  2011-12-17  1:48 [ruby-dev:45002] [ruby-trunk - Bug #5768][Assigned] TestRequire#test_race_exceptionで競合するケースがまだある Yui NARUSE
                   ` (4 preceding siblings ...)
  2012-01-05  1:58 ` [ruby-dev:45091] " Yui NARUSE
@ 2012-11-26 11:05 ` kosaki (Motohiro KOSAKI)
  5 siblings, 0 replies; 8+ messages in thread
From: kosaki (Motohiro KOSAKI) @ 2012-11-26 11:05 UTC (permalink / raw
  To: ruby developers list


Issue #5768 has been updated by kosaki (Motohiro KOSAKI).

Status changed from Assigned to Closed

さすがに時期的に、書き直しはあきらめるべきなので、close

----------------------------------------
Bug #5768: TestRequire#test_race_exceptionで競合するケースがまだある
https://bugs.ruby-lang.org/issues/5768#change-33953

Author: naruse (Yui NARUSE)
Status: Closed
Priority: Normal
Assignee: kosaki (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://bugs.ruby-lang.org/

^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2012-11-26 11:07 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-12-17  1:48 [ruby-dev:45002] [ruby-trunk - Bug #5768][Assigned] TestRequire#test_race_exceptionで競合するケースがまだある Yui NARUSE
2011-12-19  1:58 ` [ruby-dev:45006] [ruby-trunk - Bug #5768] TestRequire#test_race_exceptionで競合するケースがまだある Tomoyuki Chikanaga
2011-12-20  4:43 ` [ruby-dev:45011] " Yui NARUSE
2011-12-20  6:57   ` [ruby-dev:45013] " Nobuyoshi Nakada
2011-12-20  5:10 ` [ruby-dev:45012] " Motohiro KOSAKI
2011-12-20  7:55 ` [ruby-dev:45014] " Yui NARUSE
2012-01-05  1:58 ` [ruby-dev:45091] " Yui NARUSE
2012-11-26 11:05 ` [ruby-dev:46630] [ruby-trunk - Bug #5768][Closed] TestRequire#test_race_exceptionで競合するケースがまだある kosaki (Motohiro KOSAKI)

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).