diff --git a/doc/configuration.markdown b/doc/configuration.markdown index 0925561..5db4793 100644 --- a/doc/configuration.markdown +++ b/doc/configuration.markdown @@ -124,4 +124,14 @@ which will act as the cache key generator: end For more options see the [Rack::Request documentation](http://rack.rubyforge.org/doc/classes/Rack/Request.html) - + +### `use_native_ttl` + +Passes on the expiration timestamp to cache stores that support it, like +Memcache and Redis. This may be necessary with some stores to keep them from +filling up, e.g. if using a Redis backend and the `volatile-ttl` expiration +policy. + +If using `memcached`, it will speed up misses slightly as the middleware won't +need to fetch metadata and check timestamps. + diff --git a/lib/rack/cache/meta_store.rb b/lib/rack/cache/meta_store.rb index a06e02a..28f35b4 100644 --- a/lib/rack/cache/meta_store.rb +++ b/lib/rack/cache/meta_store.rb @@ -91,7 +91,11 @@ def store(request, response, entity_store) headers.delete 'Age' entries.unshift [stored_env, headers] - write key, entries + if request.env['rack-cache.use_native_ttl'] && response.fresh? + write key, entries, response.ttl + else + write key, entries + end key end @@ -165,7 +169,7 @@ def read(key) # Store an Array of request/response pairs for the given key. Concrete # implementations should not attempt to filter or concatenate the # list in any way. - def write(key, negotiations) + def write(key, negotiations, ttl = nil) raise NotImplementedError end @@ -198,7 +202,7 @@ def read(key) end end - def write(key, entries) + def write(key, entries, ttl = nil) @hash[key] = Marshal.dump(entries) end @@ -236,7 +240,7 @@ def read(key) [] end - def write(key, entries) + def write(key, entries, ttl = nil) tries = 0 begin path = key_path(key) @@ -335,9 +339,10 @@ def read(key) cache.get(key) || [] end - def write(key, entries) + # Default TTL to zero, interpreted as "don't expire" by Memcached. + def write(key, entries, ttl = 0) key = hexdigest(key) - cache.set(key, entries) + cache.set(key, entries, ttl) end def purge(key) @@ -369,9 +374,10 @@ def read(key) [] end - def write(key, entries) + # Default TTL to zero, interpreted as "don't expire" by Memcached. + def write(key, entries, ttl = 0) key = hexdigest(key) - cache.set(key, entries) + cache.set(key, entries, ttl) end def purge(key) diff --git a/test/meta_store_test.rb b/test/meta_store_test.rb index c587a3e..7fa951a 100644 --- a/test/meta_store_test.rb +++ b/test/meta_store_test.rb @@ -116,6 +116,12 @@ def self.call(request); request.path_info.reverse end store_simple_entry('/bad', { 'SOME_THING' => Proc.new {} }) end + it 'supports a ttl parameter for #write' do + @store.write('/test', [[{},{}],[{},{}]], 0) + tuples = @store.read('/test') + tuples.must_equal [ [{},{}], [{},{}] ] + end + # Abstract methods =========================================================== it 'stores a cache entry' do @@ -270,6 +276,32 @@ def purge(*args); nil end @store.read(key).length.must_equal 2 end + + # TTL ==================================================================== + + context 'logging writes' do + write_intercept = Module.new do + attr_reader :last_write + + def write(*args) + @last_write = args + super + end + end + + before do + @entity_store.extend(write_intercept) + @store.extend(write_intercept) + end + + it 'passes a TTL to the stores is use_native_ttl is truthy' do + req1 = mock_request('/test', { 'rack-cache.use_native_ttl' => true }) + res1 = mock_response(200, {'Cache-Control' => 'max-age=42'}, ['foo']) + @store.store(req1, res1, @entity_store) + @entity_store.last_write.must_equal [['foo'], 42] + @store.last_write[2].must_equal 42 + end + end end end end