sreeix-cache-money 0.2.24.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/LICENSE +201 -0
  2. data/README +210 -0
  3. data/README.markdown +210 -0
  4. data/TODO +17 -0
  5. data/UNSUPPORTED_FEATURES +13 -0
  6. data/config/environment.rb +16 -0
  7. data/config/memcached.yml +6 -0
  8. data/db/schema.rb +18 -0
  9. data/init.rb +1 -0
  10. data/lib/cache_money.rb +91 -0
  11. data/lib/cash/accessor.rb +83 -0
  12. data/lib/cash/buffered.rb +129 -0
  13. data/lib/cash/config.rb +75 -0
  14. data/lib/cash/fake.rb +83 -0
  15. data/lib/cash/finders.rb +38 -0
  16. data/lib/cash/index.rb +214 -0
  17. data/lib/cash/local.rb +76 -0
  18. data/lib/cash/lock.rb +63 -0
  19. data/lib/cash/mock.rb +154 -0
  20. data/lib/cash/query/abstract.rb +210 -0
  21. data/lib/cash/query/calculation.rb +45 -0
  22. data/lib/cash/query/primary_key.rb +50 -0
  23. data/lib/cash/query/select.rb +16 -0
  24. data/lib/cash/request.rb +3 -0
  25. data/lib/cash/transactional.rb +43 -0
  26. data/lib/cash/util/array.rb +9 -0
  27. data/lib/cash/util/marshal.rb +19 -0
  28. data/lib/cash/write_through.rb +69 -0
  29. data/lib/mem_cached_session_store.rb +49 -0
  30. data/lib/mem_cached_support_store.rb +135 -0
  31. data/lib/memcached_wrapper.rb +263 -0
  32. data/rails/init.rb +38 -0
  33. data/spec/cash/accessor_spec.rb +159 -0
  34. data/spec/cash/active_record_spec.rb +224 -0
  35. data/spec/cash/buffered_spec.rb +9 -0
  36. data/spec/cash/calculations_spec.rb +78 -0
  37. data/spec/cash/finders_spec.rb +430 -0
  38. data/spec/cash/local_buffer_spec.rb +9 -0
  39. data/spec/cash/local_spec.rb +9 -0
  40. data/spec/cash/lock_spec.rb +108 -0
  41. data/spec/cash/marshal_spec.rb +60 -0
  42. data/spec/cash/order_spec.rb +138 -0
  43. data/spec/cash/shared.rb +62 -0
  44. data/spec/cash/transactional_spec.rb +578 -0
  45. data/spec/cash/window_spec.rb +195 -0
  46. data/spec/cash/write_through_spec.rb +245 -0
  47. data/spec/memcached_wrapper_test.rb +209 -0
  48. data/spec/spec_helper.rb +60 -0
  49. metadata +151 -0
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.to_i }
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
data/lib/cash/mock.rb ADDED
@@ -0,0 +1,154 @@
1
+ module Cash
2
+ class Mock < HashWithIndifferentAccess
3
+ attr_accessor :servers
4
+
5
+ class CacheEntry
6
+ attr_reader :value
7
+
8
+ def self.default_ttl
9
+ 1_000_000
10
+ end
11
+
12
+ def self.now
13
+ Time.now
14
+ end
15
+
16
+ def initialize(value, raw, ttl)
17
+ if raw
18
+ @value = value.to_s
19
+ else
20
+ @value = Marshal.dump(value)
21
+ end
22
+
23
+ if ttl.zero?
24
+ @ttl = self.class.default_ttl
25
+ else
26
+ @ttl = ttl
27
+ end
28
+
29
+ @expires_at = self.class.now + @ttl
30
+ end
31
+
32
+
33
+ def expired?
34
+ self.class.now > @expires_at
35
+ end
36
+
37
+ def increment(amount = 1)
38
+ @value = (@value.to_i + amount).to_s
39
+ end
40
+
41
+ def decrement(amount = 1)
42
+ @value = (@value.to_i - amount).to_s
43
+ end
44
+
45
+ def unmarshal
46
+ Marshal.load(@value)
47
+ end
48
+
49
+ def to_i
50
+ @value.to_i
51
+ end
52
+ end
53
+
54
+ attr_accessor :logging
55
+
56
+ def initialize
57
+ @logging = false
58
+ end
59
+
60
+ def get_multi(keys)
61
+ slice(*keys).collect { |k,v| [k, v.unmarshal] }.to_hash_without_nils
62
+ end
63
+
64
+ def set(key, value, ttl = CacheEntry.default_ttl, raw = false)
65
+ log "< set #{key} #{ttl}"
66
+ self[key] = CacheEntry.new(value, raw, ttl)
67
+ log('> STORED')
68
+ end
69
+
70
+ def get(key, raw = false)
71
+ log "< get #{key}"
72
+ unless self.has_unexpired_key?(key)
73
+ log('> END')
74
+ return nil
75
+ end
76
+
77
+ log("> sending key #{key}")
78
+ log('> END')
79
+ if raw
80
+ self[key].value
81
+ else
82
+ self[key].unmarshal
83
+ end
84
+ end
85
+
86
+ def delete(key, options = {})
87
+ log "< delete #{key}"
88
+ if self.has_unexpired_key?(key)
89
+ log "> DELETED"
90
+ super(key)
91
+ else
92
+ log "> NOT FOUND"
93
+ end
94
+ end
95
+
96
+ def incr(key, amount = 1)
97
+ if self.has_unexpired_key?(key)
98
+ self[key].increment(amount)
99
+ self[key].to_i
100
+ end
101
+ end
102
+
103
+ def decr(key, amount = 1)
104
+ if self.has_unexpired_key?(key)
105
+ self[key].decrement(amount)
106
+ self[key].to_i
107
+ end
108
+ end
109
+
110
+ def add(key, value, ttl = CacheEntry.default_ttl, raw = false)
111
+ if self.has_unexpired_key?(key)
112
+ "NOT_STORED\r\n"
113
+ else
114
+ set(key, value, ttl, raw)
115
+ "STORED\r\n"
116
+ end
117
+ end
118
+
119
+ def append(key, value)
120
+ set(key, get(key, true).to_s + value.to_s, nil, true)
121
+ end
122
+
123
+ def namespace
124
+ nil
125
+ end
126
+
127
+ def flush_all
128
+ log('< flush_all')
129
+ clear
130
+ end
131
+
132
+ def stats
133
+ {}
134
+ end
135
+
136
+ def reset_runtime
137
+ [0, Hash.new(0)]
138
+ end
139
+
140
+ def has_unexpired_key?(key)
141
+ self.has_key?(key) && !self[key].expired?
142
+ end
143
+
144
+ def log(message)
145
+ return unless logging
146
+ logger.debug(message)
147
+ end
148
+
149
+ def logger
150
+ @logger ||= ActiveSupport::BufferedLogger.new(Rails.root.join('log/cash_mock.log'))
151
+ end
152
+
153
+ end
154
+ end