mcmire-cache 0.3.4

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.
Files changed (43) hide show
  1. data/.gitignore +7 -0
  2. data/CHANGELOG +21 -0
  3. data/Gemfile +22 -0
  4. data/README.md +229 -0
  5. data/Rakefile +41 -0
  6. data/benchmarks/afterrefactor.txt +86 -0
  7. data/benchmarks/midrefactor.txt +89 -0
  8. data/benchmarks/v0.0.2.txt +95 -0
  9. data/benchmarks/v0.0.3.txt +96 -0
  10. data/benchmarks/v0.1.2.txt +94 -0
  11. data/benchmarks/v0.2.1.txt +94 -0
  12. data/benchmarks/v0.2.2.txt +94 -0
  13. data/lib/cache.rb +233 -0
  14. data/lib/cache/active_support_cache_dalli_store.rb +15 -0
  15. data/lib/cache/active_support_cache_file_store.rb +11 -0
  16. data/lib/cache/active_support_cache_memory_store.rb +11 -0
  17. data/lib/cache/active_support_cache_store.rb +37 -0
  18. data/lib/cache/config.rb +27 -0
  19. data/lib/cache/dalli_client.rb +54 -0
  20. data/lib/cache/mem_cache.rb +46 -0
  21. data/lib/cache/memcached.rb +54 -0
  22. data/lib/cache/memcached_rails.rb +34 -0
  23. data/lib/cache/nothing.rb +20 -0
  24. data/lib/cache/redis.rb +44 -0
  25. data/lib/cache/redis_namespace.rb +7 -0
  26. data/lib/cache/version.rb +3 -0
  27. data/mcmire-cache.gemspec +22 -0
  28. data/test/helper.rb +29 -0
  29. data/test/profile/benchmark.rb +258 -0
  30. data/test/shared_tests.rb +169 -0
  31. data/test/test_active_support_cache_dalli_store.rb +77 -0
  32. data/test/test_active_support_cache_file_store.rb +19 -0
  33. data/test/test_active_support_cache_memory_store.rb +12 -0
  34. data/test/test_dalli_client.rb +70 -0
  35. data/test/test_mem_cache.rb +64 -0
  36. data/test/test_memcached.rb +64 -0
  37. data/test/test_memcached_rails.rb +61 -0
  38. data/test/test_memcached_with_binary.rb +17 -0
  39. data/test/test_missing_driver.rb +16 -0
  40. data/test/test_nothing.rb +147 -0
  41. data/test/test_redis.rb +60 -0
  42. data/test/test_redis_namespace.rb +64 -0
  43. metadata +108 -0
