a Hash-like structure, using WeakRef, so that the key value
pairs can be garbage collected as needed. I only need to support []
() and []=(), not the whole range of Hash functions. If the pair has
been collected, I just need a simple nil returned.
It was 1am so instead of writing a full solution, I just pointed out
some closure evilness in his WeakCache. He had written a WeakCache where only values
were "weak" (i.e. could be reclaimed by the GC at any time), and was using a
finalizer to remove the corresponding key from the @cache hash:
That closure captures the whole environment, so the value would never be GCed: the
finalizer itself would point to it!
In the derived WeakCache I posted, I forgot to consider that several
keys could refer to the same value, as reminded by Jeffrey Schwab in
ruby-talk:176720. So the code would now look like (ruby-talk:176778)
# Exercise left to the reader: add thread-safetyclass WeakCacheattr_reader:cachedef initialize(cache=Hash.new)@cache=cache@rev_cache=Hash.new{|h,k|h[k]={}}@reclaim_lambda=lambdado|value_id|if@rev_cache.has_key?value_id@rev_cache[value_id].each_key{|key|@cache.deletekey}@rev_cache.deletevalue_idendendenddef [](key)value_id=@cache[key]returnObjectSpace._id2ref(value_id)unlessvalue_id.nil?nilenddef []=(key,value)@rev_cache[value.object_id][key]=true@cache[key]=value.object_idObjectSpace.define_finalizer(value,@reclaim_lambda)endend
Thinking about the semantics and improvements towards a better WeakHash
In that WeakCache, only values are weak: keys will not perish spontaneously.
Robert Klemme presented the following code, where both the keys and the
values could be GCed:
RKWeakHash=DelegateClass(Hash)class RKWeakHashdef []=(key,val)# !> method redefined; discarding old []=__getobj__[WeakRef.new(key)]=WeakRef.new(val)enddef [](key)# !> method redefined; discarding old []__getobj__[WeakRef.new(key)]enddef each(&b)# !> method redefined; discarding old each__getobj__.eachdo|k,v|b[k.__getobj__,v.__getobj__]unlessk.__getobj__.nil?endselfenddef cleanupdelete_if{|k,v|k.__getobj__.nil?}endend
I'm normally wary of things like DelegateClass, Singleton, WeakRef,
Tempfile... because it's so easy to run into horrible
performance*1 issues. I anticipated that Robert
Klemme's solution would be very slow, and wrote a SimpleWeakHash with
relaxed semantics:
the key->value associations can disappear at any time
all existent associations are wiped out when the GC kicks in
In particular, it removes the tacit requirement that an association
remain alive as long as there is an external reference (i.e. outside the
hash) to either the key or the value. The two advantages we get in exchange are:
increased speed (we'll see how much later)
no special code to keep SimpleWeakHash invariants: we can expose all the methods from Hash, unlike WeakCache
class SimpleWeakHashdef initializeset_internal_hashenddef [](key)__get_hash__[key]enddef []=(key,value)__get_hash__[key]=valueenddef method_missing(meth,*args,&block)__get_hash__.send(meth,*args,&block)endprivatedef __get_hash__old_critical=Thread.criticalThread.critical=true@validorset_internal_hashreturnObjectSpace._id2ref(@hash_id)ensureThread.critical=old_criticalenddef set_internal_hashhash={}@hash_id=hash.object_id@valid=trueObjectSpace.define_finalizer(hash,lambda{@valid=false})hash=nilendend
Note that the #[] and #[]= are redundant, but anyway they're so short...