[#34911] erb still treats $KCODE — "Yusuke ENDOH" <mame@...>

遠藤です。

16 messages 2008/06/03

[#34923] open() and encodings — "NARUSE, Yui" <naruse@...>

成瀬です。

53 messages 2008/06/03
[#34924] Re: open() and encodings — Yukihiro Matsumoto <matz@...> 2008/06/04

まつもと ゆきひろです

[#34931] Re: open() and encodings — "NARUSE, Yui" <naruse@...> 2008/06/04

成瀬です。

[#34934] Re: open() and encodings — Yukihiro Matsumoto <matz@...> 2008/06/05

まつもと ゆきひろです

[#34935] Re: open() and encodings — "U.Nakamura" <usa@...> 2008/06/05

こんにちは、なかむら(う)です。

[#34936] Re: open() and encodings — Yukihiro Matsumoto <matz@...> 2008/06/05

まつもと ゆきひろです

[#34937] Re: open() and encodings — "U.Nakamura" <usa@...> 2008/06/05

こんにちは、なかむら(う)です。

[#34948] Re: open() and encodings — Hidetoshi NAGAI <nagai@...> 2008/06/05

永井@知能.九工大です.

[#34961] Re: open() and encodings — "NARUSE, Yui" <naruse@...> 2008/06/05

成瀬です。

[#34997] Re: [ruby-changes:5517] Ruby:r17021 (trunk): * vm_insnhelper.c, vm.c, proc.c (proc_call): allow call method with — SASADA Koichi <ko1@...>

 ささだです.

19 messages 2008/06/08
[#34998] Re: [ruby-changes:5517] Ruby:r17021 (trunk): * vm_insnhelper.c, vm.c, proc.c (proc_call): allow call method with — Yukihiro Matsumoto <matz@...> 2008/06/08

まつもと ゆきひろです

[#34999] Re: [ruby-changes:5517] Ruby:r17021 (trunk): * vm_insnhelper.c, vm.c, proc.c (proc_call): allow call method with — SASADA Koichi <ko1@...> 2008/06/08

 ささだです.

[#35000] Re: [ruby-changes:5517] Ruby:r17021 (trunk): * vm_insnhelper.c, vm.c, proc.c (proc_call): allow call method with — Yukihiro Matsumoto <matz@...> 2008/06/08

まつもと ゆきひろです

[#35001] Re: [ruby-changes:5517] Ruby:r17021 (trunk): * vm_insnhelper.c, vm.c, proc.c (proc_call): allow call method with — SASADA Koichi <ko1@...> 2008/06/08

 ささだです.

[#35003] Re: [ruby-changes:5517] Ruby:r17021 (trunk): * vm_insnhelper.c, vm.c, proc.c (proc_call): allow call method with — Yukihiro Matsumoto <matz@...> 2008/06/08

まつもと ゆきひろです

[#35007] Re: [ruby-changes:5517] Ruby:r17021 (trunk): * vm_insnhelper.c, vm.c, proc.c (proc_call): allow call method with — "Yusuke ENDOH" <mame@...> 2008/06/09

遠藤です。

[#35013] Re: [ruby-changes:5517] Ruby:r17021 (trunk): * vm_insnhelper.c, vm.c, proc.c (proc_call): allow call method with — Yukihiro Matsumoto <matz@...> 2008/06/09

まつもと ゆきひろです

[#35019] Re: [ruby-changes:5517] Ruby:r17021 (trunk): * vm_insnhelper.c, vm.c, proc.c (proc_call): allow call method with — "Yusuke ENDOH" <mame@...> 2008/06/09

遠藤です。

[#35021] Re: [ruby-changes:5517] Ruby:r17021 (trunk): * vm_insnhelper.c, vm.c, proc.c (proc_call): allow call method with — Yukihiro Matsumoto <matz@...> 2008/06/09

まつもと ゆきひろです

[#35020] Ruby 1.8.7-p17 has been released — "Akinori MUSHA" <knu@...>

 Ruby 1.8.7-p17 をリリースしました。

13 messages 2008/06/09

[#35044] deadlock detection for 1.9 — "Yusuke ENDOH" <mame@...>

遠藤です。

14 messages 2008/06/10

[#35108] Re: [ruby-list:44988] Re: 各ブランチの計画 — Urabe Shyouhei <shyouhei@...>

卜部です。

15 messages 2008/06/15

[#35200] Win32 Unicode console output — Tietew <tietew@...>

Tietew です。

22 messages 2008/06/22
[#35270] Re: Win32 Unicode console output — "NARUSE, Yui" <naruse@...> 2008/06/29

[#35226] [PATCH] freeze required_paths in gem_prelude.rb — "Keita Yamaguchi" <keita.yamaguchi@...>

山口と申します。

14 messages 2008/06/25
[#35228] Re: [PATCH] freeze required_paths in gem_prelude.rb — "Yusuke ENDOH" <mame@...> 2008/06/25

遠藤です。

[#35230] Re: [PATCH] freeze required_paths in gem_prelude.rb — Yukihiro Matsumoto <matz@...> 2008/06/25

まつもと ゆきひろです

[#35227] [Bug:trunk] Re: [ruby-cvs:24798] Ruby:r17573 (trunk): * parse.y (primary): make functional-style not operator to act — "U.Nakamura" <usa@...>

こんにちは、なかむら(う)です。

7 messages 2008/06/25

[#35247] Re: [ruby-list:45128] Re: Ruby 1.9.0/1.8.7/1.8.6/1.8.5 new releases (Security Fix) — Urabe Shyouhei <shyouhei@...>

卜部です。-devに振ります。ひょっとしてこんなパッチでSEGVのほうはおさまっ

13 messages 2008/06/26
[#35250] Re: [ruby-list:45128] Re: Ruby 1.9.0/1.8.7/1.8.6/1.8.5 new releases (Security Fix) — Yukihiro Matsumoto <matz@...> 2008/06/26

まつもと ゆきひろです

[#35273] $SAFEの今後 — Urabe Shyouhei <shyouhei@...>

〜これまでのあらすじ〜

24 messages 2008/06/30
[#35293] Re: $SAFEの今後 — Yukihiro Matsumoto <matz@...> 2008/07/01

まつもと ゆきひろです

[#35298] Re: $SAFEの今後 — Urabe Shyouhei <shyouhei@...> 2008/07/01

卜部です。

[#35303] Re: $SAFEの今後 — Yukihiro Matsumoto <matz@...> 2008/07/01

まつもと ゆきひろです

[#35304] Re: $SAFEの今後 — Urabe Shyouhei <shyouhei@...> 2008/07/01

卜部です。

[#35305] Re: $SAFEの今後 — Yukihiro Matsumoto <matz@...> 2008/07/01

まつもと ゆきひろです

[#35306] Re: $SAFEの今後 — "Shugo Maeda" <shugo@...> 2008/07/02

前田です。

[#35278] [BUG] test_win32ole_event.rb in trunk — Masaki Suketa <masaki.suketa@...>

助田です。

22 messages 2008/06/30
[#35281] Re: [BUG] test_win32ole_event.rb in trunk — "U.Nakamura" <usa@...> 2008/06/30

こんにちは、なかむら(う)です。

[#35282] Re: [BUG] test_win32ole_event.rb in trunk — arton <artonx@...> 2008/06/30

artonです。

[#35295] Re: [BUG] test_win32ole_event.rb in trunk — Masaki Suketa <masaki.suketa@...> 2008/07/01

助田です。

[ruby-dev:35044] deadlock detection for 1.9

From: "Yusuke ENDOH" <mame@...>
Date: 2008-06-10 15:25:26 UTC
List: ruby-dev #35044
遠藤です。

1.9 のスレッドにデッドロック検出を実装してみました。


$ ./ruby -e 'Thread.new { Thread.stop }; Thread.stop'
-e:1:in `stop': deadlock detected (fatal)
        from -e:1:in `<main>'


ちょっとでかいパッチ (添付) ですが、要旨は以下の通りです。

  - スレッドの状態種別に THREAD_STOPPED_FOREVER を追加。
    無期限の sleep 状態と mutex 解放待ち状態を表す。

  - rb_vm_t に変数 sleeper を追加。
    THREAD_STOPPED_FOREVER 状態のスレッドの数を表す。

  - rb_thread_t に変数 locking_mutex を追加。
    このスレッドが待っている mutex を表す。

  - rb_thread_t に変数 keeping_mutexes を追加。mutex_t に
    変数 next_mutex を追加。
    スレッドがロックしている mutex のリストを表す。

  - mutex_t に変数 cond_notified を追加。
    cond_signal されてまだ起動していないスレッドの数を表す。

  - rb_mutex_lock や rb_mutex_unlock で上記の変数たちを
    適宜更新するようにした。

  - 唯一動いていそうなスレッドが native_cond_wait しそうな
    時は、lock_func を抜けてデッドロック検査するようにした。

  - rb_check_deadlock では vm->living_threads を列挙して、
      - 例外状態のスレッド、または
      - locking_mutex のロックに成功したスレッド、または
      - locking_mutex が誰にもロックされていないスレッド、
    のいずれもなければ main_thread に fatal な例外を投げる
    ようにした。

  - スレッドの終了時にロックしていた mutex をすべて解放
    するようにした。


このパッチは元々 redmine の Feature #17 に流したものですが、
以下の修正を追加しています。

  - thread_win32.c の修正を追加した (ただし未テスト)

  - Kernel#sleep でデッドロック状態になった場合は例外にしない
    ようにした

  - バグフィクスとちょっと最適化


制限は以下の通りです。

  - スレッドやロックの処理が少し重くなる。
    計測のたびに変わるけれど、だいたいこのくらいです。
    vm3_thread_create_join  6.565   6.586
    vm3_thread_mutex        3.023   3.155

  - スレッドがロック中の mutex が GC されなくなる。
    loop { Mutex.new.lock } # どんどんメモリを消費する

  - Thread#join 、#stop 、Mutex#lock 以外で固まっている
    スレッドがあるとデッドロック検出しない。
    r, w = IO.pipe; r.read # 固まり続ける


どんなもんでしょうか。

-- 
Yusuke ENDOH <mame@tsg.ne.jp>

Attachments (1)

deadlock.patch (14.8 KB, text/x-diff)
Index: thread_win32.c
===================================================================
--- thread_win32.c	(revision 17019)
+++ thread_win32.c	(working copy)
@@ -204,7 +204,7 @@
 }
 
 static void
-native_sleep(rb_thread_t *th, struct timeval *tv)
+native_sleep(rb_thread_t *th, struct timeval *tv, int deadlockable)
 {
     DWORD msec;
     if (tv) {
@@ -214,12 +214,19 @@
 	msec = INFINITE;
     }
 
+    if (!tv && deadlockable) {
+	th->status = THREAD_STOPPED_FOREVER;
+	th->vm->sleeper++;
+	rb_check_deadlock(th->vm);
+    }
+    else {
+	th->status = THREAD_STOPPED;
+    }
     GVL_UNLOCK_BEGIN();
     {
 	DWORD ret;
 	int status = th->status;
 
-	th->status = THREAD_STOPPED;
 	th->unblock.func = ubf_handle;
 	th->unblock.arg = th;
 
@@ -234,9 +241,10 @@
 
 	th->unblock.func = 0;
 	th->unblock.arg = 0;
-	th->status = status;
     }
     GVL_UNLOCK_END();
+    th->status = status;
+    if (!tv && deadlockable) th->vm->sleeper++;
     RUBY_VM_CHECK_INTS();
 }
 
Index: thread_pthread.c
===================================================================
--- thread_pthread.c	(revision 17019)
+++ thread_pthread.c	(working copy)
@@ -402,7 +402,7 @@
 #endif
 
 static void
-native_sleep(rb_thread_t *th, struct timeval *tv)
+native_sleep(rb_thread_t *th, struct timeval *tv, int deadlockable)
 {
     int prev_status = th->status;
     struct timespec ts;
@@ -418,7 +418,14 @@
         }
     }
 
-    th->status = THREAD_STOPPED;
+    if (!tv && deadlockable) {
+	th->status = THREAD_STOPPED_FOREVER;
+	th->vm->sleeper++;
+	rb_check_deadlock(th->vm);
+    }
+    else {
+	th->status = THREAD_STOPPED;
+    }
 
     thread_debug("native_sleep %ld\n", tv ? tv->tv_sec : -1);
     GVL_UNLOCK_BEGIN();
@@ -455,9 +462,10 @@
 	th->unblock.arg = 0;
 
 	pthread_mutex_unlock(&th->interrupt_lock);
-	th->status = prev_status;
     }
     GVL_UNLOCK_END();
+    th->status = prev_status;
+    if (!tv && deadlockable) th->vm->sleeper--;
     RUBY_VM_CHECK_INTS();
 
     thread_debug("native_sleep done\n");
Index: bootstraptest/test_thread.rb
===================================================================
--- bootstraptest/test_thread.rb	(revision 17019)
+++ bootstraptest/test_thread.rb	(working copy)
@@ -268,3 +268,55 @@
   at_exit { Fiber.new{}.resume }
 }
 
+assert_equal 'ok', %q{
+  begin
+    Thread.new { Thread.stop }
+    Thread.stop
+    :ng
+  rescue Exception
+    :ok
+  end
+}
+
+assert_equal 'ok', %q{
+  begin
+    m1, m2 = Mutex.new, Mutex.new
+    Thread.new { m1.lock; sleep 1; m2.lock }
+    m2.lock; sleep 1; m1.lock
+    :ng
+  rescue Exception
+    :ok
+  end
+}
+
+assert_equal 'ok', %q{
+  m = Mutex.new
+  Thread.new { m.lock }; sleep 1; m.lock
+  :ok
+}
+
+assert_equal 'ok', %q{
+  m = Mutex.new
+  Thread.new { m.lock }; m.lock
+  :ok
+}
+
+assert_equal 'ok', %q{
+  m = Mutex.new
+  Thread.new { m.lock }.join; m.lock
+  :ok
+}
+
+assert_equal 'ok', %q{
+  m = Mutex.new
+  Thread.new { m.lock; sleep 2 }
+  sleep 1; m.lock
+  :ok
+}
+
+assert_equal 'ok', %q{
+  m = Mutex.new
+  Thread.new { m.lock; sleep 2; m.unlock }
+  sleep 1; m.lock
+  :ok
+}
Index: vm_core.h
===================================================================
--- vm_core.h	(revision 17019)
+++ vm_core.h	(working copy)
@@ -302,6 +302,7 @@
     int running;
     int thread_abort_on_exception;
     unsigned long trace_flag;
+    volatile int sleeper;
 
     /* object management */
     VALUE mark_object_ary;
@@ -356,6 +357,7 @@
     THREAD_TO_KILL,
     THREAD_RUNNABLE,
     THREAD_STOPPED,
+    THREAD_STOPPED_FOREVER,
     THREAD_KILLED,
 };
 
@@ -425,6 +427,8 @@
     int interrupt_flag;
     rb_thread_lock_t interrupt_lock;
     struct rb_unblock_callback unblock;
+    VALUE locking_mutex;
+    VALUE keeping_mutexes;
 
     struct rb_vm_tag *tag;
     struct rb_vm_trap_tag *trap_tag;
Index: thread.c
===================================================================
--- thread.c	(revision 17019)
+++ thread.c	(working copy)
@@ -57,11 +57,14 @@
 
 static void sleep_timeval(rb_thread_t *th, struct timeval time);
 static void sleep_wait_for_interrupt(rb_thread_t *th, double sleepsec);
-static void sleep_forever(rb_thread_t *th);
+static void sleep_forever(rb_thread_t *th, int nodeadlock);
 static double timeofday(void);
 struct timeval rb_time_interval(VALUE);
 static int rb_thread_dead(rb_thread_t *th);
 
+static void rb_mutex_unlock_all(VALUE);
+static void rb_check_deadlock(rb_vm_t *vm);
+
 void rb_signal_exec(rb_thread_t *th, int sig);
 void rb_disable_interrupt(void);
 
@@ -93,12 +96,12 @@
   rb_thread_set_current(_th_stored); \
 } while(0)
 
-#define BLOCKING_REGION(exec, ubf, ubfarg) do { \
+#define BLOCKING_REGION(exec, ubf, ubfarg, stopped) do { \
     rb_thread_t *__th = GET_THREAD(); \
     int __prev_status = __th->status; \
     struct rb_unblock_callback __oldubf; \
     set_unblock_function(__th, ubf, ubfarg, &__oldubf); \
-    __th->status = THREAD_STOPPED; \
+    if (stopped) __th->status = THREAD_STOPPED; \
     thread_debug("enter blocking region (%p)\n", __th); \
     GVL_UNLOCK_BEGIN(); {\
 	    exec; \
@@ -107,10 +110,9 @@
     thread_debug("leave blocking region (%p)\n", __th); \
     remove_signal_thread_list(__th); \
     reset_unblock_function(__th, &__oldubf); \
-    if (__th->status == THREAD_STOPPED) { \
+    if (stopped && __th->status == THREAD_STOPPED) { \
 	__th->status = __prev_status; \
     } \
-    RUBY_VM_CHECK_INTS(); \
 } while(0)
 
 #if THREAD_DEBUG
@@ -263,6 +265,11 @@
 	rb_bug("rb_thread_terminate_all: called by child thread (%p, %p)", vm->main_thread, th);
     }
 
+    /* unlock all locking mutexes */
+    if (th->keeping_mutexes) {
+	rb_mutex_unlock_all(th->keeping_mutexes);
+    }
+
     thread_debug("rb_thread_terminate_all (main thread: %p)\n", th);
     st_foreach(vm->living_threads, terminate_i, (st_data_t)th);
 
@@ -361,6 +368,17 @@
 	}
 	TH_POP_TAG();
 
+	/* locking_mutex must be Qfalse */
+	if (th->locking_mutex != Qfalse) {
+	    rb_bug("thread_start_func_2: locking_mutex must be NULL (%p:%ld)", th, th->locking_mutex);
+	}
+
+	/* unlock all locking mutexes */
+	if (th->keeping_mutexes) {
+	    rb_mutex_unlock_all(th->keeping_mutexes);
+	}
+
+	/* delete self from living_threads */
 	st_delete_wrap(th->vm->living_threads, th->self);
 
 	/* wake up joinning threads */
@@ -371,6 +389,7 @@
 	    join_th = join_th->join_list_next;
 	}
 	st_delete_wrap(th->vm->living_threads, th->self);
+	if (th != main_th) rb_check_deadlock(th->vm);
 
 	if (!th->root_fiber) {
 	    rb_thread_recycle_stack_release(th->stack);
@@ -511,7 +530,7 @@
 
     while (target_th->status != THREAD_KILLED) {
 	if (p->forever) {
-	    sleep_forever(th);
+	    sleep_forever(th, 1);
 	}
 	else {
 	    now = timeofday();
@@ -667,24 +686,31 @@
 }
 
 static void
-sleep_forever(rb_thread_t *th)
+sleep_forever(rb_thread_t *th, int deadlockable)
 {
-    native_sleep(th, 0);
+    native_sleep(th, 0, deadlockable);
 }
 
 static void
 sleep_timeval(rb_thread_t *th, struct timeval tv)
 {
-    native_sleep(th, &tv);
+    native_sleep(th, &tv, 0);
 }
 
 void
 rb_thread_sleep_forever()
 {
     thread_debug("rb_thread_sleep_forever\n");
-    sleep_forever(GET_THREAD());
+    sleep_forever(GET_THREAD(), 0);
 }
 
+static void
+rb_thread_sleep_deadly()
+{
+    thread_debug("rb_thread_sleep_deadly\n");
+    sleep_forever(GET_THREAD(), 1);
+}
+
 static double
 timeofday(void)
 {
@@ -782,7 +808,8 @@
 
     BLOCKING_REGION({
 	val = func(data1);
-    }, ubf, data2);
+    }, ubf, data2, 1);
+    RUBY_VM_CHECK_INTS();
 
     return val;
 }
@@ -1128,7 +1155,7 @@
 	rb_raise(rb_eThreadError,
 		 "stopping only thread\n\tnote: use sleep to stop forever");
     }
-    rb_thread_sleep_forever();
+    rb_thread_sleep_deadly();
     return Qnil;
 }
 
@@ -1142,6 +1169,7 @@
     switch (th->status) {
       case THREAD_RUNNABLE:
       case THREAD_STOPPED:
+      case THREAD_STOPPED_FOREVER:
       case THREAD_TO_KILL:
 	rb_ary_push(ary, th->self);
       default:
@@ -1336,6 +1364,7 @@
       case THREAD_RUNNABLE:
 	return "run";
       case THREAD_STOPPED:
+      case THREAD_STOPPED_FOREVER:
 	return "sleep";
       case THREAD_TO_KILL:
 	return "aborting";
@@ -1435,7 +1464,7 @@
 
     if (rb_thread_dead(th))
 	return Qtrue;
-    if (th->status == THREAD_STOPPED)
+    if (th->status == THREAD_STOPPED || th->status == THREAD_STOPPED_FOREVER)
 	return Qtrue;
     return Qfalse;
 }
@@ -1875,14 +1904,16 @@
 		    if (except) *except = orig_except;
 		    wait = &wait_100ms;
 		} while (__th->interrupt_flag == 0 && (timeout == 0 || subst(timeout, &wait_100ms)));
-	    }, 0, 0);
+	    }, 0, 0, 1);
+	    RUBY_VM_CHECK_INTS();
 	} while (result == 0 && (timeout == 0 || subst(timeout, &wait_100ms)));
     }
 #else
     BLOCKING_REGION({
 	result = select(n, read, write, except, timeout);
 	if (result < 0) lerrno = errno;
-    }, ubf_select, GET_THREAD());
+    }, ubf_select, GET_THREAD(), 1);
+    RUBY_VM_CHECK_INTS();
 #endif
 
     errno = lerrno;
@@ -2319,7 +2350,8 @@
     rb_thread_lock_t lock;
     rb_thread_cond_t cond;
     rb_thread_t volatile *th;
-    volatile int cond_waiting;
+    volatile int cond_waiting, cond_notified;
+    VALUE next_mutex;
 } mutex_t;
 
 #define GetMutexPtr(obj, tobj) \
@@ -2333,6 +2365,9 @@
 	if (mutex->th) {
 	    rb_gc_mark(mutex->th->self);
 	}
+	if (mutex->next_mutex) {
+	    rb_gc_mark(mutex->next_mutex);
+	}
     }
 }
 
@@ -2391,6 +2426,17 @@
     return mutex->th ? Qtrue : Qfalse;
 }
 
+static void
+mutex_locked(rb_thread_t *th, VALUE self)
+{
+    if (th->keeping_mutexes) {
+	mutex_t *mutex;
+	GetMutexPtr(self, mutex);
+	mutex->next_mutex = th->keeping_mutexes;
+    }
+    th->keeping_mutexes = self;
+}
+
 /*
  * call-seq:
  *    mutex.try_lock  => true or false
@@ -2413,6 +2459,8 @@
     if (mutex->th == 0) {
 	mutex->th = GET_THREAD();
 	locked = Qtrue;
+
+	mutex_locked(GET_THREAD(), self);
     }
     native_mutex_unlock(&mutex->lock);
 
@@ -2420,17 +2468,23 @@
 }
 
 static int
-lock_func(rb_thread_t *th, mutex_t *mutex)
+lock_func(rb_thread_t *th, mutex_t *mutex, int last_thread)
 {
-    int interrupted = Qfalse;
+    int interrupted = 0;
 
     native_mutex_lock(&mutex->lock);
     while (mutex->th || (mutex->th = th, 0)) {
+	if (last_thread) {
+	    interrupted = 2;
+	    break;
+	}
+
 	mutex->cond_waiting++;
 	native_cond_wait(&mutex->cond, &mutex->lock);
+	mutex->cond_notified--;
 
-	if (th->interrupt_flag) {
-	    interrupted = Qtrue;
+	if (RUBY_VM_INTERRUPTED(th)) {
+	    interrupted = 1;
 	    break;
 	}
     }
@@ -2445,6 +2499,7 @@
     native_mutex_lock(&mutex->lock);
     if (mutex->cond_waiting > 0) {
 	native_cond_broadcast(&mutex->cond);
+	mutex->cond_notified = mutex->cond_waiting;
 	mutex->cond_waiting = 0;
     }
     native_mutex_unlock(&mutex->lock);
@@ -2467,11 +2522,30 @@
 
 	while (mutex->th != th) {
 	    int interrupted;
+	    int prev_status = th->status;
+	    int last_thread = 0;
 
+	    th->locking_mutex = self;
+	    th->status = THREAD_STOPPED_FOREVER;
+	    th->vm->sleeper++;
+	    if (th->vm->living_threads->num_entries == th->vm->sleeper) {
+		last_thread = 1;
+	    }
+
 	    BLOCKING_REGION({
-		interrupted = lock_func(th, mutex);
-	    }, lock_interrupt, mutex);
+		interrupted = lock_func(th, mutex, last_thread);
+	    }, lock_interrupt, mutex, 0);
 
+	    th->locking_mutex = Qfalse;
+	    if (interrupted == 2) {
+		rb_check_deadlock(th->vm);
+		RUBY_VM_SET_TIMER_INTERRUPT(th);
+	    }
+	    th->status = prev_status;
+	    th->vm->sleeper--;
+
+	    if (mutex->th == th) mutex_locked(th, self);
+
 	    if (interrupted) {
 		RUBY_VM_CHECK_INTS();
 	    }
@@ -2480,15 +2554,8 @@
     return self;
 }
 
-/*
- * call-seq:
- *    mutex.unlock    => self
- *
- * Releases the lock.
- * Raises +ThreadError+ if +mutex+ wasn't locked by the current thread.
- */
-VALUE
-rb_mutex_unlock(VALUE self)
+static const char *
+mutex_unlock(VALUE self)
 {
     mutex_t *mutex;
     const char *err = NULL;
@@ -2513,15 +2580,61 @@
 
     native_mutex_unlock(&mutex->lock);
 
+    return err;
+}
+
+/*
+ * call-seq:
+ *    mutex.unlock    => self
+ *
+ * Releases the lock.
+ * Raises +ThreadError+ if +mutex+ wasn't locked by the current thread.
+ */
+VALUE
+rb_mutex_unlock(VALUE self)
+{
+    const char *err;
+    rb_thread_t *th = GET_THREAD();
+    VALUE mtxval = th->keeping_mutexes;
+    mutex_t *mutex, *mutex2;
+
+    err = mutex_unlock(self);
     if (err) rb_raise(rb_eThreadError, err);
+    GetMutexPtr(mtxval, mutex);
+    if (mtxval == self) {
+	th->keeping_mutexes = mutex->next_mutex;
+    }
+    else {
+	while (mutex->next_mutex != self) {
+	    mtxval = mutex->next_mutex;
+	    GetMutexPtr(mtxval, mutex);
+	}
+	mtxval = mutex->next_mutex;
+	GetMutexPtr(mtxval, mutex2);
+	mutex->next_mutex = mutex2->next_mutex;
+    }
 
     return self;
 }
 
+static void
+rb_mutex_unlock_all(VALUE mutexes)
+{
+    const char *err;
+    mutex_t *mutex;
+
+    while (mutexes) {
+	err = mutex_unlock(mutexes);
+	if (err) rb_bug("invalid keeping_mutexes");
+	GetMutexPtr(mutexes, mutex);
+	mutexes = mutex->next_mutex;
+    }
+}
+
 static VALUE
 rb_mutex_sleep_forever(VALUE time)
 {
-    rb_thread_sleep_forever();
+    rb_thread_sleep_deadly();
     return Qnil;
 }
 
@@ -3268,3 +3381,48 @@
 
     return th ? Qtrue : Qfalse;
 }
+
+static int
+check_deadlock_i(st_data_t key, st_data_t val, int *found)
+{
+    VALUE thval = key;
+    rb_thread_t *th;
+    GetThreadPtr(thval, th);
+
+    if (th->status != THREAD_STOPPED_FOREVER) {
+	rb_bug("check_deadlock_i: thread that is not THREAD_STOPPED_FOREVER found (%p:%d)", th, th->status);
+    }
+
+    if (RUBY_VM_INTERRUPTED(th)) {
+	*found = 1;
+    }
+    else if (th->locking_mutex) {
+	mutex_t *mutex;
+	GetMutexPtr(th->locking_mutex, mutex);
+
+	native_mutex_lock(&mutex->lock);
+	if (mutex->th == th || (!mutex->th && mutex->cond_notified)) {
+	    *found = 1;
+	}
+	native_mutex_unlock(&mutex->lock);
+    }
+
+    return (*found) ? ST_STOP : ST_CONTINUE;
+}
+
+static void
+rb_check_deadlock(rb_vm_t *vm)
+{
+    int found = 0;
+
+    if (vm->living_threads->num_entries != vm->sleeper) return;
+
+    st_foreach(vm->living_threads, check_deadlock_i, (st_data_t)&found);
+
+    if (!found) {
+	VALUE argv[2];
+	argv[0] = rb_eFatal;
+	argv[1] = rb_str_new2("deadlock detected");
+	rb_thread_raise(2, argv, vm->main_thread);
+    }
+}
Index: vm.c
===================================================================
--- vm.c	(revision 17019)
+++ vm.c	(working copy)
@@ -1445,6 +1445,13 @@
 	    RUBY_FREE_UNLESS_NULL(th->stack);
 	}
 
+	if (th->locking_mutex != Qfalse) {
+	    rb_bug("thread_free: locking_mutex must be NULL (%p:%ld)", th, th->locking_mutex);
+	}
+	if (th->keeping_mutexes != Qfalse) {
+	    rb_bug("thread_free: keeping_mutexes must be NULL (%p:%ld)", th, th->locking_mutex);
+	}
+
 	if (th->local_storage) {
 	    st_free_table(th->local_storage);
 	}
@@ -1512,6 +1519,9 @@
 	RUBY_MARK_UNLESS_NULL(th->root_fiber);
 	RUBY_MARK_UNLESS_NULL(th->stat_insn_usage);
 
+	RUBY_MARK_UNLESS_NULL(th->locking_mutex);
+	RUBY_MARK_UNLESS_NULL(th->keeping_mutexes);
+
 	rb_mark_tbl(th->local_storage);
 
 	if (GET_THREAD() != th && th->machine_stack_start && th->machine_stack_end) {

In This Thread

Prev Next