Created
October 13, 2010 13:12
-
-
Save miloops/624017 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class WeakHash | |
def initialize(cache = Hash.new) | |
@cache = cache | |
@key_map = {} | |
@rev_cache = Hash.new{|h,k| h[k] = {}} | |
@reclaim_value = lambda do |value_id| | |
if value = @rev_cache.delete(value_id) | |
value.each_key{|key| @cache.delete key} | |
end | |
end | |
end | |
def [](key) | |
value_id = @cache[key] | |
value_id && ObjectSpace._id2ref(value_id) | |
rescue RangeError | |
nil | |
end | |
def []=(key, value) | |
key2 = case key | |
when Fixnum, Symbol, true, false, nil | |
key | |
else | |
key.dup | |
end | |
@rev_cache[value.object_id][key2] = true | |
@cache[key2] = value.object_id | |
@key_map[key.object_id] = key2 | |
ObjectSpace.define_finalizer(value, @reclaim_value) | |
end | |
def clear | |
@cache.clear | |
end | |
def delete(key) | |
@cache.delete(key) | |
end | |
end | |
module ActiveRecord | |
# = Active Record Identity Map | |
# | |
# Ensures that each object gets loaded only once by keeping every loaded | |
# object in a map. Looks up objects using the map when referring to them. | |
# | |
# More information on Identity Map pattern: | |
# http://www.martinfowler.com/eaaCatalog/identityMap.html | |
# | |
# == Configuration | |
# | |
# In order to disable IdentityMap, set <tt>config.active_record.identity_map = false</tt> | |
# in your <tt>config/application.rb</tt> file. | |
# | |
# IdentityMap is enabled by default. | |
# | |
module IdentityMap | |
extend ActiveSupport::Concern | |
class << self | |
attr_accessor :repositories | |
attr_accessor :current_repository_name | |
attr_accessor :enabled | |
def current | |
repositories[current_repository_name] ||= Hash.new { |h,k| h[k] = WeakHash.new } | |
end | |
def with_repository(name = :default) | |
old_repository = self.current_repository_name | |
self.current_repository_name = name | |
yield if block_given? | |
ensure | |
self.current_repository_name = old_repository | |
end | |
def without | |
old, self.enabled = self.enabled, false | |
yield if block_given? | |
ensure | |
self.enabled = old | |
end | |
def get(class_name, primary_key) | |
if obj = current[class_name.to_s.to_sym][primary_key] | |
return obj if obj.id == primary_key | |
end | |
nil | |
end | |
def add(record) | |
current[record.class.name.to_s.to_sym][record.id] = record | |
end | |
def remove(record) | |
current[record.class.name.to_s.to_sym].delete(record.id) | |
end | |
def clear | |
current.clear | |
end | |
alias enabled? enabled | |
alias identity_map= enabled= | |
end | |
self.repositories ||= Hash.new | |
self.current_repository_name ||= :default | |
self.enabled = true | |
module InstanceMethods | |
# Reinitialize an Identity Map model object from +coder+. | |
# +coder+ must contain the attributes necessary for initializing an empty | |
# model object. | |
def reinit_with(coder) | |
@attributes_cache = {} | |
dirty = @changed_attributes.keys | |
@attributes.update(coder['attributes'].except(*dirty)) | |
@changed_attributes.update(coder['attributes'].slice(*dirty)) | |
@changed_attributes.delete_if{|k,v| v.eql? @attributes[k]} | |
_run_find_callbacks | |
self | |
end | |
end | |
module ClassMethods | |
def identity_map | |
ActiveRecord::IdentityMap | |
end | |
end | |
end | |
end |
First and second case you expose are not problems, it can perfectly happen that object is no longer in the map, and in that case nil will be returned and it will be fetched again.
On the last case, we could compare not only obj.id but also obj.class.name == class_name, that would prevent that 2 different objects were given the same object_id after the first one was GCed (very tiny chance) and both have same id but are from a different class ("very tiny" * 10000 chance).
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Not necessarily.
This is the situation I'm talking about:
At this point it's true that w[k] == v. But what about just after v is garbage collected? At that point you have a race condition. There are several possibilities:
You cannot assume that the finalizer runs immediately after v is garbage collected -- other objects can be allocated in the meantime, and they might receive the same object_id as you have stored in @cache, even though they are not even Posts.