@@ -0,0 +1,34 @@
1
+ require 'cache/memcached'
2
+
3
+ module Cache::MemcachedRails
4
+ def self.extended(base)
5
+ base.extend Cache::Memcached
6
+ base.extend Override
7
+ end
8
+
9
+ module Override
10
+ def _exist?(k)
11
+ thread_metal.exist? k
12
+ end
13
+
14
+ def _get(k)
15
+ thread_metal.get k
16
+ end
17
+
18
+ def _get_multi(ks)
19
+ thread_metal.get_multi ks
20
+ end
21
+
22
+ def _cas(k, ttl, &blk)
23
+ if _valid_ttl?(ttl)
24
+ thread_metal.cas k, ttl, &blk
25
+ else
26
+ thread_metal.cas k, &blk
27
+ end
28
+ end
29
+
30
+ def _delete(k)
31
+ thread_metal.delete k
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+
2
+ module Cache::Nothing
3
+ def _get(k); end
4
+
5
+ def _get_multi(ks); {}; end
6
+
7
+ def _set(k, v, ttl); end
8
+
9
+ def _fetch(k, ttl)
10
+ yield if block_given?
11
+ end
12
+
13
+ def _delete(k); end
14
+
15
+ def _flush; end
16
+
17
+ def _exist?(k); end
18
+
19
+ def _stats; end
20
+ end
@@ -0,0 +1,44 @@
1
+ module Cache::Redis
2
+ def after_fork
3
+ @metal.client.reconnect
4
+ end
5
+
6
+ def _get(k)
7
+ if cached_v = @metal.get(k) and cached_v.is_a?(String)
8
+ Marshal.load cached_v
9
+ end
10
+ end
11
+
12
+ def _get_multi(ks)
13
+ ks.inject({}) do |memo, k|
14
+ if v = _get(k)
15
+ memo[k] = v
16
+ end
17
+ memo
18
+ end
19
+ end
20
+
21
+ def _set(k, v, ttl)
22
+ if _valid_ttl?(ttl)
23
+ @metal.setex k, ttl, Marshal.dump(v)
24
+ else
25
+ @metal.set k, Marshal.dump(v)
26
+ end
27
+ end
28
+
29
+ def _delete(k)
30
+ @metal.del k
31
+ end
32
+
33
+ def _flush
34
+ @metal.flushdb
35
+ end
36
+
37
+ def _exist?(k)
38
+ @metal.exists k
39
+ end
40
+
41
+ def _stats
42
+ @metal.info
43
+ end
44
+ end
@@ -0,0 +1,7 @@
1
+ require 'cache/redis'
2
+
3
+ module Cache::RedisNamespace
4
+ def self.extended(base)
5
+ base.extend Cache::Redis
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ class Cache
2
+ VERSION = "0.3.4"
3
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require File.expand_path('../lib/cache/version', __FILE__)
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "mcmire-cache"
7
+ s.version = Cache::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Seamus Abshere","Christoph Grabo", "Elliot Winkler"]
10
+ s.email = ["seamus@abshere.net","chris@dinarrr.com", "elliot.winkler@gmail.com"]
11
+ s.homepage = "https://github.com/seamusabshere/cache"
12
+ s.summary = %q{A unified cache handling interface inspired by libraries like ActiveSupport::Cache::Store, Perl's Cache::Cache, CHI, etc.}
13
+ s.description = %q{Wraps memcached, redis(-namespace), memcache-client, dalli and handles their weirdnesses, including forking}
14
+
15
+ s.rubyforge_project = "cache"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+ end
22
+
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.setup
4
+ require 'test/unit'
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'cache'
8
+
9
+ if ::Bundler.definition.specs['ruby-debug19'].first or ::Bundler.definition.specs['ruby-debug'].first
10
+ require 'ruby-debug'
11
+ end
12
+
13
+ require 'shared_tests'
14
+
15
+ class TestCase < Test::Unit::TestCase
16
+ def raw_client_class
17
+ raise NotImplementedError, "You must add a #raw_client_class method to your test case"
18
+ end
19
+
20
+ def raw_client
21
+ raw_client_class.new
22
+ end
23
+
24
+ def cache
25
+ @cache ||= Cache.new(raw_client).tap {|c| c.flush }
26
+ end
27
+ end
28
+
29
+ ENV['CACHE_DEBUG'] = 'true'
@@ -0,0 +1,258 @@
1
+ # sabshere 2/22/11 thanks to memcached
2
+ HERE = File.dirname(__FILE__)
3
+ $LOAD_PATH << "#{HERE}/../../lib/"
4
+ UNIX_SOCKET_NAME = File.join(ENV['TMPDIR']||'/tmp','memcached')
5
+
6
+ require 'cache'
7
+ require 'benchmark'
8
+ require 'rubygems'
9
+ require 'ruby-debug' if ENV['DEBUG']
10
+ begin; require 'memory'; rescue LoadError; end
11
+
12
+ puts `uname -a`
13
+ puts `ruby -v`
14
+ puts `env | egrep '^RUBY'`
15
+ puts "Ruby #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}"
16
+
17
+ [ ["memcached", "memcached"],
18
+ # ["remix-stash", "remix/stash"],
19
+ # ["astro-remcached", "remcached"], # Clobbers the "Memcached" constant
20
+ ["memcache-client", "memcache"],
21
+ ["cache", "cache"],
22
+ ["kgio", "kgio"],
23
+ ["dalli","dalli"]].each do |gem_name, requirement|
24
+ require requirement
25
+ gem gem_name
26
+ puts "Loaded #{gem_name} #{Gem.loaded_specs[gem_name].version.to_s rescue nil}"
27
+ end
28
+
29
+ # class Remix::Stash
30
+ # # Remix::Stash API doesn't let you set servers
31
+ # @@clusters = {:default => Remix::Stash::Cluster.new(['127.0.0.1:43042', '127.0.0.1:43043'])}
32
+ # end
33
+
34
+ class Dalli::ClientCompat < Dalli::Client
35
+ def set(*args)
36
+ super(*args[0..2])
37
+ end
38
+ def get(*args)
39
+ super(args.first)
40
+ end
41
+ def get_multi(*args)
42
+ super(args.first)
43
+ end
44
+ def append(*args)
45
+ super
46
+ rescue Dalli::DalliError
47
+ end
48
+ def prepend(*args)
49
+ super
50
+ rescue Dalli::DalliError
51
+ end
52
+ end
53
+
54
+ # class Cache::Compat < Cache
55
+ # def set(*args)
56
+ # super(*args[0..2])
57
+ # end
58
+ # def get(*args)
59
+ # super(args.first)
60
+ # end
61
+ # end
62
+
63
+ class Bench
64
+
65
+ def initialize(loops = nil, stack_depth = nil)
66
+ @loops = (loops || 20000).to_i
67
+ @stack_depth = (stack_depth || 0).to_i
68
+
69
+ puts "Loops is #{@loops}"
70
+ puts "Stack depth is #{@stack_depth}"
71
+
72
+ @m_value = Marshal.dump(
73
+ @small_value = ["testing"])
74
+ @m_large_value = Marshal.dump(
75
+ @large_value = [{"test" => "1", "test2" => "2", Object.new => "3", 4 => 4, "test5" => 2**65}] * 2048)
76
+
77
+ puts "Small value size is: #{@m_value.size} bytes"
78
+ puts "Large value size is: #{@m_large_value.size} bytes"
79
+
80
+ @keys = [
81
+ @k1 = "Short",
82
+ @k2 = "Sym1-2-3::45" * 8,
83
+ @k3 = "Long" * 40,
84
+ @k4 = "Medium" * 8,
85
+ @k5 = "Medium2" * 8,
86
+ @k6 = "Long3" * 40]
87
+
88
+ reset_servers
89
+ reset_clients
90
+
91
+ Benchmark.bm(36) do |x|
92
+ @benchmark = x
93
+ end
94
+ end
95
+
96
+ def run(level = @stack_depth)
97
+ level > 0 ? run(level - 1) : run_without_recursion
98
+ end
99
+
100
+ private
101
+
102
+ def reset_servers
103
+ # Kill memcached
104
+ system("killall -9 memcached")
105
+
106
+ # Start memcached
107
+ verbosity = (ENV['DEBUG'] ? "-vv" : "")
108
+ log = "/tmp/memcached.log"
109
+ memcached = ENV['MEMCACHED_COMMAND'] || 'memcached'
110
+ system ">#{log}"
111
+
112
+ # TCP memcached
113
+ (43042..43046).each do |port|
114
+ cmd = "#{memcached} #{verbosity} -U 0 -p #{port} >> #{log} 2>&1 &"
115
+ raise "'#{cmd}' failed to start" unless system(cmd)
116
+ end
117
+ # UDP memcached
118
+ (43052..43053).each do |port|
119
+ cmd = "#{memcached} #{verbosity} -U #{port} -p 0 >> #{log} 2>&1 &"
120
+ raise "'#{cmd}' failed to start" unless system(cmd)
121
+ end
122
+ # Domain socket memcached
123
+ (0..1).each do |i|
124
+ cmd = "#{memcached} -M -s #{UNIX_SOCKET_NAME}#{i} #{verbosity} >> #{log} 2>&1 &"
125
+ raise "'#{cmd}' failed to start" unless system(cmd)
126
+ end
127
+ end
128
+
129
+ def reset_clients
130
+ @clients = {
131
+ "cache:libm:bin" => Cache.wrap(Memcached.new(['127.0.0.1:43042', '127.0.0.1:43043'], :buffer_requests => false, :no_block => false, :namespace => "namespace", :binary_protocol => true)),
132
+ "cache:dalli:bin" => Cache.wrap(Dalli::Client.new(['127.0.0.1:43042', '127.0.0.1:43043'], :marshal => false, :threadsafe => false)),
133
+ "libm:ascii" => Memcached::Rails.new(
134
+ ['127.0.0.1:43042', '127.0.0.1:43043'],
135
+ :buffer_requests => false, :no_block => false, :namespace => "namespace"),
136
+ "libm:ascii:pipeline" => Memcached::Rails.new(
137
+ ['127.0.0.1:43042', '127.0.0.1:43043'],
138
+ :no_block => true, :buffer_requests => true, :noreply => true, :namespace => "namespace"),
139
+ "libm:ascii:udp" => Memcached::Rails.new(
140
+ ["#{UNIX_SOCKET_NAME}0", "#{UNIX_SOCKET_NAME}1"],
141
+ :buffer_requests => false, :no_block => false, :namespace => "namespace"),
142
+ "libm:bin" => Memcached::Rails.new(
143
+ ['127.0.0.1:43042', '127.0.0.1:43043'],
144
+ :buffer_requests => false, :no_block => false, :namespace => "namespace", :binary_protocol => true),
145
+ "libm:bin:buffer" => Memcached::Rails.new(
146
+ ['127.0.0.1:43042', '127.0.0.1:43043'],
147
+ :no_block => true, :buffer_requests => true, :namespace => "namespace", :binary_protocol => true),
148
+ "mclient:ascii" => MemCache.new(['127.0.0.1:43042', '127.0.0.1:43043']),
149
+ # "stash:bin" => Remix::Stash.new(:root),
150
+ "dalli:bin" => Dalli::ClientCompat.new(['127.0.0.1:43042', '127.0.0.1:43043'], :marshal => false, :threadsafe => false)}
151
+ end
152
+
153
+
154
+ def benchmark_clients(test_name, clients = @clients)
155
+ clients.keys.sort.each do |client_name|
156
+ next if client_name == "stash" and test_name == "set-large" # Don't let stash break the world
157
+ client = clients[client_name]
158
+ begin
159
+ yield client
160
+ @benchmark.report("#{test_name}: #{client_name}") { @loops.times { yield client } }
161
+ rescue Exception => e
162
+ puts "#{test_name}:#{client_name} => #{e.inspect}"
163
+ reset_clients
164
+ end
165
+ end
166
+ puts
167
+ end
168
+
169
+ def benchmark_hashes(hashes, test_name)
170
+ hashes.each do |hash_name, int|
171
+ @m = Memcached::Rails.new(:hash => hash_name)
172
+ @benchmark.report("#{test_name}:#{hash_name}") do
173
+ (@loops * 5).times { yield int }
174
+ end
175
+ end
176
+ end
177
+
178
+ def run_without_recursion
179
+ benchmark_clients("set") do |c|
180
+ c.set @k1, @m_value, 0, true
181
+ c.set @k2, @m_value, 0, true
182
+ c.set @k3, @m_value, 0, true
183
+ end
184
+
185
+ benchmark_clients("get") do |c|
186
+ c.get @k1, true
187
+ c.get @k2, true
188
+ c.get @k3, true
189
+ end
190
+
191
+ # benchmark_clients("get-multi") do |c|
192
+ # c.get_multi @keys, true
193
+ # end
194
+ #
195
+ # benchmark_clients("append") do |c|
196
+ # c.append @k1, @m_value
197
+ # c.append @k2, @m_value
198
+ # c.append @k3, @m_value
199
+ # end
200
+ #
201
+ # benchmark_clients("prepend") do |c|
202
+ # c.prepend @k1, @m_value
203
+ # c.prepend @k2, @m_value
204
+ # c.prepend @k3, @m_value
205
+ # end
206
+
207
+ benchmark_clients("delete") do |c|
208
+ c.delete @k1
209
+ c.delete @k2
210
+ c.delete @k3
211
+ end
212
+
213
+ benchmark_clients("get-missing") do |c|
214
+ c.get @k1
215
+ c.get @k2
216
+ c.get @k3
217
+ end
218
+
219
+ # benchmark_clients("append-missing") do |c|
220
+ # c.append @k1, @m_value
221
+ # c.append @k2, @m_value
222
+ # c.append @k3, @m_value
223
+ # end
224
+ #
225
+ # benchmark_clients("prepend-missing") do |c|
226
+ # c.prepend @k1, @m_value
227
+ # c.prepend @k2, @m_value
228
+ # c.prepend @k3, @m_value
229
+ # end
230
+
231
+ benchmark_clients("set-large") do |c|
232
+ c.set @k1, @m_large_value, 0, true
233
+ c.set @k2, @m_large_value, 0, true
234
+ c.set @k3, @m_large_value, 0, true
235
+ end
236
+
237
+ benchmark_clients("get-large") do |c|
238
+ c.get @k1, true
239
+ c.get @k2, true
240
+ c.get @k3, true
241
+ end
242
+
243
+ benchmark_hashes(Memcached::HASH_VALUES, "hash") do |i|
244
+ Rlibmemcached.memcached_generate_hash_rvalue(@k1, i)
245
+ Rlibmemcached.memcached_generate_hash_rvalue(@k2, i)
246
+ Rlibmemcached.memcached_generate_hash_rvalue(@k3, i)
247
+ Rlibmemcached.memcached_generate_hash_rvalue(@k4, i)
248
+ Rlibmemcached.memcached_generate_hash_rvalue(@k5, i)
249
+ Rlibmemcached.memcached_generate_hash_rvalue(@k6, i)
250
+ end
251
+ end
252
+ end
253
+
254
+ Bench.new(ENV["LOOPS"], ENV["STACK_DEPTH"]).run
255
+
256
+ Process.memory.each do |key, value|
257
+ puts "#{key}: #{value/1024.0}M"
258
+ end if Process.respond_to? :memory
@@ -0,0 +1,169 @@
1
+ module SharedTests
2
+ def test_metal
3
+ assert_equal raw_client_class, cache.metal.class
4
+ end
5
+
6
+ def test_wrap_twice
7
+ c = Cache.new(Cache.new(raw_client))
8
+ assert_equal raw_client_class, c.metal.class
9
+ end
10
+
11
+ def test_wrap_absurd
12
+ c = Cache.new(Cache.new(Cache.new(raw_client)))
13
+ assert_equal raw_client_class, c.metal.class
14
+ end
15
+
16
+ def test_get
17
+ assert_equal nil, cache.get('hello')
18
+ cache.set 'hello', 'world'
19
+ assert_equal 'world', cache.get('hello')
20
+ end
21
+
22
+ def test_set
23
+ assert_nothing_raised do
24
+ cache.set 'hello', 'world'
25
+ end
26
+ end
27
+
28
+ def test_set_with_ttl
29
+ cache.set 'hello', 'world', 1
30
+ assert_equal 'world', cache.get('hello')
31
+ sleep 2
32
+ assert_equal nil, cache.get('hello')
33
+ end
34
+
35
+ def test_set_with_zero_ttl_meaning_eternal
36
+ cache.set 'hello', 'world', 0
37
+ assert_equal 'world', cache.get('hello')
38
+ sleep 1
39
+ assert_equal 'world', cache.get('hello')
40
+ end
41
+
42
+ def test_delete
43
+ cache.set 'hello', 'world'
44
+ assert_equal 'world', cache.get('hello')
45
+ cache.delete 'hello'
46
+ assert_equal nil, cache.get('hello')
47
+ end
48
+
49
+ def test_flush
50
+ cache.set 'hello', 'world'
51
+ assert cache.exist?('hello')
52
+ cache.flush
53
+ assert !cache.exist?('hello')
54
+ end
55
+
56
+ def test_exist
57
+ assert !cache.exist?('hello')
58
+ cache.set 'hello', 'world'
59
+ assert cache.exist?('hello')
60
+ end
61
+
62
+ def test_exists
63
+ assert !cache.exists?('hello')
64
+ cache.set 'hello', 'world'
65
+ assert cache.exists?('hello')
66
+ end
67
+
68
+ # This is not working with the Dalli client
69
+ #def test_exist_key_with_nil_value
70
+ # assert !cache.exist?('hello')
71
+ # cache.set 'hello', nil
72
+ # assert cache.exist?('hello')
73
+ #end
74
+
75
+ def test_stats
76
+ assert_nothing_raised do
77
+ cache.stats
78
+ end
79
+ end
80
+
81
+ def test_fetch
82
+ assert_equal nil, cache.fetch('hello')
83
+ assert_equal 'world', cache.fetch('hello') { 'world' }
84
+ end
85
+
86
+ def test_fetch_with_false_boolean
87
+ assert_equal nil, cache.fetch('hello')
88
+ assert_equal false, cache.fetch('hello') { false }
89
+ end
90
+
91
+ def test_fetch_with_expires_in
92
+ assert_equal 'world', cache.fetch('hello', :expires_in => 5) { 'world' }
93
+ end
94
+
95
+ def test_fetch_with_expires_in_stringified
96
+ assert_equal 'world', cache.fetch('hello', 'expires_in' => 5) { 'world' }
97
+ end
98
+
99
+ def test_fetch_with_ignored_options
100
+ assert_equal 'world', cache.fetch('hello', :foo => 'bar') { 'world' }
101
+ end
102
+
103
+ def test_cas
104
+ toggle = lambda do |current|
105
+ current == 'on' ? 'off' : 'on'
106
+ end
107
+
108
+ cache.set 'lights', 'on'
109
+ assert_equal 'on', cache.get('lights')
110
+ cache.cas 'lights', &toggle
111
+ assert_equal 'off', cache.get('lights')
112
+ cache.cas 'lights', &toggle
113
+ assert_equal 'on', cache.get('lights')
114
+ cache.cas 'lights', &toggle
115
+ assert_equal 'off', cache.get('lights')
116
+ end
117
+
118
+ def test_write
119
+ cache.write 'hello', 'world'
120
+ assert_equal 'world', cache.get('hello')
121
+ end
122
+
123
+ def test_write_with_expires_in
124
+ cache.write 'hello', 'world', :expires_in => 1
125
+ assert_equal 'world', cache.get('hello')
126
+ sleep 2
127
+ assert_equal nil, cache.get('hello')
128
+ end
129
+
130
+ def test_write_with_ignored_options
131
+ cache.write 'hello', 'world', :foobar => 'bazboo'
132
+ assert_equal 'world', cache.get('hello')
133
+ end
134
+
135
+ def test_read
136
+ cache.set 'hello', 'world'
137
+ assert_equal 'world', cache.read('hello')
138
+ end
139
+
140
+ def test_increment
141
+ assert !cache.exist?('high-fives')
142
+ assert_equal 1, cache.increment('high-fives')
143
+ assert_equal 1, cache.get('high-fives')
144
+ assert_equal 2, cache.increment('high-fives')
145
+ assert_equal 2, cache.get('high-fives')
146
+ end
147
+
148
+ def test_decrement
149
+ assert !cache.exist?('high-fives')
150
+ assert_equal -1, cache.decrement('high-fives')
151
+ assert_equal -1, cache.get('high-fives')
152
+ assert_equal -2, cache.decrement('high-fives')
153
+ assert_equal -2, cache.get('high-fives')
154
+ end
155
+
156
+ def test_get_multi
157
+ cache.set 'hello', 'world'
158
+ cache.set 'privyet', 'mir'
159
+ assert_equal({ 'hello' => 'world', 'privyet' => 'mir'}, cache.get_multi('hello', 'privyet', 'yoyoyo'))
160
+ end
161
+
162
+ # https://github.com/fauna/memcached/pull/50
163
+ def test_get_set_behavior
164
+ cache.flush
165
+ cache.get 'get_set'
166
+ cache.set 'get_set', 'go'
167
+ assert_equal 'go', cache.get('get_set')
168
+ end
169
+ end