artemk-cache-money 0.2.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +201 -0
- data/README +210 -0
- data/README.markdown +210 -0
- data/TODO +17 -0
- data/UNSUPPORTED_FEATURES +12 -0
- data/config/environment.rb +16 -0
- data/config/memcached.yml +6 -0
- data/db/schema.rb +18 -0
- data/init.rb +1 -0
- data/lib/cache_money.rb +86 -0
- data/lib/cash/accessor.rb +83 -0
- data/lib/cash/buffered.rb +129 -0
- data/lib/cash/config.rb +82 -0
- data/lib/cash/fake.rb +83 -0
- data/lib/cash/finders.rb +38 -0
- data/lib/cash/index.rb +214 -0
- data/lib/cash/local.rb +76 -0
- data/lib/cash/lock.rb +63 -0
- data/lib/cash/mock.rb +154 -0
- data/lib/cash/query/abstract.rb +197 -0
- data/lib/cash/query/calculation.rb +45 -0
- data/lib/cash/query/primary_key.rb +50 -0
- data/lib/cash/query/select.rb +16 -0
- data/lib/cash/request.rb +3 -0
- data/lib/cash/transactional.rb +43 -0
- data/lib/cash/util/active_record.rb +5 -0
- data/lib/cash/util/array.rb +9 -0
- data/lib/cash/util/marshal.rb +19 -0
- data/lib/cash/write_through.rb +69 -0
- data/lib/mem_cached_session_store.rb +50 -0
- data/lib/mem_cached_support_store.rb +141 -0
- data/lib/memcached_wrapper.rb +261 -0
- data/spec/cash/accessor_spec.rb +186 -0
- data/spec/cash/active_record_spec.rb +224 -0
- data/spec/cash/buffered_spec.rb +9 -0
- data/spec/cash/calculations_spec.rb +67 -0
- data/spec/cash/finders_spec.rb +408 -0
- data/spec/cash/local_buffer_spec.rb +9 -0
- data/spec/cash/local_spec.rb +9 -0
- data/spec/cash/lock_spec.rb +108 -0
- data/spec/cash/marshal_spec.rb +60 -0
- data/spec/cash/order_spec.rb +172 -0
- data/spec/cash/transactional_spec.rb +578 -0
- data/spec/cash/window_spec.rb +195 -0
- data/spec/cash/write_through_spec.rb +245 -0
- data/spec/memcached_wrapper_test.rb +209 -0
- data/spec/spec_helper.rb +68 -0
- metadata +168 -0
    
        data/lib/cash/fake.rb
    ADDED
    
    | @@ -0,0 +1,83 @@ | |
| 1 | 
            +
            module Cash
         | 
| 2 | 
            +
              class Fake < HashWithIndifferentAccess
         | 
| 3 | 
            +
                attr_accessor :servers
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def get_multi(*keys)
         | 
| 6 | 
            +
                  slice(*keys).collect { |k,v| [k, Marshal.load(v)] }.to_hash
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def set(key, value, ttl = 0, raw = false)
         | 
| 10 | 
            +
                  self[key] = marshal(value, raw)
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def get(key, raw = false)
         | 
| 14 | 
            +
                  if raw
         | 
| 15 | 
            +
                    self[key]
         | 
| 16 | 
            +
                  else
         | 
| 17 | 
            +
                    if self.has_key?(key)
         | 
| 18 | 
            +
                      Marshal.load(self[key])
         | 
| 19 | 
            +
                    else
         | 
| 20 | 
            +
                      nil
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def incr(key, amount = 1)
         | 
| 26 | 
            +
                  if self.has_key?(key)
         | 
| 27 | 
            +
                    self[key] = (self[key].to_i + amount).to_s
         | 
| 28 | 
            +
                    self[key].to_i
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def decr(key, amount = 1)
         | 
| 33 | 
            +
                  if self.has_key?(key)
         | 
| 34 | 
            +
                    self[key] = (self[key].to_i - amount).to_s
         | 
| 35 | 
            +
                    self[key].to_i
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def add(key, value, ttl = 0, raw = false)
         | 
