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