cache 0.1.3 → 0.2.0
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/.gitignore +1 -0
- data/BENCHMARKS +98 -0
- data/README.md +153 -0
- data/Rakefile +26 -7
- data/cache.gemspec +1 -0
- data/lib/cache.rb +80 -7
- data/lib/cache/storage.rb +109 -30
- data/lib/cache/version.rb +1 -1
- data/test/helper.rb +1 -1
- data/test/profile/benchmark.rb +10 -10
- data/test/shared_tests.rb +90 -1
- data/test/test_memcached_rails_storage.rb +10 -10
- data/test/test_memcached_storage.rb +10 -10
- metadata +25 -11
- data/README.rdoc +0 -87
data/.gitignore
CHANGED
data/BENCHMARKS
CHANGED
|
@@ -1,3 +1,101 @@
|
|
|
1
|
+
# sabshere 3/4/11 v0.2.0
|
|
2
|
+
|
|
3
|
+
vidalia:~/github/cache (master) $ ruby test/profile/benchmark.rb
|
|
4
|
+
Darwin vidalia 9.8.0 Darwin Kernel Version 9.8.0: Wed Jul 15 16:55:01 PDT 2009; root:xnu-1228.15.4~1/RELEASE_I386 i386
|
|
5
|
+
ruby 1.8.7 (2010-05-25 patchlevel 266) [i686-darwin9.8.0]
|
|
6
|
+
RUBY_VERSION=ruby-1.8.7-head
|
|
7
|
+
Ruby 1.8.7p266
|
|
8
|
+
Loaded memcached 1.2
|
|
9
|
+
Loaded remix-stash 1.1.3
|
|
10
|
+
Loaded memcache-client 1.8.5
|
|
11
|
+
Loaded cache 0.1.2 # i just hadn't released the gem yet
|
|
12
|
+
Loaded kgio 2.3.2
|
|
13
|
+
Loaded dalli 1.0.2
|
|
14
|
+
Loops is 20000
|
|
15
|
+
Stack depth is 0
|
|
16
|
+
Small value size is: 13 bytes
|
|
17
|
+
Large value size is: 4158 bytes
|
|
18
|
+
No matching processes belonging to you were found
|
|
19
|
+
user system total real
|
|
20
|
+
set: cache:dalli:bin 5.720000 1.860000 7.580000 ( 10.301808)
|
|
21
|
+
set: cache:libm:bin 1.250000 1.270000 2.520000 ( 5.892224)
|
|
22
|
+
set: dalli:bin 5.430000 1.860000 7.290000 ( 9.966409)
|
|
23
|
+
set: libm:ascii 0.760000 1.330000 2.090000 ( 5.348975)
|
|
24
|
+
set: libm:ascii:pipeline 0.280000 0.010000 0.290000 ( 0.297230)
|
|
25
|
+
set: libm:ascii:udp 0.650000 0.710000 1.360000 ( 3.593454)
|
|
26
|
+
set: libm:bin 0.640000 1.360000 2.000000 ( 5.285160)
|
|
27
|
+
set: libm:bin:buffer 0.270000 0.160000 0.430000 ( 1.234561)
|
|
28
|
+
set: mclient:ascii 11.330000 3.780000 15.110000 ( 16.174666)
|
|
29
|
+
set: stash:bin 3.420000 1.330000 4.750000 ( 8.016146)
|
|
30
|
+
|
|
31
|
+
get: cache:dalli:bin 5.870000 1.950000 7.820000 ( 10.644558)
|
|
32
|
+
get: cache:libm:bin 1.370000 1.200000 2.570000 ( 5.946502)
|
|
33
|
+
get: dalli:bin 5.580000 2.070000 7.650000 ( 10.361689)
|
|
34
|
+
get: libm:ascii 0.980000 1.300000 2.280000 ( 5.455222)
|
|
35
|
+
get: libm:ascii:pipeline 1.030000 1.590000 2.620000 ( 5.875592)
|
|
36
|
+
get: libm:ascii:udp 0.820000 0.730000 1.550000 ( 3.515632)
|
|
37
|
+
get: libm:bin 0.830000 1.330000 2.160000 ( 5.381290)
|
|
38
|
+
get: libm:bin:buffer 0.900000 1.630000 2.530000 ( 5.761412)
|
|
39
|
+
get: mclient:ascii 13.630000 3.870000 17.500000 ( 17.912800)
|
|
40
|
+
get: stash:bin 3.100000 1.340000 4.440000 ( 7.667182)
|
|
41
|
+
|
|
42
|
+
delete: cache:dalli:bin 6.270000 2.390000 8.660000 ( 11.313843)
|
|
43
|
+
delete: cache:libm:bin 2.190000 1.560000 3.750000 ( 7.459292)
|
|
44
|
+
delete: dalli:bin 5.660000 2.340000 8.000000 ( 10.374507)
|
|
45
|
+
delete: libm:ascii 1.850000 1.570000 3.420000 ( 6.922356)
|
|
46
|
+
delete: libm:ascii:pipeline 0.230000 0.010000 0.240000 ( 0.242642)
|
|
47
|
+
delete: libm:ascii:udp 1.690000 0.930000 2.620000 ( 4.749489)
|
|
48
|
+
delete: libm:bin 1.780000 1.530000 3.310000 ( 6.768449)
|
|
49
|
+
delete: libm:bin:buffer 1.890000 1.880000 3.770000 ( 7.149319)
|
|
50
|
+
delete: mclient:ascii 11.940000 4.040000 15.980000 ( 16.423645)
|
|
51
|
+
delete:stash:bin => #<NoMethodError: undefined method `delete' for #<Remix::Stash:0x1249824>>
|
|
52
|
+
|
|
53
|
+
get-missing: cache:dalli:bin 5.530000 2.140000 7.670000 ( 10.187561)
|
|
54
|
+
get-missing: cache:libm:bin 2.200000 1.510000 3.710000 ( 6.953625)
|
|
55
|
+
get-missing: dalli:bin 5.230000 2.160000 7.390000 ( 9.891539)
|
|
56
|
+
get-missing: libm:ascii 1.960000 1.420000 3.380000 ( 6.459433)
|
|
57
|
+
get-missing: libm:ascii:pipeline 2.080000 1.860000 3.940000 ( 6.984890)
|
|
58
|
+
get-missing: libm:ascii:udp 1.730000 0.910000 2.640000 ( 4.582510)
|
|
59
|
+
get-missing: libm:bin 1.950000 1.470000 3.420000 ( 6.854775)
|
|
60
|
+
get-missing: libm:bin:buffer 2.060000 1.900000 3.960000 ( 7.180759)
|
|
61
|
+
get-missing: mclient:ascii 12.350000 4.100000 16.450000 ( 16.986064)
|
|
62
|
+
get-missing: stash:bin 3.110000 1.410000 4.520000 ( 7.908972)
|
|
63
|
+
|
|
64
|
+
set-large: cache:dalli:bin 8.650000 2.110000 10.760000 ( 13.864690)
|
|
65
|
+
set-large: cache:libm:bin 2.600000 1.450000 4.050000 ( 7.811509)
|
|
66
|
+
set-large: dalli:bin 8.250000 2.100000 10.350000 ( 13.370233)
|
|
67
|
+
set-large: libm:ascii 0.890000 1.460000 2.350000 ( 6.069504)
|
|
68
|
+
set-large: libm:ascii:pipeline 0.570000 0.450000 1.020000 ( 1.232906)
|
|
69
|
+
set-large: libm:ascii:udp 0.730000 0.850000 1.580000 ( 4.174216)
|
|
70
|
+
set-large: libm:bin 0.760000 1.510000 2.270000 ( 5.966586)
|
|
71
|
+
set-large: libm:bin:buffer 0.530000 0.700000 1.230000 ( 2.419153)
|
|
72
|
+
set-large: mclient:ascii 13.540000 4.250000 17.790000 ( 18.447711)
|
|
73
|
+
set-large: stash:bin 6.100000 1.500000 7.600000 ( 11.216811)
|
|
74
|
+
|
|
75
|
+
get-large: cache:dalli:bin 8.000000 2.480000 10.480000 ( 13.450322)
|
|
76
|
+
get-large: cache:libm:bin 6.820000 1.490000 8.310000 ( 12.331486)
|
|
77
|
+
get-large: dalli:bin 7.670000 2.490000 10.160000 ( 13.148162)
|
|
78
|
+
get-large: libm:ascii 1.740000 1.540000 3.280000 ( 7.256110)
|
|
79
|
+
get-large: libm:ascii:pipeline 1.870000 1.960000 3.830000 ( 7.723493)
|
|
80
|
+
get-large: libm:ascii:udp 1.560000 0.980000 2.540000 ( 4.823281)
|
|
81
|
+
get-large: libm:bin 1.590000 1.590000 3.180000 ( 7.018376)
|
|
82
|
+
get-large: libm:bin:buffer 1.730000 1.960000 3.690000 ( 7.365047)
|
|
83
|
+
get-large: mclient:ascii 17.160000 4.890000 22.050000 ( 22.647077)
|
|
84
|
+
get-large: stash:bin 3.290000 1.440000 4.730000 ( 7.950154)
|
|
85
|
+
|
|
86
|
+
hash:jenkins 0.550000 0.000000 0.550000 ( 0.561738)
|
|
87
|
+
hash:default 0.550000 0.000000 0.550000 ( 0.553209)
|
|
88
|
+
hash:crc 0.660000 0.000000 0.660000 ( 0.657155)
|
|
89
|
+
hash:fnv1_32 0.530000 0.010000 0.540000 ( 0.529303)
|
|
90
|
+
hash:hsieh 0.280000 0.000000 0.280000 ( 0.281685)
|
|
91
|
+
hash:fnv1_64 1.160000 0.000000 1.160000 ( 1.169524)
|
|
92
|
+
hash:none 0.310000 0.000000 0.310000 ( 0.313095)
|
|
93
|
+
hash:murmur 0.450000 0.000000 0.450000 ( 0.454460)
|
|
94
|
+
hash:md5 0.950000 0.010000 0.960000 ( 0.949226)
|
|
95
|
+
hash:fnv1a_64 0.590000 0.000000 0.590000 ( 0.590362)
|
|
96
|
+
hash:fnv1a_32 0.590000 0.000000 0.590000 ( 0.599005)
|
|
97
|
+
|
|
98
|
+
|
|
1
99
|
# sabshere 2/22/11 v0.0.3
|
|
2
100
|
|
|
3
101
|
vidalia:~/github/cache (master) $ ruby test/profile/benchmark.rb
|
data/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# cache
|
|
2
|
+
|
|
3
|
+
A unified cache handling interface, inspired by libraries like [ActiveSupport::Cache::Store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html), Perl's [Cache::Cache](http://cpan.uwinnipeg.ca/module/Cache::Cache), and [CHI](http://cpan.uwinnipeg.ca/module/CHI).
|
|
4
|
+
|
|
5
|
+
## Supported methods
|
|
6
|
+
|
|
7
|
+
It will translate these methods to whatever Redis, Memcached, etc. client you're using:
|
|
8
|
+
|
|
9
|
+
@cache.get 'hello'
|
|
10
|
+
@cache.set 'hello', 'world', 5.minutes
|
|
11
|
+
@cache.delete 'hello'
|
|
12
|
+
@cache.flush
|
|
13
|
+
@cache.exist? 'hello'
|
|
14
|
+
@cache.reset
|
|
15
|
+
@cache.fetch 'hello' { 'world' }
|
|
16
|
+
@cache.cas 'hello' { |current| 'world' }
|
|
17
|
+
@cache.increment 'high-fives'
|
|
18
|
+
@cache.decrement 'high-fives'
|
|
19
|
+
|
|
20
|
+
Also provided for Rails compatibility:
|
|
21
|
+
|
|
22
|
+
@cache.write 'hello', 'world', :expires_in => 5.minutes
|
|
23
|
+
@cache.read 'hello'
|
|
24
|
+
@cache.clear
|
|
25
|
+
@cache.compare_and_swap
|
|
26
|
+
@cache.read_multi 'hello', 'privyet', 'hallo'
|
|
27
|
+
|
|
28
|
+
## Rationale
|
|
29
|
+
|
|
30
|
+
I wanted a common interface to a bunch of great Ruby cache clients so I can develop gems (lock_method, cache_method) that accept any of them.
|
|
31
|
+
|
|
32
|
+
* I'm tired of rescuing from Memcached::NotFound
|
|
33
|
+
* I'm tired of forgetting whether it's :expires_in or :ttl
|
|
34
|
+
* I don't know why we ever started using read/write instead of get/set.
|
|
35
|
+
* I don't like how you have to manually handle after_fork for Redis, Memcached, etc.
|
|
36
|
+
* I don't know why Memcached::Rails isn't implemented as an ActiveRecord::Cache::Store (Dalli did it just fine!)
|
|
37
|
+
* Why are you asking me about :raw or whatever? Just marshal it
|
|
38
|
+
|
|
39
|
+
## How you might use it
|
|
40
|
+
|
|
41
|
+
<table>
|
|
42
|
+
<tr>
|
|
43
|
+
<th> </th>
|
|
44
|
+
<th><a href="https://github.com/fauna/memcached">Super-fast memcached</a></th>
|
|
45
|
+
<th><a href="https://github.com/mperham/dalli">Pure Ruby memcached</a> (works on <a href="http://devcenter.heroku.com/articles/memcache">Heroku</a>)</th>
|
|
46
|
+
<th><a href="https://redistogo.com/">Redis</a></th>
|
|
47
|
+
</tr>
|
|
48
|
+
<tr>
|
|
49
|
+
<td><a href="http://guides.rubyonrails.org/caching_with_rails.html#cache-stores">Rails</a></td>
|
|
50
|
+
<td><pre>config.cache_store = Cache.wrap(Memcached.new)</pre></td>
|
|
51
|
+
<td><pre>config.cache_store = Cache.wrap(Dalli::Client.new)</pre></td>
|
|
52
|
+
<td><pre>config.cache_store = Cache.wrap(Redis.new)</pre></td>
|
|
53
|
+
</tr>
|
|
54
|
+
<tr>
|
|
55
|
+
<td><a href="https://github.com/seamusabshere/cache_method">CacheMethod</a> (already uses Cache internally)</td>
|
|
56
|
+
<td><pre>CacheMethod.config.storage = Memcached.new</pre></td>
|
|
57
|
+
<td><pre>CacheMethod.config.storage = Dalli::Client.new</pre></td>
|
|
58
|
+
<td><pre>CacheMethod.config.storage = Redis.new</pre></td>
|
|
59
|
+
</tr>
|
|
60
|
+
<tr>
|
|
61
|
+
<td><a href="https://github.com/seamusabshere/lock_method">LockMethod</a> (already uses Cache internally)</td>
|
|
62
|
+
<td><pre>LockMethod.config.storage = Memcached.new</pre></td>
|
|
63
|
+
<td><pre>LockMethod.config.storage = Dalli::Client.new</pre></td>
|
|
64
|
+
<td><pre>LockMethod.config.storage = Redis.new</pre></td>
|
|
65
|
+
</tr>
|
|
66
|
+
<tr>
|
|
67
|
+
<td>Your own library</td>
|
|
68
|
+
<td>
|
|
69
|
+
<pre>
|
|
70
|
+
# Accept any client, let Cache take care of it
|
|
71
|
+
def cache=(raw_client)
|
|
72
|
+
@cache = Cache.wrap(raw_client)
|
|
73
|
+
end
|
|
74
|
+
</pre>
|
|
75
|
+
</td>
|
|
76
|
+
<td>
|
|
77
|
+
<pre>
|
|
78
|
+
# Accept any client, let Cache take care of it
|
|
79
|
+
def cache=(raw_client)
|
|
80
|
+
@cache = Cache.wrap(raw_client)
|
|
81
|
+
end
|
|
82
|
+
</pre>
|
|
83
|
+
</td>
|
|
84
|
+
<td>
|
|
85
|
+
<pre>
|
|
86
|
+
# Accept any client, let Cache take care of it
|
|
87
|
+
def cache=(raw_client)
|
|
88
|
+
@cache = Cache.wrap(raw_client)
|
|
89
|
+
end
|
|
90
|
+
</pre>
|
|
91
|
+
</td>
|
|
92
|
+
</tr>
|
|
93
|
+
</table>
|
|
94
|
+
|
|
95
|
+
## Forking/threading
|
|
96
|
+
|
|
97
|
+
When you use a Cache object to wrap Memcached or Redis, you don't have to worry about forking or threading.
|
|
98
|
+
|
|
99
|
+
For example, you don't have to set up unicorn or PhusionPassenger's <tt>after_fork</tt>.
|
|
100
|
+
|
|
101
|
+
## TTL
|
|
102
|
+
|
|
103
|
+
0 means don't expire.
|
|
104
|
+
|
|
105
|
+
## Other examples
|
|
106
|
+
|
|
107
|
+
It defaults to an in-process memory store:
|
|
108
|
+
|
|
109
|
+
@cache = Cache.new
|
|
110
|
+
@cache.set 'hello'
|
|
111
|
+
@cache.get 'hello', 'world'
|
|
112
|
+
|
|
113
|
+
You can specify a more useful cache client:
|
|
114
|
+
|
|
115
|
+
require 'memcached' # a really fast memcached client gem
|
|
116
|
+
require 'cache' # this gem, which provides a standard interface
|
|
117
|
+
raw_client = Memcached.new '127.0.0.1:11211'
|
|
118
|
+
@cache = Cache.wrap(raw_client)
|
|
119
|
+
|
|
120
|
+
or
|
|
121
|
+
|
|
122
|
+
require 'redis' # the redis key-value store
|
|
123
|
+
require 'cache' # this gem, which provides a standard interface
|
|
124
|
+
raw_client = Redis.new
|
|
125
|
+
@cache = Cache.wrap(raw_client)
|
|
126
|
+
|
|
127
|
+
or
|
|
128
|
+
|
|
129
|
+
require 'dalli' # the dalli memcached client used by heroku
|
|
130
|
+
require 'cache' # this gem, which provides a standard interface
|
|
131
|
+
raw_client = Dalli::Client.new
|
|
132
|
+
@cache = Cache.wrap(raw_client)
|
|
133
|
+
|
|
134
|
+
Don't know why you would ever want to do this:
|
|
135
|
+
|
|
136
|
+
# Piggyback off the default rails cache
|
|
137
|
+
@cache = Cache.wrap(Rails.cache)
|
|
138
|
+
|
|
139
|
+
## Supported clients
|
|
140
|
+
|
|
141
|
+
Supported memcached clients:
|
|
142
|
+
|
|
143
|
+
* [memcached](https://github.com/fauna/memcached) (super fast!)
|
|
144
|
+
* [dalli](https://github.com/mperham/dalli) (pure ruby, recommended if you're on heroku)
|
|
145
|
+
* [memcache-client](https://github.com/mperham/memcache-client) (not recommended. the one that comes with Rails.)
|
|
146
|
+
|
|
147
|
+
Supported Redis clients:
|
|
148
|
+
|
|
149
|
+
* [redis](https://github.com/ezmobius/redis-rb)
|
|
150
|
+
|
|
151
|
+
## Copyright
|
|
152
|
+
|
|
153
|
+
Copyright 2011 Seamus Abshere
|
data/Rakefile
CHANGED
|
@@ -11,12 +11,31 @@ end
|
|
|
11
11
|
|
|
12
12
|
task :default => :test
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
# http://stackoverflow.com/questions/2138427/can-i-get-my-readme-textile-into-my-rdoc-with-proper-formatting
|
|
15
|
+
desc "Generate RDoc"
|
|
16
|
+
task :rdoc => ['doc:generate']
|
|
17
|
+
namespace :doc do
|
|
18
|
+
project_root = File.expand_path(File.dirname(__FILE__))
|
|
19
|
+
doc_destination = File.join(project_root, 'rdoc')
|
|
20
|
+
files = Dir.glob(File.join(project_root, 'lib', '**', '*.rb')) + [ File.join(project_root, 'README.md') ]
|
|
21
|
+
begin
|
|
22
|
+
require 'yard'
|
|
23
|
+
require 'yard/rake/yardoc_task'
|
|
24
|
+
|
|
25
|
+
YARD::Rake::YardocTask.new(:generate) do |yt|
|
|
26
|
+
yt.files = files
|
|
27
|
+
yt.options = ['--output-dir', doc_destination, '--readme', 'README.md']
|
|
28
|
+
end
|
|
29
|
+
rescue LoadError
|
|
30
|
+
desc "Generate YARD Documentation"
|
|
31
|
+
task :generate do
|
|
32
|
+
abort "Please install the YARD gem to generate rdoc."
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
desc "Remove generated documenation"
|
|
37
|
+
task :clean do
|
|
38
|
+
rm_r doc_dir if File.exists?(doc_destination)
|
|
39
|
+
end
|
|
17
40
|
|
|
18
|
-
rdoc.rdoc_dir = 'rdoc'
|
|
19
|
-
rdoc.title = "cache #{version}"
|
|
20
|
-
rdoc.rdoc_files.include('README*')
|
|
21
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
22
41
|
end
|
data/cache.gemspec
CHANGED
|
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
|
|
|
21
21
|
|
|
22
22
|
s.add_dependency 'activesupport', '>=2.3.11' # for default memory store
|
|
23
23
|
s.add_dependency 'i18n' # activesupport
|
|
24
|
+
s.add_development_dependency 'yard'
|
|
24
25
|
s.add_development_dependency 'test-unit'
|
|
25
26
|
s.add_development_dependency 'redis'
|
|
26
27
|
s.add_development_dependency 'dalli'
|
data/lib/cache.rb
CHANGED
|
@@ -3,9 +3,9 @@ class Cache
|
|
|
3
3
|
autoload :Config, 'cache/config'
|
|
4
4
|
autoload :Storage, 'cache/storage'
|
|
5
5
|
|
|
6
|
-
# Create a new Cache
|
|
6
|
+
# Create a new Cache instance by wrapping a client of your choice.
|
|
7
7
|
#
|
|
8
|
-
# Defaults to an in-process memory store
|
|
8
|
+
# Defaults to an in-process memory store.
|
|
9
9
|
#
|
|
10
10
|
# Supported memcached clients:
|
|
11
11
|
# * memcached[https://github.com/fauna/memcached] (either a Memcached or a Memcached::Rails)
|
|
@@ -17,8 +17,12 @@ class Cache
|
|
|
17
17
|
#
|
|
18
18
|
# Example:
|
|
19
19
|
# raw_client = Memcached.new('127.0.0.1:11211')
|
|
20
|
-
# cache = Cache.
|
|
21
|
-
def
|
|
20
|
+
# cache = Cache.wrap raw_client
|
|
21
|
+
def self.wrap(client = nil)
|
|
22
|
+
new client
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize(client = nil) #:nodoc:
|
|
22
26
|
config.client = client
|
|
23
27
|
end
|
|
24
28
|
|
|
@@ -34,7 +38,7 @@ class Cache
|
|
|
34
38
|
#
|
|
35
39
|
# Example:
|
|
36
40
|
# cache.get 'hello'
|
|
37
|
-
def get(k)
|
|
41
|
+
def get(k, ignored_options = nil)
|
|
38
42
|
storage.get k
|
|
39
43
|
end
|
|
40
44
|
|
|
@@ -43,7 +47,7 @@ class Cache
|
|
|
43
47
|
# Example:
|
|
44
48
|
# cache.set 'hello', 'world'
|
|
45
49
|
# cache.set 'hello', 'world', 80 # seconds til it expires
|
|
46
|
-
def set(k, v, ttl = nil)
|
|
50
|
+
def set(k, v, ttl = nil, ignored_options = nil)
|
|
47
51
|
storage.set k, v, ttl
|
|
48
52
|
end
|
|
49
53
|
|
|
@@ -51,7 +55,7 @@ class Cache
|
|
|
51
55
|
#
|
|
52
56
|
# Example:
|
|
53
57
|
# cache.delete 'hello'
|
|
54
|
-
def delete(k)
|
|
58
|
+
def delete(k, ignored_options = nil)
|
|
55
59
|
storage.delete k
|
|
56
60
|
end
|
|
57
61
|
|
|
@@ -62,4 +66,73 @@ class Cache
|
|
|
62
66
|
def flush
|
|
63
67
|
storage.flush
|
|
64
68
|
end
|
|
69
|
+
|
|
70
|
+
alias :clear :flush
|
|
71
|
+
|
|
72
|
+
# Check if something exists.
|
|
73
|
+
#
|
|
74
|
+
# Example:
|
|
75
|
+
# cache.exist? 'hello'
|
|
76
|
+
def exist?(k, ignored_options = nil)
|
|
77
|
+
storage.exist? k
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Increment a value.
|
|
81
|
+
#
|
|
82
|
+
# Example:
|
|
83
|
+
# cache.increment 'high-fives'
|
|
84
|
+
def increment(k, amount = 1, ignored_options = nil)
|
|
85
|
+
storage.increment k, amount
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Decrement a value.
|
|
89
|
+
#
|
|
90
|
+
# Example:
|
|
91
|
+
# cache.decrement 'high-fives'
|
|
92
|
+
def decrement(k, amount = 1, ignored_options = nil)
|
|
93
|
+
storage.decrement k, amount
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Reset the cache connection. You shouldn't really use this, because it happens automatically on forking/threading.
|
|
97
|
+
def reset
|
|
98
|
+
storage.reset
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Try to get a value and if it doesn't exist, set it to the result of the block.
|
|
102
|
+
#
|
|
103
|
+
# Example:
|
|
104
|
+
# cache.fetch 'hello' { 'world' }
|
|
105
|
+
def fetch(k, options_ignored_except_expires_in = {}, &blk)
|
|
106
|
+
storage.fetch k, options_ignored_except_expires_in[:expires_in], &blk
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Get the current value (if any), pass it into a block, and set the result.
|
|
110
|
+
#
|
|
111
|
+
# Example:
|
|
112
|
+
# cache.cas 'hello' { |current| 'world' }
|
|
113
|
+
def cas(k, ttl = nil, &blk)
|
|
114
|
+
storage.cas k, ttl, &blk
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
alias :compare_and_swap :cas
|
|
118
|
+
|
|
119
|
+
# Get stats.
|
|
120
|
+
#
|
|
121
|
+
# Example:
|
|
122
|
+
# cache.stats
|
|
123
|
+
def stats
|
|
124
|
+
storage.stats
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def write(k, v, options_ignored_except_expires_in = {}) #:nodoc:
|
|
128
|
+
storage.set k, v, options_ignored_except_expires_in[:expires_in]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def read(k, ignored_options = nil) #:nodoc:
|
|
132
|
+
storage.get k
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def read_multi(*ks) #:nodoc:
|
|
136
|
+
ks.map { |k| storage.get k }
|
|
137
|
+
end
|
|
65
138
|
end
|
data/lib/cache/storage.rb
CHANGED
|
@@ -12,17 +12,17 @@ class Cache
|
|
|
12
12
|
def get(k)
|
|
13
13
|
reset_if_forked_or_threaded
|
|
14
14
|
if memcached?
|
|
15
|
-
begin;
|
|
15
|
+
begin; bare.get(k); rescue ::Memcached::NotFound; nil; end
|
|
16
16
|
elsif dalli? or memcached_rails? or mem_cache?
|
|
17
|
-
|
|
17
|
+
bare.get k
|
|
18
18
|
elsif redis?
|
|
19
|
-
if cached_v =
|
|
19
|
+
if cached_v = bare.get(k) and cached_v.is_a?(::String)
|
|
20
20
|
::Marshal.load cached_v
|
|
21
21
|
end
|
|
22
22
|
elsif active_support_store?
|
|
23
|
-
|
|
23
|
+
bare.read k
|
|
24
24
|
else
|
|
25
|
-
raise "Don't know how to GET with #{
|
|
25
|
+
raise "Don't know how to GET with #{bare.inspect}"
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
@@ -31,51 +31,130 @@ class Cache
|
|
|
31
31
|
ttl = ttl.to_i
|
|
32
32
|
reset_if_forked_or_threaded
|
|
33
33
|
if memcached? or dalli? or memcached_rails? or mem_cache?
|
|
34
|
-
|
|
34
|
+
bare.set k, v, ttl
|
|
35
35
|
elsif redis?
|
|
36
36
|
if ttl == 0
|
|
37
|
-
|
|
37
|
+
bare.set k, ::Marshal.dump(v)
|
|
38
38
|
else
|
|
39
|
-
|
|
39
|
+
bare.setex k, ttl, ::Marshal.dump(v)
|
|
40
40
|
end
|
|
41
41
|
elsif active_support_store?
|
|
42
42
|
if ttl == 0
|
|
43
|
-
|
|
43
|
+
bare.write k, v # never expire
|
|
44
44
|
else
|
|
45
|
-
|
|
45
|
+
bare.write k, v, :expires_in => ttl
|
|
46
46
|
end
|
|
47
47
|
else
|
|
48
|
-
raise "Don't know how to SET with #{
|
|
48
|
+
raise "Don't know how to SET with #{bare.inspect}"
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def delete(k)
|
|
53
53
|
reset_if_forked_or_threaded
|
|
54
54
|
if memcached?
|
|
55
|
-
begin;
|
|
55
|
+
begin; bare.delete(k); rescue ::Memcached::NotFound; nil; end
|
|
56
56
|
elsif redis?
|
|
57
|
-
|
|
57
|
+
bare.del k
|
|
58
58
|
elsif dalli? or memcached_rails? or mem_cache? or active_support_store?
|
|
59
|
-
|
|
59
|
+
bare.delete k
|
|
60
60
|
else
|
|
61
|
-
raise "Don't know how to DELETE with #{
|
|
61
|
+
raise "Don't know how to DELETE with #{bare.inspect}"
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def flush
|
|
66
66
|
reset_if_forked_or_threaded
|
|
67
|
-
|
|
67
|
+
bare.send %w{ flush flushdb flush_all clear }.detect { |flush_cmd| bare.respond_to? flush_cmd }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# TODO detect nils
|
|
71
|
+
def exist?(k)
|
|
72
|
+
reset_if_forked_or_threaded
|
|
73
|
+
if memcached?
|
|
74
|
+
begin; bare.get(k); true; rescue ::Memcached::NotFound; false; end
|
|
75
|
+
elsif redis?
|
|
76
|
+
bare.exists k
|
|
77
|
+
elsif bare.respond_to?(:exist?)
|
|
78
|
+
# slow because we're looking it up
|
|
79
|
+
bare.exist? k
|
|
80
|
+
else
|
|
81
|
+
# weak because it doesn't detect keys that equal nil
|
|
82
|
+
!get(k).nil?
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# TODO use native memcached increment if available
|
|
87
|
+
# TODO don't reset the timer!
|
|
88
|
+
def increment(k, amount)
|
|
89
|
+
# reset_if_forked_or_threaded - uses get
|
|
90
|
+
new_v = get(k).to_i + amount
|
|
91
|
+
set k, new_v, 0
|
|
92
|
+
new_v
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def decrement(k, amount)
|
|
96
|
+
# reset_if_forked_or_threaded - uses increment, which uses get
|
|
97
|
+
increment k, -amount
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# TODO don't resort to trickery like this
|
|
101
|
+
def reset
|
|
102
|
+
@pid = nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def fetch(k, ttl, &blk)
|
|
106
|
+
ttl ||= parent.config.default_ttl
|
|
107
|
+
ttl = ttl.to_i
|
|
108
|
+
reset_if_forked_or_threaded
|
|
109
|
+
if dalli? or mem_cache?
|
|
110
|
+
bare.fetch k, ttl, &blk
|
|
111
|
+
elsif active_support_store?
|
|
112
|
+
bare.fetch k, { :expires_in => ttl }, &blk
|
|
113
|
+
else
|
|
114
|
+
if exist? k
|
|
115
|
+
get k
|
|
116
|
+
elsif blk
|
|
117
|
+
v = blk.call
|
|
118
|
+
set k, v, ttl
|
|
119
|
+
v
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def cas(k, ttl, &blk)
|
|
125
|
+
ttl ||= parent.config.default_ttl
|
|
126
|
+
ttl = ttl.to_i
|
|
127
|
+
reset_if_forked_or_threaded
|
|
128
|
+
if memcached?
|
|
129
|
+
begin; bare.cas(k, ttl, &blk); rescue ::Memcached::NotFound; nil; end
|
|
130
|
+
elsif dalli? or memcached_rails?
|
|
131
|
+
bare.cas k, ttl, &blk
|
|
132
|
+
elsif blk and exist?(k)
|
|
133
|
+
old_v = get k
|
|
134
|
+
new_v = blk.call old_v
|
|
135
|
+
set k, new_v, ttl
|
|
136
|
+
new_v
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def stats
|
|
141
|
+
reset_if_forked_or_threaded
|
|
142
|
+
if bare.respond_to?(:stats)
|
|
143
|
+
bare.stats
|
|
144
|
+
else
|
|
145
|
+
{}
|
|
146
|
+
end
|
|
68
147
|
end
|
|
69
148
|
|
|
70
149
|
private
|
|
71
150
|
|
|
72
|
-
def
|
|
73
|
-
@
|
|
151
|
+
def bare
|
|
152
|
+
@bare ||= parent.config.client
|
|
74
153
|
end
|
|
75
154
|
|
|
76
155
|
def reset_if_forked_or_threaded
|
|
77
156
|
if fork_detected?
|
|
78
|
-
$stderr.puts "fork detected" if ENV['CACHE_DEBUG'] == 'true'
|
|
157
|
+
# $stderr.puts "fork detected" if ENV['CACHE_DEBUG'] == 'true'
|
|
79
158
|
if dalli?
|
|
80
159
|
parent.config.client.close
|
|
81
160
|
elsif dalli_store?
|
|
@@ -83,18 +162,18 @@ class Cache
|
|
|
83
162
|
elsif memcached? or memcached_rails?
|
|
84
163
|
cloned_client = parent.config.client.clone
|
|
85
164
|
parent.config.client = cloned_client
|
|
86
|
-
@
|
|
165
|
+
@bare = parent.config.client
|
|
87
166
|
elsif redis?
|
|
88
167
|
parent.config.client.client.connect
|
|
89
168
|
elsif mem_cache?
|
|
90
169
|
parent.config.client.reset
|
|
91
170
|
end
|
|
92
171
|
elsif new_thread_detected?
|
|
93
|
-
$stderr.puts "new thread detected" if ENV['CACHE_DEBUG'] == 'true'
|
|
172
|
+
# $stderr.puts "new thread detected" if ENV['CACHE_DEBUG'] == 'true'
|
|
94
173
|
if memcached? or memcached_rails?
|
|
95
174
|
cloned_client = parent.config.client.clone
|
|
96
175
|
parent.config.client = cloned_client
|
|
97
|
-
@
|
|
176
|
+
@bare = parent.config.client
|
|
98
177
|
end
|
|
99
178
|
end
|
|
100
179
|
end
|
|
@@ -113,56 +192,56 @@ class Cache
|
|
|
113
192
|
|
|
114
193
|
def dalli?
|
|
115
194
|
return @dalli_query[0] if @dalli_query.is_a?(::Array)
|
|
116
|
-
answer = (defined?(::Dalli) and
|
|
195
|
+
answer = (defined?(::Dalli) and bare.is_a?(::Dalli::Client))
|
|
117
196
|
@dalli_query = [answer]
|
|
118
197
|
answer
|
|
119
198
|
end
|
|
120
199
|
|
|
121
200
|
def active_support_store?
|
|
122
201
|
return @active_support_store_query[0] if @active_support_store_query.is_a?(::Array)
|
|
123
|
-
answer = (defined?(::ActiveSupport::Cache) and
|
|
202
|
+
answer = (defined?(::ActiveSupport::Cache) and bare.is_a?(::ActiveSupport::Cache::Store))
|
|
124
203
|
@active_support_store_query = [answer]
|
|
125
204
|
answer
|
|
126
205
|
end
|
|
127
206
|
|
|
128
207
|
def dalli_store?
|
|
129
208
|
return @dalli_store_query[0] if @dalli_store_query.is_a?(::Array)
|
|
130
|
-
answer = (defined?(::ActiveSupport::Cache::DalliStore) and
|
|
209
|
+
answer = (defined?(::ActiveSupport::Cache::DalliStore) and bare.is_a?(::ActiveSupport::Cache::DalliStore))
|
|
131
210
|
@dalli_store_query = [answer]
|
|
132
211
|
answer
|
|
133
212
|
end
|
|
134
213
|
|
|
135
214
|
def memory_store?
|
|
136
215
|
return @memory_store_query[0] if @memory_store_query.is_a?(::Array)
|
|
137
|
-
answer = (defined?(::ActiveSupport::Cache::MemoryStore) and
|
|
216
|
+
answer = (defined?(::ActiveSupport::Cache::MemoryStore) and bare.is_a?(::ActiveSupport::Cache::MemoryStore))
|
|
138
217
|
@memory_store_query = [answer]
|
|
139
218
|
answer
|
|
140
219
|
end
|
|
141
220
|
|
|
142
221
|
def mem_cache?
|
|
143
222
|
return @mem_cache_query[0] if @mem_cache_query.is_a?(::Array)
|
|
144
|
-
answer = (defined?(::MemCache) and
|
|
223
|
+
answer = (defined?(::MemCache) and bare.is_a?(::MemCache))
|
|
145
224
|
@mem_cache_query = [answer]
|
|
146
225
|
answer
|
|
147
226
|
end
|
|
148
227
|
|
|
149
228
|
def memcached?
|
|
150
229
|
return @memcached_query[0] if @memcached_query.is_a?(::Array)
|
|
151
|
-
answer = (defined?(::Memcached) and
|
|
230
|
+
answer = (defined?(::Memcached) and bare.is_a?(::Memcached) and not bare.is_a?(::Memcached::Rails))
|
|
152
231
|
@memcached_query = [answer]
|
|
153
232
|
answer
|
|
154
233
|
end
|
|
155
234
|
|
|
156
235
|
def memcached_rails?
|
|
157
236
|
return @memcached_rails_query[0] if @memcached_rails_query.is_a?(::Array)
|
|
158
|
-
answer = (defined?(::Memcached) and
|
|
237
|
+
answer = (defined?(::Memcached) and bare.is_a?(::Memcached::Rails))
|
|
159
238
|
@memcached_rails_query = [answer]
|
|
160
239
|
answer
|
|
161
240
|
end
|
|
162
241
|
|
|
163
242
|
def redis?
|
|
164
243
|
return @redis_query[0] if @redis_query.is_a?(::Array)
|
|
165
|
-
answer = (defined?(::Redis) and
|
|
244
|
+
answer = (defined?(::Redis) and bare.is_a?(::Redis))
|
|
166
245
|
@redis_query = [answer]
|
|
167
246
|
answer
|
|
168
247
|
end
|
data/lib/cache/version.rb
CHANGED
data/test/helper.rb
CHANGED
data/test/profile/benchmark.rb
CHANGED
|
@@ -53,14 +53,14 @@ class Dalli::ClientCompat < Dalli::Client
|
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
class Cache::Compat < Cache
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
end
|
|
56
|
+
# class Cache::Compat < Cache
|
|
57
|
+
# def set(*args)
|
|
58
|
+
# super(*args[0..2])
|
|
59
|
+
# end
|
|
60
|
+
# def get(*args)
|
|
61
|
+
# super(args.first)
|
|
62
|
+
# end
|
|
63
|
+
# end
|
|
64
64
|
|
|
65
65
|
class Bench
|
|
66
66
|
|
|
@@ -108,8 +108,8 @@ class Bench
|
|
|
108
108
|
|
|
109
109
|
def reset_clients
|
|
110
110
|
@clients = {
|
|
111
|
-
"cache:libm:bin" => Cache
|
|
112
|
-
"cache:dalli:bin" => Cache
|
|
111
|
+
"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)),
|
|
112
|
+
"cache:dalli:bin" => Cache.wrap(Dalli::Client.new(['127.0.0.1:43042', '127.0.0.1:43043'], :marshal => false, :threadsafe => false)),
|
|
113
113
|
"libm:ascii" => Memcached::Rails.new(
|
|
114
114
|
['127.0.0.1:43042', '127.0.0.1:43043'],
|
|
115
115
|
:buffer_requests => false, :no_block => false, :namespace => "namespace"),
|
data/test/shared_tests.rb
CHANGED
|
@@ -34,8 +34,97 @@ module SharedTests
|
|
|
34
34
|
|
|
35
35
|
def test_flush
|
|
36
36
|
@cache.set 'hello', 'world'
|
|
37
|
-
|
|
37
|
+
assert @cache.exist?('hello')
|
|
38
38
|
@cache.flush
|
|
39
|
+
assert !@cache.exist?('hello')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_exist
|
|
43
|
+
assert !@cache.exist?('hello')
|
|
44
|
+
@cache.set 'hello', 'world'
|
|
45
|
+
assert @cache.exist?('hello')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_exist_key_with_nil_value
|
|
49
|
+
assert !@cache.exist?('hello')
|
|
50
|
+
@cache.set 'hello', nil
|
|
51
|
+
assert @cache.exist?('hello')
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_stats
|
|
55
|
+
assert_nothing_raised do
|
|
56
|
+
@cache.stats
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_reset
|
|
61
|
+
@cache.set 'hello', 'world'
|
|
62
|
+
assert @cache.exist?('hello')
|
|
63
|
+
@cache.reset
|
|
64
|
+
# still there!
|
|
65
|
+
assert @cache.exist?('hello')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def test_fetch
|
|
69
|
+
assert_equal nil, @cache.fetch('hello')
|
|
70
|
+
assert_equal 'world', @cache.fetch('hello') { 'world' }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_cas
|
|
74
|
+
toggle = lambda do |current|
|
|
75
|
+
current == 'on' ? 'off' : 'on'
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
@cache.set 'lights', 'on'
|
|
79
|
+
assert_equal 'on', @cache.get('lights')
|
|
80
|
+
@cache.cas 'lights', &toggle
|
|
81
|
+
assert_equal 'off', @cache.get('lights')
|
|
82
|
+
@cache.cas 'lights', &toggle
|
|
83
|
+
assert_equal 'on', @cache.get('lights')
|
|
84
|
+
@cache.cas 'lights', &toggle
|
|
85
|
+
assert_equal 'off', @cache.get('lights')
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_write
|
|
89
|
+
@cache.write 'hello', 'world'
|
|
90
|
+
assert_equal 'world', @cache.get('hello')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_write_with_expires_in
|
|
94
|
+
@cache.write 'hello', 'world', :expires_in => 1
|
|
95
|
+
assert_equal 'world', @cache.get('hello')
|
|
96
|
+
sleep 2
|
|
39
97
|
assert_equal nil, @cache.get('hello')
|
|
40
98
|
end
|
|
99
|
+
|
|
100
|
+
def test_read
|
|
101
|
+
@cache.set 'hello', 'world'
|
|
102
|
+
assert_equal 'world', @cache.read('hello')
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_increment
|
|
106
|
+
assert !@cache.exist?('high-fives')
|
|
107
|
+
@cache.increment 'high-fives'
|
|
108
|
+
assert_equal 1, @cache.get('high-fives')
|
|
109
|
+
@cache.increment 'high-fives'
|
|
110
|
+
assert_equal 2, @cache.get('high-fives')
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def test_decrement
|
|
114
|
+
assert !@cache.exist?('high-fives')
|
|
115
|
+
@cache.decrement 'high-fives'
|
|
116
|
+
assert_equal -1, @cache.get('high-fives')
|
|
117
|
+
@cache.decrement 'high-fives'
|
|
118
|
+
assert_equal -2, @cache.get('high-fives')
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def test_read_multi
|
|
122
|
+
@cache.set 'hello', 'world'
|
|
123
|
+
@cache.set 'privyet', 'mir'
|
|
124
|
+
assert_equal %w{world mir}, @cache.read_multi('hello', 'privyet')
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
alias :test_clear :test_flush
|
|
128
|
+
|
|
129
|
+
alias :test_compare_and_swap :test_cas
|
|
41
130
|
end
|
|
@@ -4,13 +4,13 @@ require 'memcached'
|
|
|
4
4
|
|
|
5
5
|
class TestMemcachedRailsStorage < Test::Unit::TestCase
|
|
6
6
|
def raw_client
|
|
7
|
-
Memcached::Rails.new 'localhost:11211'
|
|
7
|
+
Memcached::Rails.new 'localhost:11211', :support_cas => true
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
include SharedTests
|
|
11
11
|
|
|
12
|
-
def
|
|
13
|
-
@cache.storage.send(:
|
|
12
|
+
def get_bare_id
|
|
13
|
+
@cache.storage.send(:bare).object_id
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def test_treats_as_not_thread_safe
|
|
@@ -18,17 +18,17 @@ class TestMemcachedRailsStorage < Test::Unit::TestCase
|
|
|
18
18
|
@cache.get 'hi'
|
|
19
19
|
|
|
20
20
|
# get the main thread's bare client
|
|
21
|
-
|
|
21
|
+
main_thread_bare_id = get_bare_id
|
|
22
22
|
|
|
23
23
|
# sanity check that it's not changing every time
|
|
24
24
|
@cache.get 'hi'
|
|
25
|
-
assert_equal
|
|
25
|
+
assert_equal main_thread_bare_id, get_bare_id
|
|
26
26
|
|
|
27
27
|
# create a new thread and get its bare client
|
|
28
|
-
|
|
28
|
+
new_thread_bare_id = Thread.new { @cache.get 'hi'; get_bare_id }.value
|
|
29
29
|
|
|
30
30
|
# make sure the bare client was reinitialized
|
|
31
|
-
assert(
|
|
31
|
+
assert(main_thread_bare_id != new_thread_bare_id)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def test_treats_as_not_fork_safe
|
|
@@ -36,16 +36,16 @@ class TestMemcachedRailsStorage < Test::Unit::TestCase
|
|
|
36
36
|
@cache.get 'hi'
|
|
37
37
|
|
|
38
38
|
# get the main process's bare client
|
|
39
|
-
|
|
39
|
+
parent_process_bare_id = get_bare_id
|
|
40
40
|
|
|
41
41
|
# sanity check that it's not changing every time
|
|
42
42
|
@cache.get 'hi'
|
|
43
|
-
assert_equal
|
|
43
|
+
assert_equal parent_process_bare_id, get_bare_id
|
|
44
44
|
|
|
45
45
|
# fork a new process
|
|
46
46
|
pid = Kernel.fork do
|
|
47
47
|
@cache.get 'hi'
|
|
48
|
-
raise "Didn't split!" if
|
|
48
|
+
raise "Didn't split!" if parent_process_bare_id == get_bare_id
|
|
49
49
|
end
|
|
50
50
|
Process.wait pid
|
|
51
51
|
|
|
@@ -4,13 +4,13 @@ require 'memcached'
|
|
|
4
4
|
|
|
5
5
|
class TestMemcachedStorage < Test::Unit::TestCase
|
|
6
6
|
def raw_client
|
|
7
|
-
Memcached.new 'localhost:11211'
|
|
7
|
+
Memcached.new 'localhost:11211', :support_cas => true
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
include SharedTests
|
|
11
11
|
|
|
12
|
-
def
|
|
13
|
-
@cache.storage.send(:
|
|
12
|
+
def get_bare_id
|
|
13
|
+
@cache.storage.send(:bare).object_id
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def test_treats_as_not_thread_safe
|
|
@@ -18,17 +18,17 @@ class TestMemcachedStorage < Test::Unit::TestCase
|
|
|
18
18
|
@cache.get 'hi'
|
|
19
19
|
|
|
20
20
|
# get the main thread's bare client
|
|
21
|
-
|
|
21
|
+
main_thread_bare_id = get_bare_id
|
|
22
22
|
|
|
23
23
|
# sanity check that it's not changing every time
|
|
24
24
|
@cache.get 'hi'
|
|
25
|
-
assert_equal
|
|
25
|
+
assert_equal main_thread_bare_id, get_bare_id
|
|
26
26
|
|
|
27
27
|
# create a new thread and get its bare client
|
|
28
|
-
|
|
28
|
+
new_thread_bare_id = Thread.new { @cache.get 'hi'; get_bare_id }.value
|
|
29
29
|
|
|
30
30
|
# make sure the bare client was reinitialized
|
|
31
|
-
assert(
|
|
31
|
+
assert(main_thread_bare_id != new_thread_bare_id)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def test_treats_as_not_fork_safe
|
|
@@ -36,16 +36,16 @@ class TestMemcachedStorage < Test::Unit::TestCase
|
|
|
36
36
|
@cache.get 'hi'
|
|
37
37
|
|
|
38
38
|
# get the main process's bare client
|
|
39
|
-
|
|
39
|
+
parent_process_bare_id = get_bare_id
|
|
40
40
|
|
|
41
41
|
# sanity check that it's not changing every time
|
|
42
42
|
@cache.get 'hi'
|
|
43
|
-
assert_equal
|
|
43
|
+
assert_equal parent_process_bare_id, get_bare_id
|
|
44
44
|
|
|
45
45
|
# fork a new process
|
|
46
46
|
pid = Kernel.fork do
|
|
47
47
|
@cache.get 'hi'
|
|
48
|
-
raise "Didn't split!" if
|
|
48
|
+
raise "Didn't split!" if parent_process_bare_id == get_bare_id
|
|
49
49
|
end
|
|
50
50
|
Process.wait pid
|
|
51
51
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cache
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
4
|
+
hash: 23
|
|
5
5
|
prerelease: false
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
version: 0.
|
|
8
|
+
- 2
|
|
9
|
+
- 0
|
|
10
|
+
version: 0.2.0
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Seamus Abshere
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2011-03-
|
|
18
|
+
date: 2011-03-04 00:00:00 -06:00
|
|
19
19
|
default_executable:
|
|
20
20
|
dependencies:
|
|
21
21
|
- !ruby/object:Gem::Dependency
|
|
@@ -49,7 +49,7 @@ dependencies:
|
|
|
49
49
|
type: :runtime
|
|
50
50
|
version_requirements: *id002
|
|
51
51
|
- !ruby/object:Gem::Dependency
|
|
52
|
-
name:
|
|
52
|
+
name: yard
|
|
53
53
|
prerelease: false
|
|
54
54
|
requirement: &id003 !ruby/object:Gem::Requirement
|
|
55
55
|
none: false
|
|
@@ -63,7 +63,7 @@ dependencies:
|
|
|
63
63
|
type: :development
|
|
64
64
|
version_requirements: *id003
|
|
65
65
|
- !ruby/object:Gem::Dependency
|
|
66
|
-
name:
|
|
66
|
+
name: test-unit
|
|
67
67
|
prerelease: false
|
|
68
68
|
requirement: &id004 !ruby/object:Gem::Requirement
|
|
69
69
|
none: false
|
|
@@ -77,7 +77,7 @@ dependencies:
|
|
|
77
77
|
type: :development
|
|
78
78
|
version_requirements: *id004
|
|
79
79
|
- !ruby/object:Gem::Dependency
|
|
80
|
-
name:
|
|
80
|
+
name: redis
|
|
81
81
|
prerelease: false
|
|
82
82
|
requirement: &id005 !ruby/object:Gem::Requirement
|
|
83
83
|
none: false
|
|
@@ -91,7 +91,7 @@ dependencies:
|
|
|
91
91
|
type: :development
|
|
92
92
|
version_requirements: *id005
|
|
93
93
|
- !ruby/object:Gem::Dependency
|
|
94
|
-
name:
|
|
94
|
+
name: dalli
|
|
95
95
|
prerelease: false
|
|
96
96
|
requirement: &id006 !ruby/object:Gem::Requirement
|
|
97
97
|
none: false
|
|
@@ -105,7 +105,7 @@ dependencies:
|
|
|
105
105
|
type: :development
|
|
106
106
|
version_requirements: *id006
|
|
107
107
|
- !ruby/object:Gem::Dependency
|
|
108
|
-
name:
|
|
108
|
+
name: memcached
|
|
109
109
|
prerelease: false
|
|
110
110
|
requirement: &id007 !ruby/object:Gem::Requirement
|
|
111
111
|
none: false
|
|
@@ -118,6 +118,20 @@ dependencies:
|
|
|
118
118
|
version: "0"
|
|
119
119
|
type: :development
|
|
120
120
|
version_requirements: *id007
|
|
121
|
+
- !ruby/object:Gem::Dependency
|
|
122
|
+
name: memcache-client
|
|
123
|
+
prerelease: false
|
|
124
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
|
125
|
+
none: false
|
|
126
|
+
requirements:
|
|
127
|
+
- - ">="
|
|
128
|
+
- !ruby/object:Gem::Version
|
|
129
|
+
hash: 3
|
|
130
|
+
segments:
|
|
131
|
+
- 0
|
|
132
|
+
version: "0"
|
|
133
|
+
type: :development
|
|
134
|
+
version_requirements: *id008
|
|
121
135
|
description: A unified cache handling interface for Ruby, inspired by (but simpler than) Perl's Cache::Cache
|
|
122
136
|
email:
|
|
123
137
|
- seamus@abshere.net
|
|
@@ -131,7 +145,7 @@ files:
|
|
|
131
145
|
- .gitignore
|
|
132
146
|
- BENCHMARKS
|
|
133
147
|
- Gemfile
|
|
134
|
-
- README.
|
|
148
|
+
- README.md
|
|
135
149
|
- Rakefile
|
|
136
150
|
- cache.gemspec
|
|
137
151
|
- lib/cache.rb
|
data/README.rdoc
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
= cache
|
|
2
|
-
|
|
3
|
-
A unified cache handling interface, inspired (but simpler than) Perl's Cache::Cache[http://cpan.uwinnipeg.ca/module/Cache::Cache] and CHI[http://cpan.uwinnipeg.ca/module/CHI].
|
|
4
|
-
|
|
5
|
-
Takes care of exceptions like Memcached::NotFound and also forking/threading.
|
|
6
|
-
|
|
7
|
-
== Example
|
|
8
|
-
|
|
9
|
-
It uses a (useless?) memory store by default:
|
|
10
|
-
|
|
11
|
-
cache = Cache.new
|
|
12
|
-
cache.set 'hello'
|
|
13
|
-
cache.get 'hello', 'world'
|
|
14
|
-
|
|
15
|
-
You can specify a more useful cache client:
|
|
16
|
-
|
|
17
|
-
require 'memcached' # a really fast memcached client gem
|
|
18
|
-
require 'cache' # this gem, which provides a standard interface
|
|
19
|
-
raw_client = Memcached.new '127.0.0.1:11211'
|
|
20
|
-
cache = Cache.new raw_client
|
|
21
|
-
|
|
22
|
-
or
|
|
23
|
-
|
|
24
|
-
require 'redis' # the redis key-value store
|
|
25
|
-
require 'cache' # this gem, which provides a standard interface
|
|
26
|
-
raw_client = Redis.new
|
|
27
|
-
cache = Cache.new raw_client
|
|
28
|
-
|
|
29
|
-
or
|
|
30
|
-
|
|
31
|
-
require 'dalli' # the dalli memcached client used by heroku
|
|
32
|
-
require 'cache' # this gem, which provides a standard interface
|
|
33
|
-
raw_client = Dalli::Client.new
|
|
34
|
-
cache = Cache.new raw_client
|
|
35
|
-
|
|
36
|
-
Maybe this will even work:
|
|
37
|
-
|
|
38
|
-
# Piggyback off the default rails cache
|
|
39
|
-
cache = Cache.new Rails.cache
|
|
40
|
-
|
|
41
|
-
== Methods
|
|
42
|
-
|
|
43
|
-
cache.get 'hello'
|
|
44
|
-
cache.set 'hello', 'world'
|
|
45
|
-
cache.delete 'hello'
|
|
46
|
-
cache.flush
|
|
47
|
-
|
|
48
|
-
== Supported clients
|
|
49
|
-
|
|
50
|
-
Supported memcached clients:
|
|
51
|
-
|
|
52
|
-
* memcached[https://github.com/fauna/memcached] (either a Memcached or a Memcached::Rails)
|
|
53
|
-
* dalli[https://github.com/mperham/dalli] (either a Dalli::Client or an ActiveSupport::Cache::DalliStore)
|
|
54
|
-
* memcache-client[https://github.com/mperham/memcache-client] (MemCache, the one commonly used by Rails)
|
|
55
|
-
|
|
56
|
-
Supported Redis clients:
|
|
57
|
-
|
|
58
|
-
* redis[https://github.com/ezmobius/redis-rb]
|
|
59
|
-
|
|
60
|
-
== TTL
|
|
61
|
-
|
|
62
|
-
0 means don't expire.
|
|
63
|
-
|
|
64
|
-
== Forking/threading
|
|
65
|
-
|
|
66
|
-
When you use a Cache object to wrap Memcached or Redis, you don't have to worry about forking or threading.
|
|
67
|
-
|
|
68
|
-
For example, you don't have to set up unicorn or PhusionPassenger's <tt>after_fork</tt>.
|
|
69
|
-
|
|
70
|
-
== Rationale
|
|
71
|
-
|
|
72
|
-
I wanted a common interface to a bunch of great Ruby cache clients so I can develop gems (lock_method, cache_method) that accept any of them.
|
|
73
|
-
|
|
74
|
-
* I am so tired of rescuing from Memcached::NotFound
|
|
75
|
-
* I am so tired of forgetting whether it's :expires_in or :ttl
|
|
76
|
-
* I don't know why we ever started using read/write instead of get/set.
|
|
77
|
-
* I don't like how you have to manually handle after_fork for Redis, Memcached, etc.
|
|
78
|
-
|
|
79
|
-
== Currently unsupported
|
|
80
|
-
|
|
81
|
-
* cas
|
|
82
|
-
* fetch
|
|
83
|
-
* incr
|
|
84
|
-
|
|
85
|
-
== Copyright
|
|
86
|
-
|
|
87
|
-
Copyright 2011 Seamus Abshere
|