Bug #5350

WeakRef で謎の NoMethodError

Added by Makoto Kishimoto over 2 years ago. Updated about 1 year ago.

[ruby-dev:<unknown>]
Status:Closed
Priority:Normal
Assignee:Nobuyoshi Nakada
Category:-
Target version:2.0.0
ruby -v:- Backport:

Description

添付のようなスクリプトを r18232 以降の ruby 1.9 で走らせると、以下のように
RefError ではなく NoMethodError が上がってくる、ということが起きます。
( value メソッドを呼ぶ直前に weakref_alive? を呼んでみると true が
返っています )

r18220 以前では正常に動きます( r18221r18231 では core を吐きます)。

ruby 1.9.0 (2008-07-27 revision 0) [x8664-freebsd8.2]
../weakref
bug.rb:20:in part': undefined methodvalue' for [17188951200]:WeakRef (NoMethodError)
from ../weakrefbug.rb:29:in block in part'
from ../weakref_bug.rb:27:in
downto'
from ../weakref
bug.rb:27:in each'
from ../weakref_bug.rb:27:in
part'
from ../weakrefbug.rb:29:in block in part'
from ../weakref_bug.rb:27:in
downto'
from ../weakref
bug.rb:27:in each'
from ../weakref_bug.rb:27:in
part'
from ../weakrefbug.rb:38:in block in <main>'
from ../weakref_bug.rb:36:in
each'
from ../weakref
bug.rb:36:in `'

ruby 1.9.4dev (2011-07-18 trunk 32577) [x8664-freebsd8.2]
../weakref
bug.rb:20:in part': undefined methodvalue' for "../weakrefbug.rb:36:in <main>'":WeakRef (NoMethodError)
from ../weakref_bug.rb:29:in
block in part'
from ../weakref
bug.rb:27:in downto'
from ../weakref_bug.rb:27:in
each'
from ../weakrefbug.rb:27:in part'
from ../weakref_bug.rb:29:in
block in part'
from ../weakref
bug.rb:27:in downto'
from ../weakref_bug.rb:27:in
each'
from ../weakrefbug.rb:27:in part'
from ../weakref_bug.rb:29:in
block in part'
from ../weakref
bug.rb:27:in downto'
from ../weakref_bug.rb:27:in
each'
from ../weakrefbug.rb:27:in part'
from ../weakref_bug.rb:29:in
block in part'
from ../weakref
bug.rb:27:in downto'
from ../weakref_bug.rb:27:in
each'
from ../weakrefbug.rb:27:in part'
from ../weakref_bug.rb:29:in
block in part'
from ../weakref
bug.rb:27:in downto'
from ../weakref_bug.rb:27:in
each'
from ../weakrefbug.rb:27:in part'
from ../weakref_bug.rb:38:in
block in '
from ../weakref
bug.rb:36:in each'
from ../weakref_bug.rb:36:in
'

/home/ksmakoto/ruby-working/ruby-weakref/lib/ruby/1.9.0/weakref.rb:58: [BUG] gcsweep(): unknown data type 0x0(0x801019088)
ruby 1.9.0 (2008-07-25 revision 0) [x86
64-freebsd8.2]

-- control frame ----------
c:0023 p:---- s:0072 b:0072 l:000071 d:000071 CFUNC :id2ref
c:0022 p:0069 s:0068 b:0068 l:000067 d:000067 METHOD /home/ksmakoto/ruby-working/ruby-weakref/lib/ruby/1.9.0/weakref.rb:58
c:0021 p:0007 s:0065 b:0065 l:000064 d:000064 METHOD /home/ksmakoto/ruby-working/ruby-weakref/lib/ruby/1.9.0/delegate.rb:139
c:0020 p:---- s:0058 b:0058 l:000057 d:000057 FINISH :eql?
c:0019 p:0041 s:0056 b:0055 l:000054 d:000054 METHOD ../weakref
bug.rb:20
c:0018 p:0032 s:0048 b:0047 l:000036 d:000046 BLOCK ../weakrefbug.rb:29
c:0017 p:---- s:0046 b:0046 l:000045 d:000045 FINISH :==
c:0016 p:---- s:0044 b:0044 l:000039 d:000043 IFUNC :==
c:0015 p:---- s:0042 b:0042 l:000041 d:000041 CFUNC :downto
c:0014 p:---- s:0040 b:0040 l:000039 d:000039 CFUNC :each
c:0013 p:0074 s:0037 b:0037 l:000036 d:000036 METHOD ../weakref
bug.rb:27
c:0012 p:0032 s:0030 b:0029 l:000018 d:000028 BLOCK ../weakrefbug.rb:29
c:0011 p:---- s:0028 b:0028 l:000027 d:000027 FINISH :==
c:0010 p:---- s:0026 b:0026 l:000021 d:000025 IFUNC :==
c:0009 p:---- s:0024 b:0024 l:000023 d:000023 CFUNC :downto
c:0008 p:---- s:0022 b:0022 l:000021 d:000021 CFUNC :each
c:0007 p:0074 s:0019 b:0019 l:000018 d:000018 METHOD ../weakref
bug.rb:27
c:0006 p:0013 s:0012 b:0012 l:000005 d:000011 BLOCK ../weakrefbug.rb:38
c:0005 p:---- s:0011 b:0011 l:000010 d:000010 FINISH :method
added
c:0004 p:---- s:0009 b:0009 l:000008 d:000008 CFUNC :each
c:0003 p:0047 s:0006 b:0006 l:000005 d:000005 TOP ../weakrefbug.rb:36
c:0002 p:---- s:0004 b:0004 l:000003 d:000003 FINISH :private
class_method

c:0001 p:0000 s:0002 b:0002 l:000001 d:000001 TOP

/home/ksmakoto/ruby-working/ruby-weakref/lib/ruby/1.9.0/weakref.rb:58: [BUG] object allocation during garbage collection phase
ruby 1.9.0 (2008-07-25 revision 0) [x86_64-freebsd8.2]

-- control frame ----------
c:0023 p:---- s:0072 b:0072 l:000071 d:000071 CFUNC :_id2ref
(上と同じなので省略)

c:0001 p:0000 s:0002 b:0002 l:000001 d:000001 TOP

DBG> : "/home/ksmakoto/ruby-working/ruby-weakref/lib/ruby/1.9.0/weakref.rb:58:in _id2ref'"
DBG> : "/home/ksmakoto/ruby-working/ruby-weakref/lib/ruby/1.9.0/weakref.rb:58:in
getobj'"
DBG> : "/home/ksmakoto/ruby-working/ruby-weakref/lib/ruby/1.9.0/delegate.rb:139:in method_missing'"
DBG> : "../weakref_bug.rb:20:in
part'"
DBG> : "../weakrefbug.rb:29:in block in part'"
DBG> : "../weakref_bug.rb:27:in
downto'"
DBG> : "../weakref
bug.rb:27:in each'"
DBG> : "../weakref_bug.rb:27:in
part'"
DBG> : "../weakrefbug.rb:29:in block in part'"
DBG> : "../weakref_bug.rb:27:in
downto'"
DBG> : "../weakref
bug.rb:27:in each'"
DBG> : "../weakref_bug.rb:27:in
part'"
DBG> : "../weakrefbug.rb:38:in block in <main>'"
DBG> : "../weakref_bug.rb:36:in
each'"
DBG> : "../weakref
bug.rb:36:in `'"
Abort trap: 6 (core dumped)