| 40 | 
            +
                  return false if self.has_key?(key)
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  self[key] = marshal(value, raw)
         | 
| 43 | 
            +
                  true
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def append(key, value)
         | 
| 47 | 
            +
                  set(key, get(key, true).to_s + value.to_s, nil, true)
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def namespace
         | 
| 51 | 
            +
                  nil
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                def flush_all
         | 
| 55 | 
            +
                  clear
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def stats
         | 
| 59 | 
            +
                  {}
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                def reset_runtime
         | 
| 63 | 
            +
                  [0, Hash.new(0)]
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                private
         | 
| 67 | 
            +
                def marshal(value, raw)
         | 
| 68 | 
            +
                  if raw
         | 
| 69 | 
            +
                    value.to_s
         | 
| 70 | 
            +
                  else
         | 
| 71 | 
            +
                    Marshal.dump(value)
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def unmarshal(marshaled_obj)
         | 
| 76 | 
            +
                  Marshal.load(marshaled_obj)
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                def deep_clone(obj)
         | 
| 80 | 
            +
                  unmarshal(marshal(obj))
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
            end
         | 
    
        data/lib/cash/finders.rb
    ADDED
    
    | @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            module Cash
         | 
| 2 | 
            +
              module Finders
         | 
| 3 | 
            +
                def self.included(active_record_class)
         | 
| 4 | 
            +
                  active_record_class.class_eval do
         | 
| 5 | 
            +
                    extend ClassMethods
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                module ClassMethods
         | 
| 10 | 
            +
                  def self.extended(active_record_class)
         | 
| 11 | 
            +
                    class << active_record_class
         | 
| 12 | 
            +
                      alias_method_chain :find_every, :cache
         | 
| 13 | 
            +
                      alias_method_chain :find_from_ids, :cache
         | 
| 14 | 
            +
                      alias_method_chain :calculate, :cache
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def without_cache(&block)
         | 
| 19 | 
            +
                    with_scope(:find => {:readonly => true}, &block)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  # User.find(:first, ...), User.find_by_foo(...), User.find(:all, ...), User.find_all_by_foo(...)
         | 
| 23 | 
            +
                  def find_every_with_cache(options)
         | 
| 24 | 
            +
                    Query::Select.perform(self, options, scope(:find))
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  # User.find(1), User.find(1, 2, 3), User.find([1, 2, 3]), User.find([])
         | 
| 28 | 
            +
                  def find_from_ids_with_cache(ids, options)
         | 
| 29 | 
            +
                    Query::PrimaryKey.perform(self, ids, options, scope(:find))
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  # User.count(:all), User.count, User.sum(...)
         | 
| 33 | 
            +
                  def calculate_with_cache(operation, column_name, options = {})
         | 
| 34 | 
            +
                    Query::Calculation.perform(self, operation, column_name, options, scope(:find))
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
            end
         | 
    
        data/lib/cash/index.rb
    ADDED
    
    | @@ -0,0 +1,214 @@ | |
| 1 | 
            +
            module Cash
         | 
| 2 | 
            +
              class Index
         | 
| 3 | 
            +
                attr_reader :attributes, :options
         | 
| 4 | 
            +
                delegate :each, :hash, :to => :@attributes
         | 
| 5 | 
            +
                delegate :get, :set, :expire, :find_every_without_cache, :calculate_without_cache, :calculate_with_cache, :incr, :decr, :primary_key, :logger, :to => :@active_record
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                DEFAULT_OPTIONS = { :ttl => 1.day }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def initialize(config, active_record, attributes, options = {})
         | 
| 10 | 
            +
                  DEFAULT_OPTIONS[:ttl] = config.ttl || DEFAULT_OPTIONS[:ttl]
         | 
| 11 | 
            +
                  @config, @active_record, @attributes, @options = config, active_record, Array(attributes).collect(&:to_s).sort, DEFAULT_OPTIONS.merge(options)
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def ==(other)
         | 
| 15 | 
            +
                  case other
         | 
| 16 | 
            +
                  when Index
         | 
| 17 | 
            +
                    attributes == other.attributes
         | 
