Issue #7452 has been updated by authorNari (Narihiro N.).
バグの原因がわかりました。
ファイナライザに登録したメインスレッドはRubyプロセス終了時のファイナライザ実行時(rb_objspace_call_finalizer)でfreelistに追加されてしまっているようです。
(gdbのバックトレース)
#0 add_slot_local_freelist (objspace=0x5555559ed8f0, p=0x555555a5be48)
at gc.c:819
#1 0x00005555555c7f42 in finalize_list (objspace=0x5555559ed8f0,
p=0x555555a5be48) at gc.c:1423
#2 0x00005555555c7fd5 in finalize_deferred (objspace=0x5555559ed8f0) at
gc.c:1443
#3 0x00005555555c81ee in rb_objspace_call_finalizer
(objspace=0x5555559ed8f0) at gc.c:1509
#4 0x00005555555c81a6 in rb_gc_call_finalizer_at_exit () at gc.c:1493
#5 0x00005555555b1b5a in ruby_finalize_1 () at eval.c:127
#6 0x00005555555b1dbb in ruby_cleanup (ex=0) at eval.c:193
#7 0x00005555555b20a7 in ruby_run_node (n=0x555555a581e8) at eval.c:307
#8 0x00005555555746b9 in main (argc=3, argv=0x7fffffffd248) at
main.c:36
rb_objspace_call_finalizer()呼び出し後にruby_vm_destruct(VMのデストラクタ)が動き、そのデストラクタではメインスレッドを使うのですが、上記の通りfreeされてしまっているのでうまくいかないようですね。
これはメインスレッドに限った話ではなく、rb_objspace_call_finalizer()呼び出し後に触る可能性があるオブジェクトにファイナライザを登録するのがまずいみたいです。
ややad-hocですが、こんな感じでなおしてみました。
fix_7452_for_trunk.diff
diff --git a/gc.c b/gc.c
index 63869a0..a698792 100644
--- a/gc.c
+++ b/gc.c
@@ -1499,11 +1499,16 @@ rb_objspace_call_finalizer(rb_objspace_t *objspace)
RVALUE *p, *pend;
RVALUE *final_list = 0;
size_t i;
+ rb_thread_t *th = GET_THREAD();
This file has been truncated. show original
VMから見えているものはとりあえずマークしておいて、通常のファイナライズは回避し、強制的なファイナライズでそれらを実行させるようにしています。
もしかしたら以下のパッチのようにほとんど強制的なファイナライズにしてもいいかなあと思いますが、こっちはちょっと自信がないです。
Rubyのファイナライズ周りに詳しい方の意見を伺いたいところです。。。
diff --git a/gc.c b/gc.c
index 63869a0…8d68e38 100644
— a/gc.c
+++ b/gc.c
@@ -1505,15 +1505,9 @@ rb_objspace_call_finalizer(rb_objspace_t
*objspace)
if (ATOMIC_EXCHANGE(finalizing, 1)) return;
/* run finalizers */
do {
finalize_deferred(objspace);
/* mark reachable objects from finalizers */
/* They might be not referred from any place here */
mark_tbl(objspace, finalizer_table);
gc_mark_stacked_objects(objspace);
st_foreach(finalizer_table, chain_finalized_object,
(st_data_t)&deferred_final_list);
} while (deferred_final_list);
finalize_deferred(objspace);
assert(deferred_final_list == 0);
/* force to run finalizer */
while (finalizer_table->num_entries) {
struct force_finalize_list *list = 0;
Bug #7452: Main thread is stopped after running finalizers if the main
thread has a finalizer
Author: mrkn (Kenta M.)
Status: Assigned
Priority: Normal
Assignee: authorNari (Narihiro N.)
Category: core
Target version: 2.0.0
ruby -v: ruby 2.0.0dev (2012-11-28 trunk 37923) [x86_64-darwin11.4.2]
以下のようにメインスレッドにファイナライザを登録すると、ファイナライザ実行後に止まってしまいます。
ObjectSpace.define_finalizer(Thread.main) {}
trunk と ruby_1_9_3 ブランチの先頭で発生することを確認しています。
trunk は以下の gist に貼った patch で修正できました。
fix_bug_on_finalizer_of_main_thread-trunk.patch
diff --git a/gc.c b/gc.c
index 63869a0..e15f141 100644
--- a/gc.c
+++ b/gc.c
@@ -1420,9 +1420,11 @@ finalize_list(rb_objspace_t *objspace, RVALUE *p)
RVALUE *tmp = p->as.free.next;
run_final(objspace, (VALUE)p);
if (!FL_TEST(p, FL_SINGLETON)) { /* not freeing page */
- add_slot_local_freelist(objspace, p);
- if (!is_lazy_sweeping(objspace)) {
This file has been truncated. show original
1.9.3 は以下の gist に貼った patch で修正できました。
fix_bug_on_finalizer_of_main_thread-1.9.3.patch
diff --git a/gc.c b/gc.c
index 01d908d..ba9726c 100644
--- a/gc.c
+++ b/gc.c
@@ -2042,12 +2042,14 @@ finalize_list(rb_objspace_t *objspace, RVALUE *p)
RVALUE *tmp = p->as.free.next;
run_final(objspace, (VALUE)p);
if (!FL_TEST(p, FL_SINGLETON)) { /* not freeing page */
- if (objspace->heap.sweep_slots) {
- p->as.free.flags = 0;
This file has been truncated. show original