Project

General

Profile

Actions

Backport #7452

closed

Main thread is stopped after running finalizers if the main thread has a finalizer

Added by mrkn (Kenta Murata) over 11 years ago. Updated about 11 years ago.

Status:
Closed
[ruby-dev:46647]

Description

以下のようにメインスレッドにファイナライザを登録すると、ファイナライザ実行後に止まってしまいます。

ObjectSpace.define_finalizer(Thread.main) {}

trunk と ruby_1_9_3 ブランチの先頭で発生することを確認しています。

trunk は以下の gist に貼った patch で修正できました。
https://gist.github.com/4159481

1.9.3 は以下の gist に貼った patch で修正できました。
https://gist.github.com/4159480

Updated by mrkn (Kenta Murata) over 11 years ago

このバグに対する再現テストの書き方が分からないので知ってる人がいたら教えて下さい!!

Updated by authorNari (Narihiro Nakamura) over 11 years ago

メインスレッドにファイナライザを登録できないようなパッチになっているようですが、ファイナライザ実行後に止まってしまうことが問題の本質のように思います。
なんで止まってしまうのでしょうね…。

Updated by authorNari (Narihiro Nakamura) over 11 years ago

authorNari (Narihiro Nakamura) wrote:

メインスレッドにファイナライザを登録できないようなパッチになっているようですが

ああっ、すいません、パッチを読み違えてました…。
ファイナライザは実行後にメインスレッドだけfree-listには追加しないようにするパッチなんですね。

Updated by mrkn (Kenta Murata) over 11 years ago

ファイナライザは実行後にメインスレッドだけfree-listには追加しないようにするパッチなんですね。

そのとおりです。

笹田さんに修正方法が場当たり的だと指摘されて、自分もそう思っているのですが、
残念ながら今のところエレガントな解決方法を考えられていません。

Updated by authorNari (Narihiro Nakamura) over 11 years ago

バグの原因がわかりました。
ファイナライザに登録したメインスレッドは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ですが、こんな感じでなおしてみました。
https://gist.github.com/4160845
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;

Updated by nobu (Nobuyoshi Nakada) over 11 years ago

(12/11/28 21:42), authorNari (Narihiro Nakamura) wrote:

ややad-hocですが、こんな感じでなおしてみました。
https://gist.github.com/4160845
VMから見えているものはとりあえずマークしておいて、通常のファイナライズは回避し、強制的なファイナライズでそれらを実行させるようにしています。

VMから見えているものがfinalizeされてしまうというのは単純なマーク漏れのバグと考えていいんじゃないですかね。

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

Actions #7

Updated by authorNari (Narihiro Nakamura) over 11 years ago

  • Status changed from Assigned to Closed
  • % Done changed from 0 to 100

This issue was solved with changeset r38010.
Kenta, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.


  • gc.c (rb_objspace_call_finalizer): finalize_deferred may free up
    a object which is reachable from a part after this function,
    e.g. ruby_vm_destruct(). [ruby-dev:46647] [Bug #7452]

  • test/ruby/test_gc.rb (test_finalizing_main_thread): add a test
    for above.

Updated by authorNari (Narihiro Nakamura) over 11 years ago

2012年11月29日 18:01 Nobuyoshi Nakada :

(12/11/28 21:42), authorNari (Narihiro Nakamura) wrote:

ややad-hocですが、こんな感じでなおしてみました。
https://gist.github.com/4160845
VMから見えているものはとりあえずマークしておいて、通常のファイナライズは回避し、強制的なファイナライズでそれらを実行させるようにしています。

VMから見えているものがfinalizeされてしまうというのは単純なマーク漏れのバグと考えていいんじゃないですかね。

rb_objspace_call_finalizerではマークがすべてのオブジェクトに付いてない状態で実行されるんですよね。
なので、もしかするとここでVMから見えるものだけマークするだけじゃなく、gc_marksとか読んだほうがいいのかもですが…。

とりあえずは、「ほとんど強制的なファイナライズ」の方でコミットしてみました。
test-allなどは通ったのでとりあえずこれで様子を見たいとおもいます。

--
Narihiro Nakamura (nari)

Actions #9

Updated by mrkn (Kenta Murata) about 11 years ago

  • Tracker changed from Bug to Backport
  • Project changed from Ruby master to Backport193
  • Category deleted (core)
  • Status changed from Closed to Assigned
  • Assignee changed from authorNari (Narihiro Nakamura) to usa (Usaku NAKAMURA)
  • Target version deleted (2.0.0)

1.9.3 でも再現するのでバックポートして頂けませんか。

Actions #10

Updated by usa (Usaku NAKAMURA) about 11 years ago

  • Status changed from Assigned to Closed

This issue was solved with changeset r38831.
Kenta, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.


merge revision(s) 38010: [Backport #7452]

* gc.c (rb_objspace_call_finalizer): finalize_deferred may free up
  a object which is reachable from a part after this function,
  e.g. ruby_vm_destruct(). [ruby-dev:46647] [Bug #7452]

* test/ruby/test_gc.rb (test_finalizing_main_thread): add a test
  for above.

* test/rdoc/test_rdoc_servlet.rb:  Tets for above
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0