| 18 | 
            +
                  else
         | 
| 19 | 
            +
                    attributes == Array(other)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
                alias_method :eql?, :==
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                module Commands
         | 
| 25 | 
            +
                  def add(object)
         | 
| 26 | 
            +
                    _, new_attribute_value_pairs = old_and_new_attribute_value_pairs(object)
         | 
| 27 | 
            +
                    add_to_index_with_minimal_network_operations(new_attribute_value_pairs, object)
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def update(object)
         | 
| 31 | 
            +
                    old_attribute_value_pairs, new_attribute_value_pairs = old_and_new_attribute_value_pairs(object)
         | 
| 32 | 
            +
                    update_index_with_minimal_network_operations(old_attribute_value_pairs, new_attribute_value_pairs, object)
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def remove(object)
         | 
| 36 | 
            +
                    old_attribute_value_pairs, _ = old_and_new_attribute_value_pairs(object)
         | 
| 37 | 
            +
                    remove_from_index_with_minimal_network_operations(old_attribute_value_pairs, object)
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  def delete(object)
         | 
| 41 | 
            +
                    old_attribute_value_pairs, _ = old_and_new_attribute_value_pairs(object)
         | 
| 42 | 
            +
                    key = cache_key(old_attribute_value_pairs)
         | 
| 43 | 
            +
                    expire(key)
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
                include Commands
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                module Attributes
         | 
| 49 | 
            +
                  def ttl
         | 
| 50 | 
            +
                    @ttl ||= options[:ttl] || config.ttl
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  def order
         | 
| 54 | 
            +
                    @order ||= options[:order] || :asc
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  def limit
         | 
| 58 | 
            +
                    options[:limit]
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  def buffer
         | 
| 62 | 
            +
                    options[:buffer]
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  def window
         | 
| 66 | 
            +
                    limit && limit + buffer
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
                  
         | 
| 69 | 
            +
                  def order_column
         | 
| 70 | 
            +
                    options[:order_column] || 'id'
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
                include Attributes
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def serialize_object(object)
         | 
| 76 | 
            +
                  primary_key? ? object.shallow_clone : object.id
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                def matches?(query)
         | 
| 80 | 
            +
                  query.calculation? ||
         | 
| 81 | 
            +
                  (query.order == [order_column, order] &&
         | 
| 82 | 
            +
                  (!limit || (query.limit && query.limit + query.offset <= limit)))
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                private
         | 
| 86 | 
            +
                def old_and_new_attribute_value_pairs(object)
         | 
| 87 | 
            +
                  old_attribute_value_pairs = []
         | 
| 88 | 
            +
                  new_attribute_value_pairs = []
         | 
| 89 | 
            +
                  @attributes.each do |name|
         | 
| 90 | 
            +
                    new_value = object.attributes[name]
         | 
| 91 | 
            +
                    if object.changed.include? name
         | 
| 92 | 
            +
                      original_value = object.send("#{name}_was")
         | 
| 93 | 
            +
                    else
         | 
| 94 | 
            +
                      original_value = new_value
         | 
| 95 | 
            +
                    end
         | 
| 96 | 
            +
                    old_attribute_value_pairs << [name, original_value]
         | 
| 97 | 
            +
                    new_attribute_value_pairs << [name, new_value]
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
                  [old_attribute_value_pairs, new_attribute_value_pairs]
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                def add_to_index_with_minimal_network_operations(attribute_value_pairs, object)
         | 
| 103 | 
            +
                  if primary_key?
         | 
| 104 | 
            +
                    add_object_to_primary_key_cache(attribute_value_pairs, object)
         | 
| 105 | 
            +
                  else
         | 
| 106 | 
            +
                    add_object_to_cache(attribute_value_pairs, object)
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                def primary_key?
         | 
| 111 | 
            +
                  @attributes.size == 1 && @attributes.first == primary_key
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                def add_object_to_primary_key_cache(attribute_value_pairs, object)
         | 
| 115 | 
            +
                  set(cache_key(attribute_value_pairs), [serialize_object(object)], :ttl => ttl)
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                def cache_key(attribute_value_pairs)
         | 
