Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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é methodmissing authored josh committed
4 activesupport/lib/active_support/cache.rb
View
@@ -10,6 +10,10 @@ module Cache
autoload :MemCacheStore, 'active_support/cache/mem_cache_store'
autoload :CompressedMemCacheStore, 'active_support/cache/compressed_mem_cache_store'
+ module Strategy
+ autoload :LocalCache, 'active_support/cache/strategy/local_cache'
+ end
+
# Creates a new CacheStore object according to the given options.
#
# If no arguments are passed to this method, then a new
64 activesupport/lib/active_support/cache/mem_cache_store.rb
View
@@ -23,24 +23,6 @@ module Response # :nodoc:
DELETED = "DELETED\r\n"
end
- # this allows caching of the fact that there is nothing in the remote cache
- NULL = 'mem_cache_store:null'
-
- THREAD_LOCAL_KEY = :mem_cache_store_cache
-
- class LocalCache
- def initialize(app)
- @app = app
- end
-
- def call(env)
- Thread.current[THREAD_LOCAL_KEY] = MemoryStore.new
- @app.call(env)
- ensure
- Thread.current[THREAD_LOCAL_KEY] = nil
- end
- end
-
attr_reader :addresses
# Creates a new MemCacheStore object, with the given memcached server
@@ -57,22 +39,13 @@ def initialize(*addresses)
addresses = ["localhost"] if addresses.empty?
@addresses = addresses
@data = MemCache.new(addresses, options)
+
+ extend Strategy::LocalCache
end
def read(key, options = nil) # :nodoc:
super
-
- value = local_cache && local_cache.read(key)
- if value == NULL
- nil
- elsif value.nil?
- value = @data.get(key, raw?(options))
- local_cache.write(key, value || NULL) if local_cache
- value
- else
- # forcing the value to be immutable
- value.dup
- end
+ @data.get(key, raw?(options))
rescue MemCache::MemCacheError => e
logger.error("MemCacheError (#{e}): #{e.message}")
nil
@@ -91,7 +64,6 @@ def write(key, value, options = nil)
# memcache-client will break the connection if you send it an integer
# in raw mode, so we convert it to a string to be sure it continues working.
value = value.to_s if raw?(options)
- local_cache.write(key, value || NULL) if local_cache
response = @data.send(method, key, value, expires_in(options), raw?(options))
response == Response::STORED
rescue MemCache::MemCacheError => e
@@ -101,7 +73,6 @@ def write(key, value, options = nil)
def delete(key, options = nil) # :nodoc:
super
- local_cache.write(key, NULL) if local_cache
response = @data.delete(key, expires_in(options))
response == Response::DELETED
rescue MemCache::MemCacheError => e
@@ -113,40 +84,22 @@ def exist?(key, options = nil) # :nodoc:
# Doesn't call super, cause exist? in memcache is in fact a read
# But who cares? Reading is very fast anyway
# Local cache is checked first, if it doesn't know then memcache itself is read from
- value = local_cache.read(key) if local_cache
- if value == NULL
- false
- elsif value
- true
- else
- !read(key, options).nil?
- end
+ !read(key, options).nil?
end
def increment(key, amount = 1) # :nodoc:
log("incrementing", key, amount)
response = @data.incr(key, amount)
- unless response == Response::NOT_FOUND
- local_cache.write(key, response.to_s) if local_cache
- response
- else
- nil
- end
+ response == Response::NOT_FOUND ? nil : response
rescue MemCache::MemCacheError
nil
end
def decrement(key, amount = 1) # :nodoc:
log("decrement", key, amount)
-
response = @data.decr(key, amount)
- unless response == Response::NOT_FOUND
- local_cache.write(key, response.to_s) if local_cache
- response
- else
- nil
- end
+ response == Response::NOT_FOUND ? nil : response
rescue MemCache::MemCacheError
nil
end
@@ -159,7 +112,6 @@ def delete_matched(matcher, options = nil) # :nodoc:
end
def clear
- local_cache.clear if local_cache
@data.flush_all
end
@@ -168,10 +120,6 @@ def stats
end
private
- def local_cache
- Thread.current[THREAD_LOCAL_KEY]
- end
-
def expires_in(options)
(options && options[:expires_in]) || 0
end
104 activesupport/lib/active_support/cache/strategy/local_cache.rb
View
@@ -0,0 +1,104 @@
+module ActiveSupport
+ module Cache
+ module Strategy
+ module LocalCache
+ # this allows caching of the fact that there is nothing in the remote cache
+ NULL = 'remote_cache_store:null'
+
+ def with_local_cache
+ Thread.current[thread_local_key] = MemoryStore.new
+ yield
+ ensure
+ Thread.current[thread_local_key] = nil
+ end
+
+ def middleware
+ @middleware ||= begin
+ klass = Class.new
+ klass.class_eval(<<-EOS, __FILE__, __LINE__)
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ Thread.current[:#{thread_local_key}] = MemoryStore.new
+ @app.call(env)
+ ensure
+ Thread.current[:#{thread_local_key}] = nil
+ end
+ EOS
+ klass
+ end
+ end
+
+ def read(key, options = nil)
+ value = local_cache && local_cache.read(key)
+ if value == NULL
+ nil
+ elsif value.nil?
+ value = super
+ local_cache.write(key, value || NULL) if local_cache
+ value
+ else
+ # forcing the value to be immutable
+ value.dup
+ end
+ end
+
+ def write(key, value, options = nil)
+ value = value.to_s if respond_to?(:raw?) && raw?(options)
+ local_cache.write(key, value || NULL) if local_cache
+ super
+ end
+
+ def delete(key, options = nil)
+ local_cache.write(key, NULL) if local_cache
+ super
+ end
+
+ def exist(key, options = nil)
+ value = local_cache.read(key) if local_cache
+ if value == NULL
+ false
+ elsif value
+ true
+ else
+ super
+ end
+ end
+
+ def increment(key, amount = 1)
+ if value = super
+ local_cache.write(key, value.to_s) if local_cache
+ value
+ else
+ nil
+ end
+ end
+
+ def decrement(key, amount = 1)
+ if value = super
+ local_cache.write(key, value.to_s) if local_cache
+ value
+ else
+ nil
+ end
+ end
+
+ def clear
+ local_cache.clear if local_cache
+ super
+ end
+
+ private
+ def thread_local_key
+ @thread_local_key ||= "#{self.class.name.underscore}_local_cache".gsub("/", "_").to_sym
+ end
+
+ def local_cache
+ Thread.current[thread_local_key]
+ end
+ end
+ end
+ end
+end
36 activesupport/test/caching_test.rb
View
@@ -161,7 +161,7 @@ def setup
include CacheStoreBehavior
def test_store_objects_should_be_immutable
- with_local_cache do
+ @cache.with_local_cache do
@cache.write('foo', 'bar')
@cache.read('foo').gsub!(/.*/, 'baz')
assert_equal 'bar', @cache.read('foo')
@@ -169,7 +169,7 @@ def test_store_objects_should_be_immutable
end
def test_write_should_return_true_on_success
- with_local_cache do
+ @cache.with_local_cache do
result = @cache.write('foo', 'bar')
assert_equal 'bar', @cache.read('foo') # make sure 'foo' was written
assert result
@@ -177,7 +177,7 @@ def test_write_should_return_true_on_success
end
def test_local_writes_are_persistent_on_the_remote_cache
- with_local_cache do
+ @cache.with_local_cache do
@cache.write('foo', 'bar')
end
@@ -185,7 +185,7 @@ def test_local_writes_are_persistent_on_the_remote_cache
end
def test_clear_also_clears_local_cache
- with_local_cache do
+ @cache.with_local_cache do
@cache.write('foo', 'bar')
@cache.clear
assert_nil @cache.read('foo')
@@ -193,7 +193,7 @@ def test_clear_also_clears_local_cache
end
def test_local_cache_of_read_and_write
- with_local_cache do
+ @cache.with_local_cache do
@cache.write('foo', 'bar')
@data.flush_all # Clear remote cache
assert_equal 'bar', @cache.read('foo')
@@ -201,7 +201,7 @@ def test_local_cache_of_read_and_write
end
def test_local_cache_of_delete
- with_local_cache do
+ @cache.with_local_cache do
@cache.write('foo', 'bar')
@cache.delete('foo')
@data.flush_all # Clear remote cache
@@ -210,7 +210,7 @@ def test_local_cache_of_delete
end
def test_local_cache_of_exist
- with_local_cache do
+ @cache.with_local_cache do
@cache.write('foo', 'bar')
@cache.instance_variable_set(:@data, nil)
@data.flush_all # Clear remote cache
@@ -219,7 +219,7 @@ def test_local_cache_of_exist
end
def test_local_cache_of_increment
- with_local_cache do
+ @cache.with_local_cache do
@cache.write('foo', 1, :raw => true)
@cache.increment('foo')
@data.flush_all # Clear remote cache
@@ -228,7 +228,7 @@ def test_local_cache_of_increment
end
def test_local_cache_of_decrement
- with_local_cache do
+ @cache.with_local_cache do
@cache.write('foo', 1, :raw => true)
@cache.decrement('foo')
@data.flush_all # Clear remote cache
@@ -237,20 +237,22 @@ def test_local_cache_of_decrement
end
def test_exist_with_nulls_cached_locally
- with_local_cache do
+ @cache.with_local_cache do
@cache.write('foo', 'bar')
@cache.delete('foo')
assert !@cache.exist?('foo')
end
end
- private
- def with_local_cache
- Thread.current[ActiveSupport::Cache::MemCacheStore::THREAD_LOCAL_KEY] = ActiveSupport::Cache::MemoryStore.new
- yield
- ensure
- Thread.current[ActiveSupport::Cache::MemCacheStore::THREAD_LOCAL_KEY] = nil
- end
+ def test_middleware
+ app = lambda { |env|
+ result = @cache.write('foo', 'bar')
+ assert_equal 'bar', @cache.read('foo') # make sure 'foo' was written
+ assert result
+ }
+ app = @cache.middleware.new(app)
+ app.call({})
+ end
end
class CompressedMemCacheStore < ActiveSupport::TestCase
6 railties/lib/initializer.rb
View
@@ -414,8 +414,10 @@ def initialize_database
def initialize_cache
unless defined?(RAILS_CACHE)
silence_warnings { Object.const_set "RAILS_CACHE", ActiveSupport::Cache.lookup_store(configuration.cache_store) }
- if RAILS_CACHE.class.name == "ActiveSupport::Cache::MemCacheStore"
- configuration.middleware.insert_after(:"ActionController::Failsafe", ActiveSupport::Cache::MemCacheStore::LocalCache)
+
+ if RAILS_CACHE.respond_to?(:middleware)
+ # Insert middleware to setup and teardown local cache for each request
+ configuration.middleware.insert_after(:"ActionController::Failsafe", RAILS_CACHE.middleware)
end
end
end

1 comment on commit b08c968

José Valim
Owner

Great refactoring! :)

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