require 'thread' ## # A container for data structures that hold weakly on their # contents. module Weak ## # Weak::RefError is raised when the system is unable to # dereference an object_id. class RefError < RuntimeError; end ## # A key for Weak::IdentityKeyHash that holds weakly on its # contents. class Key ## # The object being weakly held by this key. attr_reader :internal_id ## # Map from an object to a list of its references ID_MAP = {} ## # Map from a reference to its object ID_REV_MAP = {} ## # Creates a finalizer for the object referenced by # +internal_id+ that removes that object from +hash+. def self.make_finalizer(internal_id, hash_id) return proc do |id| Thread.exclusive do rids = ID_MAP[id] if rids then for rid in rids ID_REV_MAP.delete rid end ID_MAP.delete id end rid = ID_REV_MAP[id] if rid then ID_REV_MAP.delete id ID_MAP[rid].delete id ID_MAP.delete rid if ID_MAP[rid].empty? end end unless hash_id.nil? then hash = ObjectSpace._id2ref hash_id hash.delete_if do |k, v| k.internal_id == internal_id rescue true end unless hash.nil? end end end ## # Create a new WeakKey from +orig+ that will remove itself # from +hash+when it is no longer referenced. def initialize(orig, hash) @internal_id = orig.object_id ObjectSpace.define_finalizer(orig, self.class.make_finalizer(@internal_id, hash.object_id)) ObjectSpace.define_finalizer(self, self.class.make_finalizer(@internal_id, nil)) Thread.exclusive do ID_MAP[@internal_id] = [] unless ID_MAP[@internal_id] end ID_MAP[@internal_id].push self.object_id ID_REV_MAP[self.object_id] = @internal_id end ## # A WeakKey's hash is the held object's object_id. def hash @internal_id end ## # A WeakKey is equal to +other+ if +other+ has the same # internal_id or object_id. def eql?(other) if other.respond_to? :internal_id then return other.internal_id == @internal_id else return other.object_id == @internal_id end return false end ## # Creates a string that represents the internal state of this # object. def inspect "#<#{self.class}:0x%x @internal_id=0x%x>" % [object_id, @internal_id] end ## # Retrieves an object from its id. def get_obj unless ID_REV_MAP[self.object_id] == @internal_id then raise RefError, "Illegal Reference - probably recycled", caller(2) end begin return ObjectSpace._id2ref(@internal_id) rescue RangeError raise RefError, "Illegal Reference - probably recycled", caller(2) end end ## # Retrives +obj+'s WeakKey reference. def self.by_obj(obj) ref = nil Thread.exclusive do rids = ID_MAP[obj.object_id] if rids.nil? or rids.empty? then raise RefError, "Reference does not exist" end for rid in rids do begin ref = ObjectSpace._id2ref rid rescue RangeError next end return ref unless ref.nil? end raise RefError, "Reference does not exist" end end end # class Weak::Key ## # Creates a Hash that holds weakly on its keys. class IdentityKeyHash < Hash ## # Retrieves +key+ from the hash, if it still exists. def [](key) return super(Weak::Key.by_obj(key)) end ## # Sets +key+ of the hash to +value+. +value+ is weakly held. def []=(key, value) ref_key = nil unless self.has_key? key then ref_key = Weak::Key.new key, self else ref_key = Weak::Key.by_id key.object_id end super ref_key, value end ## # Hash's #each alias internal_each each ## # Iterates over the hash, retrieving the weakly referenced # objects. def each key = nil super do |ref_key, value| begin key = ref_key.get_obj rescue RefError next end yield key, value end end end # class Weak::IdentityKeyHash end # module Weak