On Tue, Jan 24, 2006 at 03:02:04AM +0900, James Edward Gray II wrote:
> The following code, adapted from an old post by Guy Decoux seems to  
> do the trick:
> 
> class WeakCache
>    def initialize( cache = Hash.new )
>       @cache = cache
>    end
[...]
>    def []=( key, value )
>       ObjectSpace.define_finalizer(value, lambda { @cache.delete(key) })
                                            ==============================
This keeps a reference to value so it will never be reclaimed:

class WeakCache
   attr_reader :cache
   def initialize( cache = Hash.new )
      @cache = cache
   end

   def []( key )
      value_id = @cache[key]
      return ObjectSpace._id2ref(value_id) unless value_id.nil?
      nil
   end

   def []=( key, value )
      ObjectSpace.define_finalizer(value, lambda { @cache.delete(key) })
      @cache[key] = value.object_id
   end
end

RUBY_VERSION                                       # => "1.8.4"
RUBY_RELEASE_DATE                                  # => "2005-12-24"
c = WeakCache.new
puts c.cache.size
100000.times{|i| c[i] = i.to_s}
GC.start
puts c.cache.size
__END__
# >> 0
# >> 100000


Compare to this:


class WeakCache
   attr_reader :cache
   def initialize( cache = Hash.new )
      @cache = cache
   end

   def []( key )
      value_id = @cache[key]
      return ObjectSpace._id2ref(value_id) unless value_id.nil?
      nil
   end

   def make_lambda(key)
     lambda{|value| @cache.delete(key) }
   end

   def []=( key, value )
      ObjectSpace.define_finalizer(value, make_lambda(key))
      @cache[key] = value.object_id
   end
end

RUBY_VERSION                                       # => "1.8.4"
RUBY_RELEASE_DATE                                  # => "2005-12-24"
c = WeakCache.new
puts c.cache.size
100000.times{|i| c[i] = i.to_s}
GC.start
puts c.cache.size
__END__
# >> 0
# >> 3



That is very inefficient though, you want something more like


class WeakCache
   attr_reader :cache
   def initialize( cache = Hash.new )
      @cache = cache
      @rev_cache = {}
      @reclaim_method = method(:reclaim_value)
   end

   def []( key )
      value_id = @cache[key]
      return ObjectSpace._id2ref(value_id) unless value_id.nil?
      nil
   end

   def reclaim_value(value_id)
     @cache.delete @rev_cache[value_id]
     @rev_cache.delete value_id
   end

   def []=( key, value )
      @rev_cache[value.object_id] = key
      @cache[key] = value.object_id
      ObjectSpace.define_finalizer(value, @reclaim_method)
   end
end

RUBY_VERSION                                       # => "1.8.4"
RUBY_RELEASE_DATE                                  # => "2005-12-24"
c = WeakCache.new
puts c.cache.size
100000.times{|i| c[i] = i.to_s}
GC.start
puts c.cache.size
__END__
# >> 0
# >> 4


-- 
Mauricio Fernandez