| 119 | 
            +
                  attribute_value_pairs.flatten.join('/')
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                def add_object_to_cache(attribute_value_pairs, object, overwrite = true)
         | 
| 123 | 
            +
                  return if invalid_cache_key?(attribute_value_pairs)
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                  key, cache_value, cache_hit = get_key_and_value_at_index(attribute_value_pairs)
         | 
| 126 | 
            +
                  if !cache_hit || overwrite
         | 
| 127 | 
            +
                    object_to_add = serialize_object(object)
         | 
| 128 | 
            +
                    objects = (cache_value + [object_to_add]).sort do |a, b|
         | 
| 129 | 
            +
                      (a <=> b) * (order == :asc ? 1 : -1)
         | 
| 130 | 
            +
                    end.uniq
         | 
| 131 | 
            +
                    objects = truncate_if_necessary(objects)
         | 
| 132 | 
            +
                    set(key, objects, :ttl => ttl)
         | 
| 133 | 
            +
                    incr("#{key}/count") { calculate_at_index(:count, attribute_value_pairs) }
         | 
| 134 | 
            +
                  end
         | 
| 135 | 
            +
                end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                def invalid_cache_key?(attribute_value_pairs)
         | 
| 138 | 
            +
                  attribute_value_pairs.collect { |_,value| value }.any? { |x| x.nil? }
         | 
| 139 | 
            +
                end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                def get_key_and_value_at_index(attribute_value_pairs)
         | 
| 142 | 
            +
                  key = cache_key(attribute_value_pairs)
         | 
| 143 | 
            +
                  cache_hit = true
         | 
| 144 | 
            +
                  cache_value = get(key) do
         | 
| 145 | 
            +
                    cache_hit = false
         | 
| 146 | 
            +
                    conditions = attribute_value_pairs.to_hash_without_nils
         | 
| 147 | 
            +
                    find_every_without_cache(:conditions => conditions, :limit => window).collect do |object|
         | 
| 148 | 
            +
                      serialize_object(object)
         | 
| 149 | 
            +
                    end
         | 
| 150 | 
            +
                  end
         | 
| 151 | 
            +
                  [key, cache_value, cache_hit]
         | 
| 152 | 
            +
                end
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                def truncate_if_necessary(objects)
         | 
| 155 | 
            +
                  objects.slice(0, window || objects.size)
         | 
| 156 | 
            +
                end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                def calculate_at_index(operation, attribute_value_pairs)
         | 
| 159 | 
            +
                  conditions = attribute_value_pairs.to_hash_without_nils
         | 
| 160 | 
            +
                  calculate_without_cache(operation, :all, :conditions => conditions)
         | 
| 161 | 
            +
                end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                def update_index_with_minimal_network_operations(old_attribute_value_pairs, new_attribute_value_pairs, object)
         | 
| 164 | 
            +
                  if index_is_stale?(old_attribute_value_pairs, new_attribute_value_pairs)
         | 
| 165 | 
            +
                    remove_object_from_cache(old_attribute_value_pairs, object)
         | 
| 166 | 
            +
                    add_object_to_cache(new_attribute_value_pairs, object)
         | 
| 167 | 
            +
                  elsif primary_key?
         | 
| 168 | 
            +
                    add_object_to_primary_key_cache(new_attribute_value_pairs, object)
         | 
| 169 | 
            +
                  else
         | 
| 170 | 
            +
                    add_object_to_cache(new_attribute_value_pairs, object, false)
         | 
| 171 | 
            +
                  end
         | 
| 172 | 
            +
                end
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                def index_is_stale?(old_attribute_value_pairs, new_attribute_value_pairs)
         | 
| 175 | 
            +
                  old_attribute_value_pairs != new_attribute_value_pairs
         | 
| 176 | 
            +
                end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                def remove_from_index_with_minimal_network_operations(attribute_value_pairs, object)
         | 
| 179 | 
            +
                  if primary_key?
         | 
| 180 | 
            +
                    remove_object_from_primary_key_cache(attribute_value_pairs, object)
         | 