weakref_bug.rb Magnifier (583 Bytes) Makoto Kishimoto, 09/22/2011 06:48 AM

patch1.diff Magnifier (932 Bytes) Masaki Matsushita, 09/23/2011 11:43 PM

patch2.diff Magnifier (1.55 KB) Masaki Matsushita, 09/23/2011 11:43 PM


Subtasks

Bug #5439: r33361以降sample/test.rb:systemがFになるClosed

Associated revisions

Revision 34995
Added by Nobuyoshi Nakada about 2 years ago

Bug #5350
* gc.c: add ObjectSpace::WeakMap. [Bug #5350]
* lib/weakref.rb: use WeakMap instead of _id2ref.

History

#1 Updated by Masaki Matsushita over 2 years ago

=begin
もう少しシンプルなコードにしてみました。
大量のWeakRefオブジェクトを生成すると、たまに正しく参照できない事があるようです。

require "weakref"

class Foo
def hoge; end
end

TIMES = 100000

A = []
TIMES.times do
A.push WeakRef.new Foo.new
end

A.each do |x|
begin
x.hoge
rescue WeakRef::RefError
end
end

正しく参照できなかった場合には、参照先のobject_idに20を足したFixnumが格納されたArrayや、caller、callerの一部と思われるStringなどが返ります。
=end

#2 Updated by Shota Fukumori over 2 years ago

調査結果を報告します.

再現コードさらに短縮--

require "weakref"

class Foo
def initialize
@hoge = "fuga"
end

attr_reader :hoge
end

A = Array.new(5000) do
WeakRef.new Foo.new
end
GC.start

A.each do |x|
begin
x.hoge
rescue WeakRef::RefError
end
end

で,軽く調べまわって見たところ

WeakRefの@@finalの中の@@mutex.synchronizeで何故かdeadlock; recursive locking例外が発生していました.
その所為でNoMethodErrorが起きるWeakRefの対象のオブジェクトがGCされていたとしても@@mutex.synchronize内の処理
で死亡したことがHashに書き込まれないため,WeakRef#weakref_alive?がtrueのままである感じです.

その場合はRangeErrorのほうでひっかかっても良いんじゃないのかなーと思うのですが,何故ファイナライザー中のsynchronizeで
recursive lockingになるのかを調査した所,rbgcfinalize_deferred() が関係してるのかなあ.うーん,よくわからない.

#20 0x0000000100059610 in rbprotect (proc=0x1000776c0 <runsinglefinal>, data=140734799778632, state=0x7fff5fbf9364) at eval.c:709
#21 0x00000001000778db in run
finalizer (objspace=0x10081a800, objid=4303620561, table=4303620400) at gc.c:2919
#22 0x0000000100077a2f in runfinal (objspace=0x10081a800, obj=4303620560) at gc.c:2947
#23 0x0000000100074844 in finalize
list (objspace=0x10081a800, p=0x1008409d0) at gc.c:1937
#24 0x0000000100077a8c in finalizedeferred (objspace=0x10081a800) at gc.c:2959
#25 0x0000000100077abd in rb
gcfinalizedeferred () at gc.c:2966
#26 0x000000010021370d in rbthreadptrexecuteinterruptscommon (th=0x100401db0) at thread.c:1311
#27 0x0000000100213858 in rbthreadptrexecuteinterrupts (th=0x100401db0) at thread.c:1335
#28 0x00000001001fc2f2 in vm
callmethod (th=0x100401db0, cfp=0x1005ffbe0, num=2, blockptr=0x0, flag=0, id=337, me=0x100449970, recv=4303690840) at vminsnhelper.c:676
#29 0x00000001001f39f8 in vmexeccore (th=0x100401db0, initial=0) at insns.def:1015
#30 0x000000010020a58b in vm_exec (th=0x100401db0) at vm.c:1220

#3 Updated by Shota Fukumori over 2 years ago

a = Object.new
mutex = Mutex.new
ObjectSpace.define_finalizer(a) {
begin
mutex.synchronize { p "hi" }
rescue Exception => e
p e
end
}
mutex.synchronize {
a = nil
GC.stress = true
loop{ Object.new }
}

依存関係を無くしてみました.例外が発生しているのが確認できると思います.
(finalizer中の例外で終了することは無いみたいなので手動でrescue → Kernel#pしています.)

#4 Updated by Shota Fukumori over 2 years ago

書こうと思っていて忘れたので追記.

どうやらmutex.synchronize中に何かの拍子でfinalizerが走り,そのfinalizerの中で同じmutexをsynchronizeすると起こってしまう模様.

そしてさっき古い再現しない再現コードを載せてしまいました… 再現するコードはこちらです.

a = Object.new
mutex = Mutex.new
ObjectSpace.define_finalizer(a) {
mutex.synchronize { p "ho" }
}
mutex.synchronize {
a = nil
GC.start
p "hi"
loop{ Object.new }
}

#5 Updated by Masaki Matsushita over 2 years ago

@@final内でのMutex#synchronizeをやめる(添付のpatch1.diff)か、Mutexのロック前にGC.disableして解放後にenableする(patch2)かすれば再現しなくなりました。

#6 Updated by Shota Fukumori over 2 years ago

両方共その場しのぎ,症状に対して防止策をしているだけだと思うのでこの2つのパッチはあまり根本的な解決にはならないと思います.

#7 Updated by Shota Fukumori over 2 years ago

test-allは通ったけど,はたしてこれでいいんだろうか.

diff --git a/thread.c b/thread.c
index 10b73eb..de63c3a 100644
--- a/thread.c
+++ b/thread.c
@@ -1307,7 +1307,7 @@ rbthreadptrexecuteinterruptscommon(rbthreadt *th)
}
th->status = status;

  • if (finalizer_interrupt) {
  • if (finalizerinterrupt && !th->keepingmutexes) { rbgcfinalize_deferred(); }

#8 Updated by Koichi Sasada over 2 years ago

  • ruby -v changed from ruby 1.9.4dev (2011-07-18 trunk 32577) [x86_64-freebsd8.2] to -

(2011/09/23 17:44), Shota Fukumori wrote:

test-allは通ったけど,はたしてこれでいいんだろうか.

 良くないです.Mutex の保持と finalizer の実行は関係ありません.

# このコードだけ見て反応しているので,元の問題は見ておらず,
# そちらの解決策を示していなくてすみません.

--
// SASADA Koichi at atdot dot net

#9 Updated by Koichi Sasada over 2 years ago

 ささだです.

(2011/09/23 18:47), Shota Fukumori (sora_h) wrote:

元の問題がややこしくなっているので一旦まとめると,

a = Object.new
mutex = Mutex.new # finalizer と共有している mutex

ObjectSpace.define_finalizer(a) { # (1) a に対する finalizer
mutex.synchronize { p "ho" } # (2) ここでロックしようとするが, (3) と
# 同じスレッドなので ThreadError 例外発生
# (例外の発生は rescue で確認可能)
}
mutex.synchronize { # (3)
a = nil
GC.start
p "hi"
loop{ Object.new } # このループ中で (1) で定義した finalizer が呼ばれる
}

このようなサンプルコードを実行したときに (3) の mutex.synchronize 中に
オブジェクト a が解放された後,何らかのタイミングで a に対する finalizer
(1) が実行されると, (3) で mutex がロックされたまま同じスレッド (?) で
再び (2) で Mutex#lock を試みるため ThreadError "dead lock; recursive
locking" (thread.c:3555) が発生しています.

で,この問題に対する解決策は

  1. これは仕様という事にしてしまう → これが採用された場合,Glass_saga の GC.disable と GC.enable を挟む パッチ を取り込んで解決の方向?
  2. その他何らかの良い方法

 問題のまとめをありがとうございます.

 ファイナライザの実行は,何時起こるかわからないものなので,デッドロック
の可能性がある処理を行うのは,プログラムが悪い,ということになります.基
本的には,デッドロックを起こさないように書き直す必要がありますが,例えば
上記の場合では,mutex.trylock を利用することで回避することができます.

# 本質的に,何かしら他の対処が必要な話だろうか?

--
// SASADA Koichi at atdot dot net

#10 Updated by Koichi Sasada over 2 years ago

(2011/09/23 18:58), SASADA Koichi wrote:

 ファイナライザの実行は,何時起こるかわからないものなので,デッドロック
の可能性がある処理を行うのは,プログラムが悪い,ということになります.基
本的には,デッドロックを起こさないように書き直す必要がありますが,例えば
上記の場合では,mutex.trylock を利用することで回避することができます.

 すみません,trylock ではダメですね.

 うーん,これはどうするべきかな.ファイナライズ処理を遅延させるように
コードを書き換えれば解決できますが,ちょっと大がかりな気もしますね.問題
が weakref だけなら,大がかりでもいい気がしますが.

 この問題は,pthreadmutex* で管理している状況をシグナルハンドラでどう
処理するか,みたいなのに似ていると思うのですが,そもそも pthread
mutex*
はシグナルセーフじゃないから使えないんだよな.

--
// SASADA Koichi at atdot dot net

#11 Updated by Koichi Sasada over 2 years ago

(2011/09/23 19:02), SASADA Koichi wrote:

 うーん,これはどうするべきかな.ファイナライズ処理を遅延させるように
コードを書き換えれば解決できますが,ちょっと大がかりな気もしますね.問題
が weakref だけなら,大がかりでもいい気がしますが.

 わかりづらい文章になってしまってすみません.weakref 側を,ファイナライ
ザ処理を遅延できるように大がかりに書き換えればよい,という意図でした.例
えば,ファイナライザはこの処理をファイナライザの 後で 実行するように,
例えば Thread 作っちゃうとかすれば解決できます.

 weakref のコードを見ていないので,どのように解決するのがスマートかわか
りませんが,現状ですと「単に weakref のバグです」としか言えないんじゃな
いかと思います.ただ,Ruby 自体に「こういう機能があるともっと綺麗に書け
る」といった話に発展する可能性は否定しません.

--
// SASADA Koichi at atdot dot net

#12 Updated by Shota Fukumori over 2 years ago

ささださんがパッチを書いてくれました.
(許可を得て)MLに転載しておきます.

手元では再現しなくなりましたが, ( の再現コードを使用)
どうでしょうか.

diff --git a/lib/weakref.rb b/lib/weakref.rb
index ee5444a..f3e669b 100644
--- a/lib/weakref.rb
+++ b/lib/weakref.rb
@@ -28,7 +28,7 @@ class WeakRef < Delegator
@@idrevmap = {} # ref -> obj
@@mutex = Mutex.new
@@final = lambda {|id|
- @@mutex.synchronize {
+ pr = lambda{
rids = @@idmap[id]
if rids
for rid in rids
@@ -43,6 +43,19 @@ class WeakRef < Delegator
@@id
map.delete(rid) if @@idmap[rid].empty?
end
}
+ if @@mutex.try
lock
+ begin
+ pr.call
+ ensure
+ @@mutex.unlock
+ end
+ else
+ Thread.new{
+ @@mutex.synchronize{
+ pr.call
+ }
+ }
+ end
}

##

#13 Updated by Nobuyoshi Nakada over 2 years ago

=begin
なかだです。

At Sat, 24 Sep 2011 11:38:54 +0900,
SASADA Koichi wrote in :

(2011/09/23 19:02), SASADA Koichi wrote:

 うーん,これはどうするべきかな.ファイナライズ処理を遅延させるように
コードを書き換えれば解決できますが,ちょっと大がかりな気もしますね.問題
が weakref だけなら,大がかりでもいい気がしますが.

 わかりづらい文章になってしまってすみません.weakref 側を,ファイナライ
ザ処理を遅延できるように大がかりに書き換えればよい,という意図でした.例
えば,ファイナライザはこの処理をファイナライザの 後で 実行するように,
例えば Thread 作っちゃうとかすれば解決できます.

weakrefのマップからの削除処理は、ファイナライザから遅延させてはまずいで
す。削除が完了する前にファイナライザが終了してしまうと、対象のオブジェ
クトは再利用される可能性があり、その時点でweakrefから参照すると意図しな
いオブジェクトが得られることになります。

第一の問題点は、weakrefのファイナライズ処理が再入不能なのに、ファイナラ
イザ自体は再入することがあり得ることです。にある再現コー
ドのように、weakrefのファイナライズ処理で内部的に使っているMutexが外部
からも容易にアクセスできてしまうことも問題といっていいでしょう。

とりあえずファイナライザで再入しないようにするパッチです。

diff --git i/gc.c w/gc.c
index f9a945c..fad49e0 100644
--- i/gc.c
+++ w/gc.c
@@ -345,6 +345,7 @@ typedef struct rbobjspace {
int dont
gc;
int dontlazysweep;
int duringgc;
+ rb
atomict finalizing;
} flags;
struct {
st
table *table;
@@ -387,6 +388,7 @@ int *rubyinitialgcstressptr = &rbobjspace.gcstress;
#define heapsfreed objspace->heap.freed
#define dont
gc objspace->flags.dontgc
#define during
gc objspace->flags.duringgc
+#define finalizing objspace->flags.finalizing
#define finalizer
table objspace->final.table
#define deferredfinallist objspace->final.deferred
#define markstack objspace->markstack.buffer
@@ -2064,7 +2066,7 @@ slot
sweep(rbobjspacet *objspace, struct heapsslot *sweepslot)
}
objspace->heap.finalnum += finalnum;

  • if (deferredfinallist) {
  • if (deferredfinallist && !finalizing) { rbthreadt *th = GETTHREAD(); if (th) { RUBYVMSETFINALIZERINTERRUPT(th); @@ -2968,7 +2970,10 @@ finalizedeferred(rbobjspacet *objspace) void rbgcfinalize_deferred(void) {
  • finalizedeferred(&rbobjspace);
  • rbobjspacet *objspace = &rb_objspace;
  • if (ATOMIC_SET(finalizing, 1)) return;
  • finalize_deferred(objspace);
  • ATOMIC_SET(finalizing, 0);
    }

    static int
    @@ -3020,6 +3025,8 @@ rbobjspacecallfinalizer(rbobjspacet objspace)
    /
    run finalizers */
    gc
    clearmarkonsweepslots(objspace);

  • if (ATOMICSET(finalizing, 1)) return;
    +
    do {
    /* XXX: this loop will make no sense /
    /
    because mark will not be removed */
    @@ -3082,6 +3089,7 @@ rb
    objspacecallfinalizer(rbobjspacet *objspace)

    stfreetable(finalizertable);
    finalizer
    table = 0;

  • ATOMIC_SET(finalizing, 0);
    }

    void
    @@ -3089,7 +3097,7 @@ rbgc(void)
    {
    rb
    objspacet *objspace = &rbobjspace;
    garbage_collect(objspace);

  • finalize_deferred(objspace);

  • if (!finalizing) finalizedeferred(objspace);
    free
    unused_heaps(objspace);
    }

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

#14 Updated by Nobuyoshi Nakada over 2 years ago

=begin
なかだです。

Thu, 29 Sep 2011 20:48:38 +0900,
Nobuyoshi Nakada wrote in :

第一の問題点は、weakrefのファイナライズ処理が再入不能なのに、ファイナラ
イザ自体は再入することがあり得ることです。にある再現コー
ドのように、weakrefのファイナライズ処理で内部的に使っているMutexが外部
からも容易にアクセスできてしまうことも問題といっていいでしょう。

ObjectSpace::WeakMapを追加してそれを使うようにしたパッチです。

diff --git i/gc.c w/gc.c
index fad49e0..ba0cac4 100644
--- i/gc.c
+++ w/gc.c
@@ -402,6 +402,9 @@ int *rubyinitialgcstressptr = &rbobjspace.gcstress;
#define nonspecialobjid(obj) (VALUE)((SIGNEDVALUE)(obj)|FIXNUMFLAG)

static void rbobjspacecallfinalizer(rbobjspacet *objspace);
+static VALUE define
final0(VALUE obj, VALUE block);
+VALUE rbdefinefinal(VALUE obj, VALUE block);
+VALUE rbundefinefinal(VALUE obj);

#if defined(ENABLEVMOBJSPACE) && ENABLEVMOBJSPACE
rbobjspacet *
@@ -2829,6 +2832,12 @@ oseachobj(int argc, VALUE *argv, VALUE os)
static VALUE
undefinefinal(VALUE os, VALUE obj)
{
+ return rb
undefinefinal(obj);
+}
+
+VALUE
+rb
undefinefinal(VALUE obj)
+{
rb
objspacet *objspace = &rbobjspace;
stdatat data = obj;
rbcheckfrozen(obj);
@@ -2849,9 +2858,7 @@ undefinefinal(VALUE os, VALUE obj)
static VALUE
define
final(int argc, VALUE *argv, VALUE os)
{
- rbobjspacet *objspace = &rbobjspace;
- VALUE obj, block, table;
- st
data_t data;
+ VALUE obj, block;

  rb_scan_args(argc, argv, "11", &obj, &block);
  rb_check_frozen(obj);

@@ -2862,6 +2869,16 @@ definefinal(int argc, VALUE *argv, VALUE os)
rb
raise(rbeArgError, "wrong type argument %s (should be callable)",
rb
objclassname(block));
}
+ return define
final0(obj, block);
+}
+
+static VALUE
+definefinal0(VALUE obj, VALUE block)
+{
+ rb
objspacet *objspace = &rbobjspace;
+ VALUE table;
+ stdatat data;
+
if (!FLABLE(obj)) {
rb
raise(rbeArgError, "cannot define finalizer for %s",
rb
objclassname(obj));
@@ -2883,6 +2900,17 @@ define
final(int argc, VALUE *argv, VALUE os)
return block;
}

+VALUE
+rbdefinefinal(VALUE obj, VALUE block)
+{
+ rbcheckfrozen(obj);
+ if (!rbrespondto(block, rbintern("call"))) {
+ rb
raise(rbeArgError, "wrong type argument %s (should be callable)",
+ rb
objclassname(block));
+ }
+ return define
final0(obj, block);
+}
+
void
rbgccopyfinalizer(VALUE dest, VALUE obj)
{
@@ -3656,6 +3684,157 @@ gc
profiletotaltime(VALUE self)
* See also GC.count, GC.mallocallocatedsize and GC.malloc_allocations
*/

+struct weakmap {
+ sttable obj2wmap; / obj -> [ref,...] */
+ st
table wmap2obj; / ref -> obj /
+ VALUE final;
+};
+
+static int
+wmapmarkmap(stdatat key, stdatat val, stdatat arg)
+{
+ FLSET((VALUE)val, FLMARK);
+ return STCONTINUE;
+}
+
+static void
+wmap
mark(void *ptr)
+{
+ struct weakmap *w = ptr;
+ stforeach(w->obj2wmap, wmapmarkmap, 0);
+ rb
gcmark(w->final);
+}
+
+static int
+wmap
freemap(stdatat key, stdatat val, stdatat arg)
+{
+ rb
aryresize((VALUE)val, 0);
+ return ST
CONTINUE;
+}
+
+static void
+wmapfree(void *ptr)
+{
+ struct weakmap *w = ptr;
+ st
foreach(w->obj2wmap, wmapfreemap, 0);
+ stclear(w->obj2wmap);
+ st
clear(w->wmap2obj);
+}
+
+sizet rbarymemsize(VALUE ary);
+static int
+wmap
memsizemap(stdatat key, stdatat val, stdatat arg)
+{
+ *(size
t *)arg += rbarymemsize((VALUE)val);
+ return STCONTINUE;
+}
+
+static size
t
+wmapmemsize(const void *ptr)
+{
+ size
t size;
+ const struct weakmap *w = ptr;
+ if (!w) return 0;
+ size = sizeof(
w);
+ size += stmemsize(w->obj2wmap);
+ size += st
memsize(w->wmap2obj);
+ stforeach(w->obj2wmap, wmapmemsizemap, (stdatat)&size);
+ return size;
+}
+
+static const rb
datatypet weakmaptype = {
+ "weakmap",
+ {
+ wmap
mark,
+ wmapfree,
+ wmap
memsize,
+ }
+};
+
+static VALUE
+wmapallocate(VALUE klass)
+{
+ struct weakmap *w;
+ VALUE obj = TypedData
MakeStruct(klass, struct weakmap, &weakmaptype, w);
+ w->obj2wmap = stinitnumtable();
+ w->wmap2obj = stinitnumtable();
+ w->final = rbobjmethod(obj, ID2SYM(rbintern("finalize")));
+ return obj;
+}
+
+static VALUE
+wmap
finalize(VALUE self, VALUE obj)
+{
+ stdatat data;
+ VALUE rids;
+ long i;
+ struct weakmap w;
+
+ TypedDataGetStruct(self, struct weakmap, &weakmaptype, w);
+ obj = NUM2PTR(obj);
+
+ data = (st
datat)obj;
+ if (st
delete(w->obj2wmap, &data, &data)) {
+ rids = (VALUE)data;
+ for (i = 0; i < RARRAYLEN(rids); ++i) {
+ data = (st
datat)RARRAYPTR(rids)[i];
+ stdelete(w->wmap2obj, &data, NULL);
+ }
+ }
+
+ data = (st
datat)obj;
+ if (st
delete(w->wmap2obj, &data, &data)) {
+ VALUE rid = (VALUE)data;
+ int empty = 1;
+ if (stlookup(w->obj2wmap, (stdatat)rid, &data)) {
+ rb
arydelete((VALUE)data, obj);
+ empty = !RARRAY
LEN((VALUE)data);
+ }
+ if (empty) {
+ data = (stdatat)rid;
+ stdelete(w->obj2wmap, &data, &data);
+ }
+ }
+}
+
+static VALUE
+wmap
aset(VALUE self, VALUE wmap, VALUE orig)
+{
+ stdatat data;
+ VALUE rids;
+ struct weakmap *w;
+
+ TypedDataGetStruct(self, struct weakmap, &weakmaptype, w);
+ rb
definefinal(orig, w->final);
+ rb
definefinal(wmap, w->final);
+ if (!st
lookup(w->obj2wmap, (stdatat)orig, &data)) {
+ rids = rbarytmpnew(1);
+ st
insert(w->obj2wmap, (stdatat)orig, (stdatat)rids);
+ }
+ else {
+ rids = (VALUE)data;
+ }
+ rbarypush(rids, orig);
+ stinsert(w->wmap2obj, (stdatat)wmap, (stdatat)orig);
+ return nonspecial
objid(orig);
+}
+
+static VALUE
+wmap
aref(VALUE self, VALUE wmap)
+{
+ stdatat data;
+ VALUE obj;
+ struct weakmap *w;
+ rbobjspacet *objspace = &rbobjspace;
+
+ TypedData
GetStruct(self, struct weakmap, &weakmaptype, w);
+ if (!stlookup(w->wmap2obj, (stdatat)wmap, &data)) return Qnil;
+ obj = (VALUE)data;
+ if (!is
idvalue(objspace, obj)) return Qnil;
+ if (!is
live_object(objspace, obj)) return Qnil;
+ return obj;
+}
+
/

* The GC module provides an interface to Ruby's mark and
* sweep garbage collection mechanism. Some of the underlying methods
@@ -3710,6 +3889,14 @@ Init_GC(void)

  rb_define_module_function(rb_mObSpace, "count_objects", count_objects, -1);
  • {
  • VALUE rbcWeakMap = rbdefineclassunder(rbmObSpace, "WeakMap", rbcObject);
  • rbdefineallocfunc(rbcWeakMap, wmap_allocate);
  • rbdefinemethod(rbcWeakMap, "[]=", wmapaset, 2);
  • rbdefinemethod(rbcWeakMap, "[]", wmaparef, 1);
  • rbdefineprivatemethod(rbcWeakMap, "finalize", wmap_finalize, 1);
  • }
    +
    #if CALCEXACTMALLOCSIZE
    rb
    definesingletonmethod(rbmGC, "mallocallocatedsize", gcmallocallocatedsize, 0);
    rbdefinesingletonmethod(rbmGC, "mallocallocations", gcmalloc_allocations, 0);
    diff --git i/lib/weakref.rb w/lib/weakref.rb
    index ee5444a..1fea9a9 100644
    --- i/lib/weakref.rb
    +++ w/lib/weakref.rb
    @@ -1,5 +1,4 @@
    require "delegate"
    -require 'thread'

    Weak Reference class that allows a referenced object to be

    garbage-collected. A WeakRef may be used exactly like the object it

    @@ -16,6 +15,7 @@ require 'thread'

    p foo.to_s # should raise exception (recycled)

    class WeakRef < Delegator

  • @@__map = ::ObjectSpace::WeakMap.new

    ##

    RefError is raised when a referenced object has been recycled by the

    @@ -24,51 +24,17 @@ class WeakRef < Delegator
    class RefError < StandardError
    end

  • @@id_map = {} # obj -> [ref,...]

  • @@idrevmap = {} # ref -> obj

  • @@mutex = Mutex.new

  • @@final = lambda {|id|

  • @@mutex.synchronize {

  •  rids = @@id_map[id]
    
  •  if rids
    
  •    for rid in rids
    
  •      @@id_rev_map.delete(rid)
    
  •    end
    
  •    @@id_map.delete(id)
    
  •  end
    
  •  rid = @@id_rev_map[id]
    
  •  if rid
    
  •    @@id_rev_map.delete(id)
    
  •    @@id_map[rid].delete(id)
    
  •    @@id_map.delete(rid) if @@id_map[rid].empty?
    
  •  end
    
  • }

  • }

    ##

    Creates a weak reference to +orig+

    def initialize(orig)

  • @_id = orig.objectid

  • ObjectSpace.define_finalizer orig, @@final

  • ObjectSpace.define_finalizer self, @@final

  • @@mutex.synchronize {

  •  @@id_map[@__id] = [] unless @@id_map[@__id]
    
  • }

  • @@idmap[@id].push self.objectid

  • @@idrevmap[self.objectid] = @_id

  • @@__map[self] = orig
    super
    end

    def getobj # :nodoc:

  • unless @@idrevmap[self.objectid] == @_id

  •  Kernel::raise RefError, "Invalid Reference - probably recycled", Kernel::caller(2)
    
  • end

  • begin

  •  ObjectSpace._id2ref(@__id)
    
  • rescue RangeError

  • @@__map[self] or
    Kernel::raise RefError, "Invalid Reference - probably recycled", Kernel::caller(2)

  • end
    end

    def setobj(obj) # :nodoc:
    @@ -78,7 +44,7 @@ class WeakRef < Delegator

    Returns true if the referenced object is still alive.

    def weakref_alive?

  • @@idrevmap[self.objectid] == @_id

  • !!@@__map[self]
    end
    end

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

#15 Updated by Koichi Sasada about 2 years ago

  • Status changed from Open to Assigned
  • Assignee set to Nobuyoshi Nakada

#16 Updated by Nobuyoshi Nakada about 2 years ago

  • Status changed from Assigned to Closed

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


Bug #5350
* gc.c: add ObjectSpace::WeakMap. [Bug #5350]
* lib/weakref.rb: use WeakMap instead of _id2ref.

#17 Updated by Zachary Scott over 1 year ago

Could someone help me understand WeakMap?

I'm looking for a good example, or use-case for it to use in documentation.

Thank you!

#18 Updated by Yui NARUSE over 1 year ago

  • Status changed from Closed to Assigned

#19 Updated by Nobuyoshi Nakada about 1 year ago

  • Status changed from Assigned to Closed

Please file new ticket for documentation.

Also available in: Atom PDF