Skip to content
This repository
Browse code

Decouple the local cache strategy from MemCacheStore for reuse with o…

…ther remote stores [#1653 state:resolved]

Signed-off-by: Joshua Peek <josh@joshpeek.com>
  • Loading branch information...
commit b08c96887538cf53670bb882e79996582375e6c9 1 parent 29e7a02
Lourens Naudé authored January 17, 2009 josh committed January 17, 2009
4  activesupport/lib/active_support/cache.rb
@@ -10,6 +10,10 @@ module Cache
10 10
     autoload :MemCacheStore, 'active_support/cache/mem_cache_store'
11 11
     autoload :CompressedMemCacheStore, 'active_support/cache/compressed_mem_cache_store'
12 12
 
  13
+    module Strategy
  14
+      autoload :LocalCache, 'active_support/cache/strategy/local_cache'
  15
+    end
  16
+
13 17
     # Creates a new CacheStore object according to the given options.
14 18
     #
15 19
     # If no arguments are passed to this method, then a new
64  activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -23,24 +23,6 @@ module Response # :nodoc:
23 23
         DELETED     = "DELETED\r\n"
24 24
       end
25 25
 
26  
-      # this allows caching of the fact that there is nothing in the remote cache
27  
-      NULL = 'mem_cache_store:null'
28  
-
29  
-      THREAD_LOCAL_KEY = :mem_cache_store_cache
30  
-
31  
-      class LocalCache
32  
-        def initialize(app)
33  
-          @app = app
34  
-        end
35  
-
36  
-        def call(env)
37  
-          Thread.current[THREAD_LOCAL_KEY] = MemoryStore.new
38  
-          @app.call(env)
39  
-        ensure
40  
-          Thread.current[THREAD_LOCAL_KEY] = nil
41  
-        end
42  
-      end
43  
-
44 26
       attr_reader :addresses
45 27
 
46 28
       # Creates a new MemCacheStore object, with the given memcached server
@@ -57,22 +39,13 @@ def initialize(*addresses)
57 39
         addresses = ["localhost"] if addresses.empty?
58 40
         @addresses = addresses
59 41
         @data = MemCache.new(addresses, options)
  42
+
  43
+        extend Strategy::LocalCache
60 44
       end
61 45
 
62 46
       def read(key, options = nil) # :nodoc:
63 47
         super
64  
-
65  
-        value = local_cache && local_cache.read(key)
66  
-        if value == NULL
67  
-          nil
68  
-        elsif value.nil?
69  
-          value = @data.get(key, raw?(options))
70  
-          local_cache.write(key, value || NULL) if local_cache
71  
-          value
72  
-        else
73  
-          # forcing the value to be immutable
74  
-          value.dup
75  
-        end
  48
+        @data.get(key, raw?(options))
76 49
       rescue MemCache::MemCacheError => e
77 50
         logger.error("MemCacheError (#{e}): #{e.message}")
78 51
         nil
@@ -91,7 +64,6 @@ def write(key, value, options = nil)
91 64
         # memcache-client will break the connection if you send it an integer
92 65
         # in raw mode, so we convert it to a string to be sure it continues working.
93 66
         value = value.to_s if raw?(options)
94  
-        local_cache.write(key, value || NULL) if local_cache
95 67
         response = @data.send(method, key, value, expires_in(options), raw?(options))
96 68
         response == Response::STORED
97 69
       rescue MemCache::MemCacheError => e
@@ -101,7 +73,6 @@ def write(key, value, options = nil)
101 73
 
102 74
       def delete(key, options = nil) # :nodoc:
103 75
         super
104  
-        local_cache.write(key, NULL) if local_cache
105 76
         response = @data.delete(key, expires_in(options))
106 77
         response == Response::DELETED
107 78
       rescue MemCache::MemCacheError => e
@@ -113,40 +84,22 @@ def exist?(key, options = nil) # :nodoc:
113 84
         # Doesn't call super, cause exist? in memcache is in fact a read
114 85
         # But who cares? Reading is very fast anyway
115 86
         # Local cache is checked first, if it doesn't know then memcache itself is read from
116  
-        value = local_cache.read(key) if local_cache
117  
-        if value == NULL
118  
-          false
119  
-        elsif value
120  
-          true
121  
-        else
122  
-          !read(key, options).nil?
123  
-        end
  87
+        !read(key, options).nil?
124 88
       end
125 89
 
126 90
       def increment(key, amount = 1) # :nodoc:
127 91
         log("incrementing", key, amount)
128 92
 
129 93
         response = @data.incr(key, amount)
130  
-        unless response == Response::NOT_FOUND
131  
-          local_cache.write(key, response.to_s) if local_cache
132  
-          response
133  
-        else
134  
-          nil
135  
-        end
  94
+        response == Response::NOT_FOUND ? nil : response
136 95
       rescue MemCache::MemCacheError
137 96
         nil
138 97
       end
139 98
 
140 99
       def decrement(key, amount = 1) # :nodoc:
141 100
         log("decrement", key, amount)
142  
-
143 101
         response = @data.decr(key, amount)
144  
-        unless response == Response::NOT_FOUND
145  
-          local_cache.write(key, response.to_s) if local_cache
146  
-          response
147  
-        else
148  
-          nil
149  
-        end
  102
+        response == Response::NOT_FOUND ? nil : response
150 103
       rescue MemCache::MemCacheError
151 104
         nil
152 105
       end
@@ -159,7 +112,6 @@ def delete_matched(matcher, options = nil) # :nodoc:
159 112
       end
160 113
 
161 114
       def clear
162  
-        local_cache.clear if local_cache
163 115
         @data.flush_all
164 116
       end
165 117
 
@@ -168,10 +120,6 @@ def stats
168 120
       end
169 121
 
170 122
       private
171  
-        def local_cache
172  
-          Thread.current[THREAD_LOCAL_KEY]
173  
-        end
174  
-
175 123
         def expires_in(options)
176 124
           (options && options[:expires_in]) || 0
177 125
         end
104  activesupport/lib/active_support/cache/strategy/local_cache.rb
... ...
@@ -0,0 +1,104 @@
  1
+module ActiveSupport
  2
+  module Cache
  3
+    module Strategy
  4
+      module LocalCache
  5
+        # this allows caching of the fact that there is nothing in the remote cache
  6
+        NULL = 'remote_cache_store:null'
  7
+
  8
+        def with_local_cache
  9
+          Thread.current[thread_local_key] = MemoryStore.new
  10
+          yield
  11
+        ensure
  12
+          Thread.current[thread_local_key] = nil
  13
+        end
  14
+
  15
+        def middleware
  16
+          @middleware ||= begin
  17
+            klass = Class.new
  18
+            klass.class_eval(<<-EOS, __FILE__, __LINE__)
  19
+              def initialize(app)
  20
+                @app = app
  21
+              end
  22
+
  23
+              def call(env)
  24
+                Thread.current[:#{thread_local_key}] = MemoryStore.new
  25
+                @app.call(env)
  26
+              ensure
  27
+                Thread.current[:#{thread_local_key}] = nil
  28
+              end
  29
+            EOS
  30
+            klass
  31
+          end
  32
+        end
  33
+
  34
+        def read(key, options = nil)
  35
+          value = local_cache && local_cache.read(key)
  36
+          if value == NULL
  37
+            nil
  38
+          elsif value.nil?
  39
+            value = super
  40
+            local_cache.write(key, value || NULL) if local_cache
  41
+            value
  42
+          else
  43
+            # forcing the value to be immutable
  44
+            value.dup
  45
+          end
  46
+        end
  47
+
  48
+        def write(key, value, options = nil)
  49
+          value = value.to_s if respond_to?(:raw?) && raw?(options)
  50
+          local_cache.write(key, value || NULL) if local_cache
  51
+          super
  52
+        end
  53
+
  54
+        def delete(key, options = nil)
  55
+          local_cache.write(key, NULL) if local_cache
  56
+          super
  57
+        end
  58
+
  59
+        def exist(key, options = nil)
  60
+          value = local_cache.read(key) if local_cache
  61
+          if value == NULL
  62
+            false
  63
+          elsif value
  64
+            true
  65
+          else
  66
+            super
  67
+          end
  68
+        end
  69
+
  70
+        def increment(key, amount = 1)
  71
+          if value = super
  72
+            local_cache.write(key, value.to_s) if local_cache
  73
+            value
  74
+          else
  75
+            nil
  76
+          end
  77
+        end
  78
+
  79
+        def decrement(key, amount = 1)
  80
+          if value = super
  81
+            local_cache.write(key, value.to_s) if local_cache
  82
+            value
  83
+          else
  84
+            nil
  85
+          end
  86
+        end
  87
+
  88
+        def clear
  89
+          local_cache.clear if local_cache
  90
+          super
  91
+        end
  92
+
  93
+        private
  94
+          def thread_local_key
  95
+            @thread_local_key ||= "#{self.class.name.underscore}_local_cache".gsub("/", "_").to_sym
  96
+          end
  97
+
  98
+          def local_cache
  99
+            Thread.current[thread_local_key]
  100
+          end
  101
+      end
  102
+    end
  103
+  end
  104
+end
36  activesupport/test/caching_test.rb
@@ -161,7 +161,7 @@ def setup
161 161
     include CacheStoreBehavior
162 162
 
163 163
     def test_store_objects_should_be_immutable
164  
-      with_local_cache do
  164
+      @cache.with_local_cache do
165 165
         @cache.write('foo', 'bar')
166 166
         @cache.read('foo').gsub!(/.*/, 'baz')
167 167
         assert_equal 'bar', @cache.read('foo')
@@ -169,7 +169,7 @@ def test_store_objects_should_be_immutable
169 169
     end
170 170
 
171 171
     def test_write_should_return_true_on_success
172  
-      with_local_cache do
  172
+      @cache.with_local_cache do
173 173
         result = @cache.write('foo', 'bar')
174 174
         assert_equal 'bar', @cache.read('foo') # make sure 'foo' was written
175 175
         assert result
@@ -177,7 +177,7 @@ def test_write_should_return_true_on_success
177 177
     end
178 178
 
179 179
     def test_local_writes_are_persistent_on_the_remote_cache
180  
-      with_local_cache do
  180
+      @cache.with_local_cache do
181 181
         @cache.write('foo', 'bar')
182 182
       end
183 183
 
@@ -185,7 +185,7 @@ def test_local_writes_are_persistent_on_the_remote_cache
185 185
     end
186 186
 
187 187
     def test_clear_also_clears_local_cache
188  
-      with_local_cache do
  188
+      @cache.with_local_cache do
189 189
         @cache.write('foo', 'bar')
190 190
         @cache.clear
191 191
         assert_nil @cache.read('foo')
@@ -193,7 +193,7 @@ def test_clear_also_clears_local_cache
193 193
     end
194 194
 
195 195
     def test_local_cache_of_read_and_write
196  
-      with_local_cache do
  196
+      @cache.with_local_cache do
197 197
         @cache.write('foo', 'bar')
198 198
         @data.flush_all # Clear remote cache
199 199
         assert_equal 'bar', @cache.read('foo')
@@ -201,7 +201,7 @@ def test_local_cache_of_read_and_write
201 201
     end
202 202
 
203 203
     def test_local_cache_of_delete
204  
-      with_local_cache do
  204
+      @cache.with_local_cache do
205 205
         @cache.write('foo', 'bar')
206 206
         @cache.delete('foo')
207 207
         @data.flush_all # Clear remote cache
@@ -210,7 +210,7 @@ def test_local_cache_of_delete
210 210
     end
211 211
 
212 212
     def test_local_cache_of_exist
213  
-      with_local_cache do
  213
+      @cache.with_local_cache do
214 214
         @cache.write('foo', 'bar')
215 215
         @cache.instance_variable_set(:@data, nil)
216 216
         @data.flush_all # Clear remote cache
@@ -219,7 +219,7 @@ def test_local_cache_of_exist
219 219
     end
220 220
 
221 221
     def test_local_cache_of_increment
222  
-      with_local_cache do
  222
+      @cache.with_local_cache do
223 223
         @cache.write('foo', 1, :raw => true)
224 224
         @cache.increment('foo')
225 225
         @data.flush_all # Clear remote cache
@@ -228,7 +228,7 @@ def test_local_cache_of_increment
228 228
     end
229 229
 
230 230
     def test_local_cache_of_decrement
231  
-      with_local_cache do
  231
+      @cache.with_local_cache do
232 232
         @cache.write('foo', 1, :raw => true)
233 233
         @cache.decrement('foo')
234 234
         @data.flush_all # Clear remote cache
@@ -237,20 +237,22 @@ def test_local_cache_of_decrement
237 237
     end
238 238
 
239 239
     def test_exist_with_nulls_cached_locally
240  
-      with_local_cache do
  240
+      @cache.with_local_cache do
241 241
         @cache.write('foo', 'bar')
242 242
         @cache.delete('foo')
243 243
         assert !@cache.exist?('foo')
244 244
       end
245 245
     end
246 246
 
247  
-    private
248  
-      def with_local_cache
249  
-        Thread.current[ActiveSupport::Cache::MemCacheStore::THREAD_LOCAL_KEY] = ActiveSupport::Cache::MemoryStore.new
250  
-        yield
251  
-      ensure
252  
-        Thread.current[ActiveSupport::Cache::MemCacheStore::THREAD_LOCAL_KEY] = nil
253  
-      end
  247
+    def test_middleware
  248
+      app = lambda { |env|
  249
+        result = @cache.write('foo', 'bar')
  250
+        assert_equal 'bar', @cache.read('foo') # make sure 'foo' was written
  251
+        assert result
  252
+      }
  253
+      app = @cache.middleware.new(app)
  254
+      app.call({})
  255
+    end
254 256
   end
255 257
 
256 258
   class CompressedMemCacheStore < ActiveSupport::TestCase
6  railties/lib/initializer.rb
@@ -414,8 +414,10 @@ def initialize_database
414 414
     def initialize_cache
415 415
       unless defined?(RAILS_CACHE)
416 416
         silence_warnings { Object.const_set "RAILS_CACHE", ActiveSupport::Cache.lookup_store(configuration.cache_store) }
417  
-        if RAILS_CACHE.class.name == "ActiveSupport::Cache::MemCacheStore"
418  
-          configuration.middleware.insert_after(:"ActionController::Failsafe", ActiveSupport::Cache::MemCacheStore::LocalCache)
  417
+
  418
+        if RAILS_CACHE.respond_to?(:middleware)
  419
+          # Insert middleware to setup and teardown local cache for each request
  420
+          configuration.middleware.insert_after(:"ActionController::Failsafe", RAILS_CACHE.middleware)
419 421
         end
420 422
       end
421 423
     end

1 note on commit b08c968

José Valim
Owner

Great refactoring! :)

Please sign in to comment.
Something went wrong with that request. Please try again.