| 181 | 
            +
                  else
         | 
| 182 | 
            +
                    remove_object_from_cache(attribute_value_pairs, object)
         | 
| 183 | 
            +
                  end
         | 
| 184 | 
            +
                end
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                def remove_object_from_primary_key_cache(attribute_value_pairs, object)
         | 
| 187 | 
            +
                  set(cache_key(attribute_value_pairs), [], :ttl => ttl)
         | 
| 188 | 
            +
                end
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                def remove_object_from_cache(attribute_value_pairs, object)
         | 
| 191 | 
            +
                  return if invalid_cache_key?(attribute_value_pairs)
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                  key, cache_value, _ = get_key_and_value_at_index(attribute_value_pairs)
         | 
| 194 | 
            +
                  object_to_remove = serialize_object(object)
         | 
| 195 | 
            +
                  objects = cache_value - [object_to_remove]
         | 
| 196 | 
            +
                  objects = resize_if_necessary(attribute_value_pairs, objects)
         | 
| 197 | 
            +
                  set(key, objects, :ttl => ttl)
         | 
| 198 | 
            +
                end
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                def resize_if_necessary(attribute_value_pairs, objects)
         | 
| 201 | 
            +
                  conditions = attribute_value_pairs.to_hash_without_nils
         | 
| 202 | 
            +
                  key = cache_key(attribute_value_pairs)
         | 
| 203 | 
            +
                  count = decr("#{key}/count") { calculate_at_index(:count, attribute_value_pairs) }
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                  if limit && objects.size < limit && objects.size < count
         | 
| 206 | 
            +
                    find_every_without_cache(:select => :id, :conditions => conditions).collect do |object|
         | 
| 207 | 
            +
                      serialize_object(object)
         | 
| 208 | 
            +
                    end
         | 
| 209 | 
            +
                  else
         | 
| 210 | 
            +
                    objects
         | 
| 211 | 
            +
                  end
         | 
| 212 | 
            +
                end
         | 
| 213 | 
            +
              end
         | 
| 214 | 
            +
            end
         | 
    
        data/lib/cash/local.rb
    ADDED
    
    | @@ -0,0 +1,76 @@ | |
| 1 | 
            +
            module Cash
         | 
| 2 | 
            +
              class Local
         | 
| 3 | 
            +
                delegate :respond_to?, :to => :@remote_cache
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(remote_cache)
         | 
| 6 | 
            +
                  @remote_cache = remote_cache
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def cache_locally
         | 
| 10 | 
            +
                  @remote_cache = LocalBuffer.new(original_cache = @remote_cache)
         | 
| 11 | 
            +
                  yield if block_given?
         | 
| 12 | 
            +
                ensure
         | 
| 13 | 
            +
                  @remote_cache = original_cache
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
                
         | 
| 16 | 
            +
                def autoload_missing_constants
         | 
| 17 | 
            +
                  yield if block_given?
         | 
| 18 | 
            +
                rescue ArgumentError, MemCache::MemCacheError => error
         | 
| 19 | 
            +
                  lazy_load ||= Hash.new { |hash, hash_key| hash[hash_key] = true; false }
         | 
| 20 | 
            +
                  if error.to_s[/undefined class|referred/] && !lazy_load[error.to_s.split.last.constantize]
         | 
| 21 | 
            +
                    retry
         | 
| 22 | 
            +
                  else
         | 
| 23 | 
            +
                    raise error
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                private
         | 
| 28 | 
            +
                
         | 
| 29 | 
            +
                def method_missing(method, *args, &block)
         | 
| 30 | 
            +
                  autoload_missing_constants do
         | 
| 31 | 
            +
                    @remote_cache.send(method, *args, &block)
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              class LocalBuffer
         | 
| 37 | 
            +
                delegate :respond_to?, :to => :@remote_cache
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def initialize(remote_cache)
         | 
| 40 | 
            +
                  @local_cache = {}
         | 
| 41 | 
            +
                  @remote_cache = remote_cache
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def get(key, *options)
         | 
| 45 | 
            +
                  if @local_cache.has_key?(key)
         | 
