From: Yusuke ENDOH Date: 2010-02-12T22:32:10+09:00 Subject: [ruby-dev:40409] Re: [Bug:trunk] rubyspec: ObjectSpace.define_finalizer doesn't call self-referencing finalizers FAILED 遠藤です。 2010年2月11日19:08 Yusuke ENDOH : >> 以下のようにすると T_ZOMBIE が見えてしまうのはバグでしょうか。 *snip* > GC に興味のある人に考えてほしいです。 期待していた wanabe さんに「見ない宣言」をされてしまったので、 自分でいい加減に考えてみました。 r18398 で runs finalizers with the object terminated. という 変更が入っていたので、これを revert するような感じでパッチを 書いてみました。T_ZOMBIE は見えなくなったようです。 $ ./ruby -e ' obj = "test" ObjectSpace.define_finalizer(obj, proc { begin; p obj; ensure; p $!; end }) ' また、make check でも objectspace のエラーが 1 つ増えるだけの ようです。 けれど、以下の 2 つのよくわからない挙動に悩まされています。 1) 対象オブジェクトをトップレベルのローカル変数に代入すると ファイナライザが実行されない # 回収されない $ ./ruby -e ' def foo; proc { p :foo }; end s = "foo" ObjectSpace.define_finalizer(s, foo) ' # ローカル変数に入れなければ回収される $ ./ruby -e ' def foo; proc { p :foo }; end ObjectSpace.define_finalizer("foo", foo) ' :foo # トップレベルでなければ回収される $ ./ruby -e ' class C def self.foo; proc { p :foo }; end s = "foo" ObjectSpace.define_finalizer("foo", foo) end ' ファイナライザの proc からトップレベルのローカル変数がなぜか マークされるんでしょうか。 test/ruby/test_objectspace.rb がこの挙動に依存して失敗する ようです。 2) Enumerator#next を使うと回収されない # 回収されない $ ./ruby -e ' module M def self.callback proc { p "finalized" } end def self.run @enum = 1.enum_for(:upto, 3) @enum.next ObjectSpace.define_finalizer("foo", callback) end end M.run ' # next を使わなければ回収される $ ./ruby -e ' module M def self.callback proc { p "finalized" } end def self.run @enum = 1.enum_for(:upto, 3) #@enum.next ObjectSpace.define_finalizer("foo", callback) end end M.run ' "finalized" # Enumerator をインスタンス変数に入れなければ回収される $ ./ruby -e ' module M def self.callback proc { p "finalized" } end def self.run enum = 1.enum_for(:upto, 3) enum.next ObjectSpace.define_finalizer("foo", callback) end end M.run ' "finalized" おそらく Fiber のマシンスタックの中に参照があるのではないかと 思いますが、どう対処したものかわかりません。 rubyspec がこの挙動に依存して失敗します (Enumerator の spec と あわせて実行したときだけ失敗する) 。 もう少し考えてみますが、それでも T_ZOMBIE が見えるよりはいいと 思うので、反対がなければとりあえずコミットしようと思います。 diff --git a/gc.c b/gc.c index 4cfc23c..32b7436 100644 --- a/gc.c +++ b/gc.c @@ -2652,7 +2652,7 @@ static int chain_finalized_object(st_data_t key, st_data_t val, st_data_t arg) { RVALUE *p = (RVALUE *)key, **final_list = (RVALUE **)arg; - if (p->as.basic.flags & FL_FINALIZE) { + if ((p->as.basic.flags & (FL_FINALIZE|FL_MARK)) == FL_FINALIZE) { if (BUILTIN_TYPE(p) != T_ZOMBIE) { p->as.free.flags = FL_MARK | T_ZOMBIE; /* remain marked */ RDATA(p)->dfree = 0; @@ -2661,9 +2661,7 @@ chain_finalized_object(st_data_t key, st_data_t val, st_data_t arg) *final_list = p; return ST_CONTINUE; } - else { - return ST_DELETE; - } + return ST_DELETE; } void @@ -2681,15 +2679,16 @@ rb_objspace_call_finalizer(rb_objspace_t *objspace) /* run finalizers */ if (finalizer_table) { - finalize_deferred(objspace); - while (finalizer_table->num_entries > 0) { + do { + finalize_deferred(objspace); + mark_tbl(objspace, finalizer_table, 0); st_foreach(finalizer_table, chain_finalized_object, - (st_data_t)&final_list); - if (!(p = final_list)) break; - do { - final_list = p->as.free.next; - run_final(objspace, (VALUE)p); - } while ((p = final_list) != 0); + (st_data_t)&deferred_final_list); + } while (deferred_final_list); + if (finalizer_table->num_entries) { + rb_warning("%d finalizer%s left not-invoked due to self-reference", + finalizer_table->num_entries, + finalizer_table->num_entries > 1 ? "s" : ""); } st_free_table(finalizer_table); finalizer_table = 0; -- Yusuke ENDOH