cachy 0.1.4 → 0.1.5
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/{README.markdown → Readme.md} +6 -3
- data/VERSION +1 -1
- data/cachy.gemspec +8 -7
- data/lib/cachy.rb +40 -22
- data/lib/cachy/memcache_timeout_protection.rb +30 -0
- data/lib/cachy/memcached_wrapper.rb +0 -1
- data/lib/cachy/redis_wrapper.rb +20 -0
- data/spec/cachy/redis_wrapper_spec.rb +67 -0
- data/spec/cachy_spec.rb +20 -3
- metadata +10 -6
@@ -8,7 +8,7 @@ Caching library to simplify and organize caching.
|
|
8
8
|
- Global cache_version (expire everything Cachy cached, but not e.g. sessions)
|
9
9
|
- ...
|
10
10
|
- works out of the box with Rails
|
11
|
-
- works with pure Memcache and [Moneta](http://github.com/wycats/moneta/tree/master)(-> Tokyo Cabinet / CouchDB / S3 / Berkeley DB / DataMapper / Memory store)
|
11
|
+
- works with pure Memcache, Redis and [Moneta](http://github.com/wycats/moneta/tree/master)(-> Tokyo Cabinet / CouchDB / S3 / Berkeley DB / DataMapper / Memory store)
|
12
12
|
|
13
13
|
Install
|
14
14
|
=======
|
@@ -104,6 +104,9 @@ Give me something that responds to read/write(Rails style) or []/store([Moneta](
|
|
104
104
|
No I18n.available_locales ?
|
105
105
|
Cachy.locales = [:de, :en, :fr]
|
106
106
|
|
107
|
+
### Memcache timeout protection
|
108
|
+
If Memcache timeouts keep killing your pages -> [catch MemCache timeouts](http://github.com/grosser/cachy/blob/master/lib/cachy/memcache_timeout_protection)
|
109
|
+
|
107
110
|
TODO
|
108
111
|
====
|
109
112
|
- optionally store dependent keys (:keys=>xxx), so that they can be setup up once and do not need to be remembered
|
@@ -114,6 +117,6 @@ Authors
|
|
114
117
|
###Contributors
|
115
118
|
- [mindreframer](http://www.simplewebapp.de/roman)
|
116
119
|
|
117
|
-
[Michael Grosser](http://
|
120
|
+
[Michael Grosser](http://grosser.it)
|
118
121
|
grosser.michael@gmail.com
|
119
|
-
Hereby placed under public domain, do what you want, just do not hold me accountable...
|
122
|
+
Hereby placed under public domain, do what you want, just do not hold me accountable...
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.5
|
data/cachy.gemspec
CHANGED
@@ -5,26 +5,26 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{cachy}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.5"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Michael Grosser"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-10-29}
|
13
13
|
s.email = %q{grosser.michael@gmail.com}
|
14
|
-
s.extra_rdoc_files = [
|
15
|
-
"README.markdown"
|
16
|
-
]
|
17
14
|
s.files = [
|
18
|
-
"
|
19
|
-
"
|
15
|
+
"Rakefile",
|
16
|
+
"Readme.md",
|
20
17
|
"VERSION",
|
21
18
|
"cachy.gemspec",
|
22
19
|
"lib/cachy.rb",
|
20
|
+
"lib/cachy/memcache_timeout_protection.rb",
|
23
21
|
"lib/cachy/memcached_wrapper.rb",
|
24
22
|
"lib/cachy/moneta_wrapper.rb",
|
23
|
+
"lib/cachy/redis_wrapper.rb",
|
25
24
|
"lib/cachy/wrapper.rb",
|
26
25
|
"spec/cachy/memcached_wrapper_spec.rb",
|
27
26
|
"spec/cachy/moneta_wrapper_spec.rb",
|
27
|
+
"spec/cachy/redis_wrapper_spec.rb",
|
28
28
|
"spec/cachy_spec.rb",
|
29
29
|
"spec/spec_helper.rb",
|
30
30
|
"spec/test_cache.rb"
|
@@ -37,6 +37,7 @@ Gem::Specification.new do |s|
|
|
37
37
|
s.test_files = [
|
38
38
|
"spec/cachy/memcached_wrapper_spec.rb",
|
39
39
|
"spec/cachy/moneta_wrapper_spec.rb",
|
40
|
+
"spec/cachy/redis_wrapper_spec.rb",
|
40
41
|
"spec/cachy_spec.rb",
|
41
42
|
"spec/test_cache.rb",
|
42
43
|
"spec/spec_helper.rb"
|
data/lib/cachy.rb
CHANGED
@@ -116,18 +116,8 @@ class Cachy
|
|
116
116
|
|
117
117
|
# Wrap non ActiveSupport style cache stores,
|
118
118
|
# to get the same interface for all
|
119
|
-
def self.cache_store=(
|
120
|
-
|
121
|
-
@cache_store=x
|
122
|
-
elsif x.respond_to? "[]" and x.respond_to? :set
|
123
|
-
require 'cachy/memcached_wrapper'
|
124
|
-
@cache_store = MemcachedWrapper.new(x)
|
125
|
-
elsif x.respond_to? "[]" and x.respond_to? :store
|
126
|
-
require 'cachy/moneta_wrapper'
|
127
|
-
@cache_store = MonetaWrapper.new(x)
|
128
|
-
else
|
129
|
-
raise "This cache_store type is not usable for Cachy!"
|
130
|
-
end
|
119
|
+
def self.cache_store=(cache)
|
120
|
+
@cache_store = wrap_cache(cache)
|
131
121
|
@cache_store.write HEALTH_CHECK_KEY, 'yes'
|
132
122
|
end
|
133
123
|
|
@@ -135,7 +125,13 @@ class Cachy
|
|
135
125
|
@cache_store || raise("Use: Cachy.cache_store = your_cache_store")
|
136
126
|
end
|
137
127
|
|
138
|
-
self.
|
128
|
+
def self.key_versions_cache_store=(cache)
|
129
|
+
@key_versions_cache_store = wrap_cache(cache)
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.key_versions_cache_store
|
133
|
+
@key_versions_cache_store || cache_store
|
134
|
+
end
|
139
135
|
|
140
136
|
# locales
|
141
137
|
@@locales = nil
|
@@ -154,19 +150,39 @@ class Cachy
|
|
154
150
|
|
155
151
|
private
|
156
152
|
|
157
|
-
def self.
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
153
|
+
def self.wrap_cache(cache)
|
154
|
+
if cache.respond_to? :read and cache.respond_to? :write
|
155
|
+
cache
|
156
|
+
elsif cache.class.to_s == 'Redis'
|
157
|
+
require 'cachy/redis_wrapper'
|
158
|
+
RedisWrapper.new(cache)
|
159
|
+
elsif cache.respond_to? "[]" and cache.respond_to? :set
|
160
|
+
require 'cachy/memcached_wrapper'
|
161
|
+
MemcachedWrapper.new(cache)
|
162
|
+
elsif cache.respond_to? "[]" and cache.respond_to? :store
|
163
|
+
require 'cachy/moneta_wrapper'
|
164
|
+
MonetaWrapper.new(cache)
|
163
165
|
else
|
164
|
-
cache_store
|
166
|
+
raise "This cache_store type is not usable for Cachy!"
|
165
167
|
end
|
166
168
|
end
|
167
169
|
|
170
|
+
def self.read_versions
|
171
|
+
store = key_versions_cache_store
|
172
|
+
result = store.read(KEY_VERSIONS_KEY) || {}
|
173
|
+
detect_cache_error(store)
|
174
|
+
result
|
175
|
+
end
|
176
|
+
|
168
177
|
def self.write_version(data)
|
169
|
-
|
178
|
+
key_versions_cache_store.write(KEY_VERSIONS_KEY, data) unless @@cache_error
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.detect_cache_error(store)
|
182
|
+
data = store.instance_variable_get('@data')
|
183
|
+
if data.respond_to? :read_error_occurred
|
184
|
+
@@cache_error = data.read_error_occurred
|
185
|
+
end
|
170
186
|
end
|
171
187
|
|
172
188
|
def self.cache_healthy?
|
@@ -233,4 +249,6 @@ class Cachy
|
|
233
249
|
{}
|
234
250
|
end
|
235
251
|
end
|
236
|
-
end
|
252
|
+
end
|
253
|
+
|
254
|
+
Cachy.cache_store = ActionController::Base.cache_store if defined? ActionController::Base
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# do not let MemCache timeouts kill your app,
|
2
|
+
# mark as error and return read_error_callback (e.g. nil -> cache miss)
|
3
|
+
require 'memcache'
|
4
|
+
|
5
|
+
class MemCache
|
6
|
+
# Add your callback to stop timeouts from raising
|
7
|
+
#
|
8
|
+
# MemCache.read_error_callback = lambda{|error|
|
9
|
+
# error.message << ' -- catched'
|
10
|
+
# HoptoadNotifier.notify error
|
11
|
+
# nil
|
12
|
+
# }
|
13
|
+
|
14
|
+
cattr_accessor :read_error_callback, :read_error_occurred
|
15
|
+
|
16
|
+
def cache_get_with_timeout_protection(*args)
|
17
|
+
begin
|
18
|
+
@read_error_occurred = false
|
19
|
+
cache_get_without_timeout_protection(*args)
|
20
|
+
rescue Exception => error
|
21
|
+
@read_error_occurred = true
|
22
|
+
if error.to_s == 'IO timeout' and self.class.read_error_callback
|
23
|
+
self.class.read_error_callback.call error
|
24
|
+
else
|
25
|
+
raise error
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
alias_method_chain :cache_get, :timeout_protection
|
30
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'cachy/wrapper'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
class Cachy::RedisWrapper < Cachy::Wrapper
|
5
|
+
def read(key)
|
6
|
+
result = @wrapped[key]
|
7
|
+
return if result.nil?
|
8
|
+
YAML.load(result)
|
9
|
+
end
|
10
|
+
|
11
|
+
def write(key, value, options={})
|
12
|
+
result = @wrapped.set(key, value.to_yaml)
|
13
|
+
@wrapped.expire(key, options[:expires_in].to_i) if options[:expires_in]
|
14
|
+
result
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete(key)
|
18
|
+
@wrapped.del(key)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
class TestRedis
|
4
|
+
def self.to_s
|
5
|
+
'Redis'
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@wrapped = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def set(key, object)
|
13
|
+
@wrapped[key] = object
|
14
|
+
end
|
15
|
+
|
16
|
+
def expire(key, seconds)
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](x)
|
20
|
+
@wrapped[x]
|
21
|
+
end
|
22
|
+
|
23
|
+
def clear
|
24
|
+
@wrapped.clear
|
25
|
+
end
|
26
|
+
|
27
|
+
def del(key)
|
28
|
+
@wrapped.delete(key)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "Cachy::RedisWrapper" do
|
33
|
+
before :all do
|
34
|
+
@cache = TestRedis.new
|
35
|
+
Cachy.cache_store = @cache
|
36
|
+
end
|
37
|
+
|
38
|
+
before do
|
39
|
+
@cache.clear
|
40
|
+
end
|
41
|
+
|
42
|
+
it "is wrapped" do
|
43
|
+
Cachy.cache_store.class.should == Cachy::RedisWrapper
|
44
|
+
end
|
45
|
+
|
46
|
+
it "can cache" do
|
47
|
+
Cachy.cache(:x){ 'SUCCESS' }
|
48
|
+
Cachy.cache(:x){ 'FAIL' }.should == 'SUCCESS'
|
49
|
+
end
|
50
|
+
|
51
|
+
it "can cache without expires" do
|
52
|
+
@cache.should_receive(:set).with('x_v1', "--- SUCCESS\n")
|
53
|
+
@cache.should_not_receive(:expire)
|
54
|
+
Cachy.cache(:x){ 'SUCCESS' }.should == 'SUCCESS'
|
55
|
+
end
|
56
|
+
|
57
|
+
it "can cache with expires" do
|
58
|
+
@cache.should_receive(:set).with('x_v1', "--- SUCCESS\n")
|
59
|
+
@cache.should_receive(:expire).with('x_v1', 1)
|
60
|
+
Cachy.cache(:x, :expires_in=>1){ 'SUCCESS' }.should == 'SUCCESS'
|
61
|
+
end
|
62
|
+
|
63
|
+
it "can expire" do
|
64
|
+
@cache.should_receive(:del).with('x_v1')
|
65
|
+
Cachy.expire(:x)
|
66
|
+
end
|
67
|
+
end
|
data/spec/cachy_spec.rb
CHANGED
@@ -285,7 +285,7 @@ describe Cachy do
|
|
285
285
|
before do
|
286
286
|
@mock = mock = {}
|
287
287
|
@cache.instance_eval{@data = mock}
|
288
|
-
def @mock.
|
288
|
+
def @mock.read_error_occurred
|
289
289
|
false
|
290
290
|
end
|
291
291
|
def @mock.[](x)
|
@@ -300,13 +300,13 @@ describe Cachy do
|
|
300
300
|
|
301
301
|
it "reads empty when it crashes" do
|
302
302
|
@mock.should_receive(:[]).and_return nil # e.g. Timout happended
|
303
|
-
@mock.should_receive(:
|
303
|
+
@mock.should_receive(:read_error_occurred).and_return true
|
304
304
|
Cachy.send(:read_versions).should == {}
|
305
305
|
end
|
306
306
|
|
307
307
|
it "marks as error when it crashes" do
|
308
308
|
Cachy.send(:class_variable_get, '@@cache_error').should == false
|
309
|
-
@mock.should_receive(:
|
309
|
+
@mock.should_receive(:read_error_occurred).and_return true
|
310
310
|
Cachy.send(:read_versions)
|
311
311
|
Cachy.send(:class_variable_get, '@@cache_error').should == true
|
312
312
|
end
|
@@ -330,6 +330,23 @@ describe Cachy do
|
|
330
330
|
end
|
331
331
|
end
|
332
332
|
|
333
|
+
describe :key_versions, 'with separate store' do
|
334
|
+
let(:key_cache){ TestCache.new }
|
335
|
+
|
336
|
+
it "reads from separate store" do
|
337
|
+
key_cache.write(Cachy::KEY_VERSIONS_KEY, :x => 1)
|
338
|
+
Cachy.key_versions_cache_store = key_cache
|
339
|
+
Cachy.key_versions.should == {:x => 1}
|
340
|
+
end
|
341
|
+
|
342
|
+
it "writes to separate store" do
|
343
|
+
key_cache.write(Cachy::KEY_VERSIONS_KEY, :x => 1)
|
344
|
+
Cachy.key_versions_cache_store = key_cache
|
345
|
+
Cachy.increment_key :x
|
346
|
+
Cachy.key_versions.should == {:x => 2}
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
333
350
|
describe :delete_key do
|
334
351
|
it "removes a key from key versions" do
|
335
352
|
Cachy.cache(:xxx){1}
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 5
|
9
|
+
version: 0.1.5
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Michael Grosser
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-10-29 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|
@@ -24,19 +24,22 @@ executables: []
|
|
24
24
|
|
25
25
|
extensions: []
|
26
26
|
|
27
|
-
extra_rdoc_files:
|
28
|
-
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
29
|
files:
|
30
|
-
- README.markdown
|
31
30
|
- Rakefile
|
31
|
+
- Readme.md
|
32
32
|
- VERSION
|
33
33
|
- cachy.gemspec
|
34
34
|
- lib/cachy.rb
|
35
|
+
- lib/cachy/memcache_timeout_protection.rb
|
35
36
|
- lib/cachy/memcached_wrapper.rb
|
36
37
|
- lib/cachy/moneta_wrapper.rb
|
38
|
+
- lib/cachy/redis_wrapper.rb
|
37
39
|
- lib/cachy/wrapper.rb
|
38
40
|
- spec/cachy/memcached_wrapper_spec.rb
|
39
41
|
- spec/cachy/moneta_wrapper_spec.rb
|
42
|
+
- spec/cachy/redis_wrapper_spec.rb
|
40
43
|
- spec/cachy_spec.rb
|
41
44
|
- spec/spec_helper.rb
|
42
45
|
- spec/test_cache.rb
|
@@ -73,6 +76,7 @@ summary: Caching library for projects that have many processes or many caches
|
|
73
76
|
test_files:
|
74
77
|
- spec/cachy/memcached_wrapper_spec.rb
|
75
78
|
- spec/cachy/moneta_wrapper_spec.rb
|
79
|
+
- spec/cachy/redis_wrapper_spec.rb
|
76
80
|
- spec/cachy_spec.rb
|
77
81
|
- spec/test_cache.rb
|
78
82
|
- spec/spec_helper.rb
|