| 46 | 
            +
                    @local_cache[key]
         | 
| 47 | 
            +
                  else
         | 
| 48 | 
            +
                    @local_cache[key] = @remote_cache.get(key, *options)
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                def set(key, value, *options)
         | 
| 53 | 
            +
                  @remote_cache.set(key, value, *options)
         | 
| 54 | 
            +
                  @local_cache[key] = value
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def add(key, value, *options)
         | 
| 58 | 
            +
                  result = @remote_cache.add(key, value, *options)
         | 
| 59 | 
            +
                  if result == "STORED\r\n"
         | 
| 60 | 
            +
                    @local_cache[key] = value
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                  result
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                def delete(key, *options)
         | 
| 66 | 
            +
                  @remote_cache.delete(key, *options)
         | 
| 67 | 
            +
                  @local_cache.delete(key)
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                private
         | 
| 71 | 
            +
                
         | 
| 72 | 
            +
                def method_missing(method, *args, &block)
         | 
| 73 | 
            +
                  @remote_cache.send(method, *args, &block)
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
            end
         | 
    
        data/lib/cash/lock.rb
    ADDED
    
    | @@ -0,0 +1,63 @@ | |
| 1 | 
            +
            require 'socket'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Cash
         | 
| 4 | 
            +
              class Lock
         | 
| 5 | 
            +
                class Error < RuntimeError; end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                INITIAL_WAIT = 2
         | 
| 8 | 
            +
                DEFAULT_RETRY = 8
         | 
| 9 | 
            +
                DEFAULT_EXPIRY = 30
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def initialize(cache)
         | 
| 12 | 
            +
                  @cache = cache
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def synchronize(key, lock_expiry = DEFAULT_EXPIRY, retries = DEFAULT_RETRY, initial_wait = INITIAL_WAIT)
         | 
| 16 | 
            +
                  if recursive_lock?(key)
         | 
| 17 | 
            +
                    yield
         | 
| 18 | 
            +
                  else
         | 
| 19 | 
            +
                    acquire_lock(key, lock_expiry, retries, initial_wait)
         | 
| 20 | 
            +
                    begin
         | 
| 21 | 
            +
                      yield
         | 
| 22 | 
            +
                    ensure
         | 
| 23 | 
            +
                      release_lock(key)
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def acquire_lock(key, lock_expiry = DEFAULT_EXPIRY, retries = DEFAULT_RETRY, initial_wait = INITIAL_WAIT)
         | 
| 29 | 
            +
                  retries.times do |count|
         | 
| 30 | 
            +
                    response = @cache.add("lock/#{key}", host_pid, lock_expiry)
         | 
| 31 | 
            +
                    return if response == "STORED\r\n"
         | 
| 32 | 
            +
                    return if recursive_lock?(key)
         | 
| 33 | 
            +
                    exponential_sleep(count, initial_wait) unless count == retries - 1
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                  debug_lock(key)
         | 
| 36 | 
            +
                  raise Error, "Couldn't acquire memcache lock on #{@cache.get_server_for_key("lock/#{key}")}"
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def release_lock(key)
         | 
| 40 | 
            +
                  @cache.delete("lock/#{key}")
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def exponential_sleep(count, initial_wait)
         | 
| 44 | 
            +
                  sleep((2**count) / initial_wait)
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                private
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def recursive_lock?(key)
         | 
| 50 | 
            +
                  @cache.get("lock/#{key}") == host_pid
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def debug_lock(key)
         | 
| 54 | 
            +
                  @cache.logger.warn("Cash::Lock[#{key}]: #{@cache.get("lock/#{key}")}") if @cache.respond_to?(:logger) && @cache.logger.respond_to?(:warn)
         | 
| 55 | 
            +
                rescue
         | 
| 56 | 
            +
                  @cache.logger.warn("#{$!}") if @cache.respond_to?(:logger) && @cache.logger.respond_to?(:warn)
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
                
         | 
| 59 | 
            +
                def host_pid
         | 
| 60 | 
            +
                  "#{Socket.gethostname} #{Process.pid}"
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
            end
         |