cachy 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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://pragmatig.wordpress.com)
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.4
1
+ 0.1.5
@@ -5,26 +5,26 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{cachy}
8
- s.version = "0.1.4"
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-08-14}
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
- "README.markdown",
19
- "Rakefile",
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"
@@ -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=(x)
120
- if x.respond_to? :read and x.respond_to? :write
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.cache_store = ActionController::Base.cache_store if defined? ActionController::Base
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.read_versions
158
- data = cache_store.instance_variable_get('@data')
159
- if data.respond_to? :read_error_occured # its a MemCache with read_error detection !
160
- result = data[KEY_VERSIONS_KEY] || {}
161
- @@cache_error = data.read_error_occured
162
- result
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.read(KEY_VERSIONS_KEY) || {}
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
- cache_store.write(KEY_VERSIONS_KEY, data) unless @@cache_error
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
@@ -1,6 +1,5 @@
1
1
  require 'cachy/wrapper'
2
2
 
3
- # Wrapper for Memcached
4
3
  class Cachy::MemcachedWrapper < Cachy::Wrapper
5
4
  def write(key, result, options={})
6
5
  @wrapped.set(key, result, options[:expires_in].to_i)
@@ -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
@@ -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.read_error_occured
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(:read_error_occured).and_return true
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(:read_error_occured).and_return true
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
- - 4
9
- version: 0.1.4
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-08-14 00:00:00 +02:00
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
- - README.markdown
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