Skip to content

Commit

Permalink
add memcached metastore implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
rtomayko committed Oct 20, 2008
1 parent 424515d commit 8b9d5f2
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 21 deletions.
72 changes: 56 additions & 16 deletions lib/rack/cache/meta_store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ def lookup(request, entity_store)
# Write a cache entry to the store under the given key. Existing
# entries are read and any that match the response are removed.
# This method calls #write with the new list of cache entries.
#--
# TODO canonicalize URL key
def store(request, response, entity_store)
# TODO canonicalize URL key
key = request.fullpath
req = persist_request(request)
res = persist_response(response)
Expand All @@ -98,7 +99,6 @@ def store(request, response, entity_store)
end

private

# Extract the environment Hash from +request+ while making any
# necessary modifications in preparation for persistence. The Hash
# returned must be marshalable.
Expand Down Expand Up @@ -129,7 +129,6 @@ def requests_match?(vary, env1, env2)
end

protected

# Locate all cached negotiations that match the specified request
# URL key. The result must be an Array of all cached negotation
# tuples. An empty Array must be returned if nothing is cached for
Expand All @@ -145,7 +144,8 @@ def write(key, negotiations)
raise NotImplemented
end

# Remove all cached entries at the key specified.
# Remove all cached entries at the key specified. No error is raised
# when the key does not exist.
def purge(key)
raise NotImplemented
end
Expand All @@ -154,6 +154,12 @@ def default_entity_store
raise NotImplemented
end

# Generate a SHA-1 hex digest for the specified string. This is a
# simple utility method for meta store implementations.
def hexdigest(data)
Digest::SHA1.hexdigest(data)
end

public

# Concrete MetaStore implementation that uses a simple Hash to store
Expand All @@ -172,7 +178,8 @@ def write(key, entries)
end

def purge(key)
@hash.delete(key) || []
@hash.delete(key)
nil
end

def default_entity_store
Expand All @@ -184,7 +191,6 @@ def to_hash
end
end


# Concrete MetaStore implementation that stores negotiations on disk.
class Disk < MetaStore

Expand Down Expand Up @@ -212,27 +218,21 @@ def write(key, entries)

def purge(key)
path = key_path(key)
result = read(key)
File.unlink(path)
result
nil
rescue Errno::ENOENT
[]
nil
end

def default_entity_store
Rack::Cache::EntityStore::Disk
end

private

def key_path(key)
File.join(root, spread(hexdigest(key)))
end

def hexdigest(key)
Digest::SHA1.hexdigest(key)
end

def spread(sha, n=2)
sha = sha.dup
sha[n,0] = '/'
Expand All @@ -241,8 +241,48 @@ def spread(sha, n=2)

end

# TODO: Sqlite3 MetaStore implementation
# TODO: Memcached MetaStore implementation
# Stores negotiation meta information in memcached. Keys are not stored
# directly since memcached has a 250-byte limit on key names. Instead,
# the SHA-1 hexdigest of the key is used.
class MemCache < MetaStore

# A Memcached instance.
attr_reader :cache

def initialize(server="localhost:11211")
@cache =
if server.respond_to?(:stats)
server
else
require 'memcached'
Memcached.new(server)
end
end

def read(key)
key = hexdigest(key)
cache.get(key)
rescue Memcached::NotFound
[]
end

def write(key, entries)
key = hexdigest(key)
cache.set(key, entries)
end

def purge(key)
key = hexdigest(key)
cache.delete(key)
nil
rescue Memcached::NotFound
nil
end

def default_entity_store
Rack::Cache::EntityStore::Disk
end
end

end

Expand Down
54 changes: 49 additions & 5 deletions test/meta_store_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,23 @@
@store.read('/test').should.be.empty
end

it 'returns purged entries from #purge' do
it 'returns nil from #purge' do
@store.write('/test', [[{},{}]])
@store.purge('/test').should.be == [[{},{}]]
@store.purge('/test').should.be.empty
@store.purge('/test').should.be nil
@store.read('/test').should.be == []
end

%w[/test http://example.com:8080/ /test?x=y /test?x=y&p=q].each do |key|
it "can read and write key: '#{key}'" do
lambda { @store.write(key, [[{},{}]]) }.should.not.raise
@store.read(key).should.be == [[{},{}]]
end
end

it "can read and write fairly large keys" do
key = "b" * 4096
lambda { @store.write(key, [[{},{}]]) }.should.not.raise
@store.read(key).should.be == [[{},{}]]
end

# Abstract methods ===========================================================
Expand Down Expand Up @@ -83,7 +96,6 @@

# Vary =======================================================================


it 'does not return entries that Vary with #lookup' do
req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'})
Expand Down Expand Up @@ -157,8 +169,22 @@

end

describe 'Rack::Cache::MetaStore' do
# Set the MEMCACHED environment variable as follows to enable testing
# of the MemCached meta store.
ENV['MEMCACHED'] ||= 'localhost:11215'
$memcached = nil

def have_memcached?(server=ENV['MEMCACHED'])
return true if $memcached
require 'memcached'
$memcached = Memcached.new(server)
$memcached.set('ping', '')
true
rescue => boom
false
end

describe 'Rack::Cache::MetaStore' do
describe 'Heap' do
it_should_behave_like 'A Rack::Cache::MetaStore Implementation'
before { @store = Rack::Cache::MetaStore::Heap.new }
Expand All @@ -176,4 +202,22 @@
remove_entry_secure @temp_dir
end
end

if have_memcached?
describe 'MemCache' do
it_should_behave_like 'A Rack::Cache::MetaStore Implementation'
before :each do
@temp_dir = create_temp_directory
$memcached.flush
@store = Rack::Cache::MetaStore::MemCache.new($memcached)
@entity_store = Rack::Cache::EntityStore::Disk.new("#{@temp_dir}/entity")
end
after :each do
@store, @entity_store = nil
remove_entry_secure @temp_dir
end
end
else
STDERR.puts "memcached tests disabled: MEMCACHED environment variable not set or server down."
end
end

0 comments on commit 8b9d5f2

Please sign in to comment.