mcmire-cache 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
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