cachetastic-memcache-pool 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ .DS_Store
2
+ log
3
+ coverage
4
+ .rvmrc
5
+ *.gem
6
+ .bundle
7
+ Gemfile.lock
8
+ pkg/*
9
+
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in cachetastic-dalli.gemspec
4
+ gemspec
data/README ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ desc "Run specs"
6
+ RSpec::Core::RakeTask.new do |t|
7
+ t.pattern = "./spec/**/*_spec.rb"
8
+ end
9
+
10
+ desc "Generate code coverage"
11
+ RSpec::Core::RakeTask.new(:rcov) do |t|
12
+ t.pattern = "./spec/**/*_spec.rb"
13
+ t.rcov = true
14
+ t.rcov_opts = ['--exclude', 'spec']
15
+ end
16
+
17
+ desc 'Default: run specs.'
18
+ task :default => :spec
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "cachetastic/adapters/memcache_pool/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "cachetastic-memcache-pool"
7
+ s.version = Cachetastic::Adapters::MemcachePool::VERSION
8
+ s.authors = ["Jason Wadsworth"]
9
+ s.email = ["jason@gazelle.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Cachetastic Memcached Adapter with Connection Pooling}
12
+ s.description = %q{Cachetastic Memcached Adapter with Connection Pooling}
13
+
14
+ s.rubyforge_project = "cachetastic-memcache-pool"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "rspec", "~> 2.6.0"
22
+ s.add_development_dependency "rcov", "~> 0.9.0"
23
+
24
+ s.add_runtime_dependency "cachetastic", "~> 3.0.0"
25
+ s.add_runtime_dependency "memcache-client", "~> 1.8.5"
26
+ end
@@ -0,0 +1,4 @@
1
+ require 'memcache'
2
+ require 'cachetastic'
3
+ require 'cachetastic/adapters/memcache_pool/version'
4
+ require 'cachetastic/adapters/memcache_pool/adapter'
@@ -0,0 +1,149 @@
1
+ module Cachetastic # :nodoc:
2
+ module Adapters
3
+ module MemcachePool
4
+ # An adapter to cache objects to the file system.
5
+ #
6
+ # This adapter supports the following configuration settings,
7
+ # in addition to the default settings:
8
+ #
9
+ # configatron.cachetastic.defaults.servers = ['127.0.0.1:11211']
10
+ # configatron.cachetastic.defaults.mc_options = {:c_threshold => 10_000,
11
+ # :compression => true,
12
+ # :debug => false,
13
+ # :readonly => false,
14
+ # :urlencode => false}
15
+ # configatron.cachetastic.delete_delay = 0
16
+ #
17
+ # The <tt>servers</tt> setting defines an <tt>Array</tt> of Mecached
18
+ # servers, represented as "<host>:<port>".
19
+ #
20
+ # The <tt>mc_options</tt> setting is a <tt>Hash</tt> of settings required
21
+ # by Memcached. See the Memcached documentation for more information on
22
+ # what the settings mean.
23
+ #
24
+ # The <tt>delete_delay</tt> setting tells Memcached how long to wait
25
+ # before it deletes the object. This is not the same as <tt>expiry_time</tt>.
26
+ # It is only used when the <tt>delete</tt> method is called.
27
+ #
28
+ # See <tt>Cachetastic::Adapters::Base</tt> for a list of public API
29
+ # methods.
30
+ class Adapter < Cachetastic::Adapters::Base
31
+
32
+ def initialize(klass) # :nodoc:
33
+ define_accessor(:servers)
34
+ define_accessor(:mc_options)
35
+ define_accessor(:delete_delay)
36
+ self.delete_delay = 0
37
+ self.servers = ['127.0.0.1:11211']
38
+ self.mc_options = {:c_threshold => 10_000,
39
+ :compression => true,
40
+ :debug => false,
41
+ :readonly => false,
42
+ :urlencode => false}
43
+ super
44
+ connection
45
+ end
46
+
47
+ def get(key) # :nodoc:
48
+ connection.get(transform_key(key), false)
49
+ end # get
50
+
51
+ def set(key, value, expiry_time = configatron.cachetastic.defaults.default_expiry) # :nodoc:
52
+ connection.set(transform_key(key), marshal(value), expiry_time, false)
53
+ end # set
54
+
55
+ def delete(key) # :nodoc:
56
+ connection.delete(transform_key(key), self.delete_delay)
57
+ end # delete
58
+
59
+ def expire_all # :nodoc:
60
+ increment_version
61
+ return nil
62
+ end # expire_all
63
+
64
+ def transform_key(key) # :nodoc:
65
+ namespace + ':' + key.to_s.hexdigest
66
+ end
67
+
68
+ # Return <tt>false</tt> if the connection to Memcached is
69
+ # either <tt>nil</tt> or not active.
70
+ def valid?
71
+ return false if @_mc_connection.nil?
72
+ return false unless @_mc_connection.active?
73
+ return true
74
+ end
75
+
76
+ private
77
+ def connection
78
+ self.class.data_connection(self)
79
+ end
80
+
81
+ def ns_connection
82
+ self.class.ns_connection(self)
83
+ end
84
+
85
+ def increment_version
86
+ name = self.klass.name
87
+ v = get_version
88
+ ns_connection.set(name, v + 1)
89
+ end
90
+
91
+ def get_version
92
+ name = self.klass.name
93
+ v = ns_connection.get(name)
94
+ if v.nil?
95
+ ns_connection.set(name, 1)
96
+ v = 1
97
+ end
98
+ v
99
+ end
100
+
101
+ def namespace
102
+ @_ns_version = get_version
103
+ "#{self.klass.name}.#{@_ns_version}"
104
+ end
105
+
106
+ class << self
107
+ # JDW: TODO: Extract all of this into a MemCachePool class, and add the ability to do true thread-safe pooling
108
+ def reset_connections
109
+ @_connections_by_klass = {}
110
+ @_connections_by_digest = {}
111
+ @_ns_connections_by_klass = {}
112
+ @_ns_connections_by_digest = {}
113
+ end
114
+
115
+ def connection_digest(adapter)
116
+ {:servers => adapter.servers, :mc_options => adapter.mc_options}.to_s.hexdigest
117
+ end
118
+
119
+ def data_connection(adapter)
120
+ @_connections_by_klass ||= {}
121
+ @_connections_by_digest ||= {}
122
+ return get_connection(adapter, @_connections_by_klass, @_connections_by_digest, nil)
123
+ end
124
+
125
+ def ns_connection(adapter)
126
+ @_ns_connections_by_klass ||= {}
127
+ @_ns_connections_by_digest ||= {}
128
+ return get_connection(adapter, @_ns_connections_by_klass, @_ns_connections_by_digest, :namespace_versions)
129
+ end
130
+
131
+ def get_connection(adapter, connections_by_class, connections_by_digest, namespace)
132
+ matching_connection = connections_by_class[adapter.klass.name]
133
+ if !matching_connection || !matching_connection.active?
134
+ digest = connection_digest(adapter)
135
+ matching_connection = connections_by_digest[digest]
136
+ if !matching_connection || !matching_connection.active?
137
+ matching_connection = MemCache.new(adapter.servers, adapter.mc_options.merge(:namespace => namespace))
138
+ end
139
+ connections_by_digest[digest] = matching_connection
140
+ end
141
+ connections_by_class[adapter.klass.name] = matching_connection
142
+ matching_connection
143
+ end
144
+ end
145
+
146
+ end # Adapter
147
+ end # MemcachePool
148
+ end # Adapters
149
+ end # Cachetastic
@@ -0,0 +1,7 @@
1
+ module Cachetastic
2
+ module Adapters
3
+ module MemcachePool
4
+ VERSION = "0.1.0"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,147 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cachetastic::Adapters::MemcachePool::Adapter do
4
+ before(:each) do
5
+ configatron.temp_start
6
+ configatron.cachetastic.defaults.adapter = Cachetastic::Adapters::MemcachePool::Adapter
7
+ clear_test_cache_keys
8
+ end
9
+
10
+ before(:each) do
11
+ pending 'JDW: This is an integration test. It requires a memcached server to be running on localhost:11211'
12
+ end
13
+
14
+ after(:each) do
15
+ Cachetastic::Adapters::MemcachePool::Adapter.reset_connections
16
+ configatron.temp_end
17
+ end
18
+
19
+ class FakeCacheClass; end
20
+ class AnotherFakeCacheClass; end
21
+
22
+ def clear_test_cache_keys
23
+ # Clear namespace values
24
+ cache_classes = [FakeCacheClass, AnotherFakeCacheClass]
25
+ ns_connection = memcached_connection(:namespace_versions)
26
+ cache_classes.each do |cache_classs|
27
+ ns_connection.delete(cache_classs.name)
28
+ end
29
+
30
+ # Clear data values
31
+ data_connection = memcached_connection(nil)
32
+ cache_classes.each do |cache_class|
33
+ %w{key1 key2}.each do |key|
34
+ (1..2).each do |version|
35
+ data_connection.delete("#{cache_class.name}.${version}:#{key.to_s.hexdigest}")
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def expect_connection_creation(namespace)
42
+ mock_cache = mock(MemCache, :get => nil, :active? => true)
43
+ MemCache.should_receive(:new) do |servers, options|
44
+ servers.should == ['127.0.0.1:11211']
45
+ options[:namespace].should == namespace
46
+ mock_cache
47
+ end
48
+ mock_cache
49
+ end
50
+
51
+ def memcached_connection(namespace)
52
+ servers = ['127.0.0.1:11211']
53
+ mc_options = {
54
+ :c_threshold => 10_000,
55
+ :compression => true,
56
+ :debug => true,
57
+ :readonly => false,
58
+ :urlencode => false,
59
+ :namespace => namespace
60
+ }
61
+ MemCache.new(servers, mc_options)
62
+ end
63
+
64
+ def check_memcached(key, expected_value, namespace = nil)
65
+ mc = memcached_connection(namespace)
66
+ mc.get(key.hexdigest).should == expected_value
67
+ end
68
+
69
+ it "should set a value in memcached" do
70
+ adapter = Cachetastic::Adapters::MemcachePool::Adapter.new(FakeCacheClass)
71
+ adapter.set('test-key', 'test-value', 10)
72
+
73
+ check_memcached('test-key', 'test-value', "#{FakeCacheClass.name}.1")
74
+ end
75
+
76
+ it "should get a value in memcached" do
77
+ memcached_connection(nil).set("#{FakeCacheClass.name}.1:#{'key1'.hexdigest}", 'value1', 86400, false)
78
+
79
+ adapter = Cachetastic::Adapters::MemcachePool::Adapter.new(FakeCacheClass)
80
+ adapter.get('key1').should == 'value1'
81
+ end
82
+
83
+ it "should delete a value in memcached" do
84
+ key = "#{FakeCacheClass.name}.1:#{'key1'.hexdigest}"
85
+ conn = memcached_connection(nil)
86
+ conn.set(key, 'value1', 86400, false)
87
+
88
+ adapter = Cachetastic::Adapters::MemcachePool::Adapter.new(FakeCacheClass)
89
+ adapter.delete('key1')
90
+ conn.get('key').should be_nil
91
+ end
92
+
93
+ it "should not see the old value in memcached after expire_all is called" do
94
+ memcached_connection(nil).set("#{FakeCacheClass.name}.1:#{'key1'.hexdigest}", 'value1', 86400, false)
95
+
96
+ adapter = Cachetastic::Adapters::MemcachePool::Adapter.new(FakeCacheClass)
97
+ adapter.get('key1').should == 'value1'
98
+ adapter.expire_all
99
+ adapter.get('key1').should be_nil
100
+ end
101
+
102
+ it "should create a namespace connection and a data connection" do
103
+ data_connection = expect_connection_creation(nil)
104
+ ns_connection = expect_connection_creation(:namespace_versions)
105
+
106
+ ns_connection.should_receive(:set).with(FakeCacheClass.name, 1)
107
+ data_connection.should_receive(:set).with("#{FakeCacheClass.name}.1:#{'key1'.hexdigest}", "value1", 86400, false)
108
+
109
+ cache = Cachetastic::Adapters::MemcachePool::Adapter.new(FakeCacheClass)
110
+ cache.set('key1', 'value1')
111
+ end
112
+
113
+ it "should create only one namespace and data connection for caches with the same configuration" do
114
+ data_connection = expect_connection_creation(nil)
115
+ ns_connection = expect_connection_creation(:namespace_versions)
116
+
117
+ ns_connection.should_receive(:set).with(FakeCacheClass.name, 1)
118
+ data_connection.should_receive(:set).with("#{FakeCacheClass.name}.1:#{'key1'.hexdigest}", "value1", 86400, false)
119
+ cache1 = Cachetastic::Adapters::MemcachePool::Adapter.new(FakeCacheClass)
120
+ cache1.set('key1', 'value1')
121
+
122
+ ns_connection.should_receive(:set).with(AnotherFakeCacheClass.name, 1)
123
+ data_connection.should_receive(:set).with("#{AnotherFakeCacheClass.name}.1:#{'key2'.hexdigest}", "value2", 86400, false)
124
+ cache2 = Cachetastic::Adapters::MemcachePool::Adapter.new(AnotherFakeCacheClass)
125
+ cache2.set('key2', 'value2')
126
+ end
127
+
128
+ it "should create a new namespace connection if the configuration is different" do
129
+ configatron.cachetastic.fake_cache_class.mc_options = {:c_threshold => 20_000}
130
+
131
+ data_connection1 = expect_connection_creation(nil)
132
+ data_connection1.should_receive(:set).with("#{FakeCacheClass.name}.1:#{'key1'.hexdigest}", "value1", 86400, false)
133
+ ns_connection1 = expect_connection_creation(:namespace_versions)
134
+ ns_connection1.should_receive(:set).with(FakeCacheClass.name, 1)
135
+
136
+ cache1 = Cachetastic::Adapters::MemcachePool::Adapter.new(FakeCacheClass)
137
+ cache1.set('key1', 'value1')
138
+
139
+
140
+ data_connection2 = expect_connection_creation(nil)
141
+ data_connection2.should_receive(:set).with("#{AnotherFakeCacheClass.name}.1:#{'key2'.hexdigest}", "value2", 86400, false)
142
+ ns_connection2 = expect_connection_creation(:namespace_versions)
143
+ ns_connection2.should_receive(:set).with(AnotherFakeCacheClass.name, 1)
144
+ cache2 = Cachetastic::Adapters::MemcachePool::Adapter.new(AnotherFakeCacheClass)
145
+ cache2.set('key2', 'value2')
146
+ end
147
+ end
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'rspec'
5
+ require 'cachetastic/adapters/memcache_pool'
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cachetastic-memcache-pool
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Jason Wadsworth
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-09-21 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 23
30
+ segments:
31
+ - 2
32
+ - 6
33
+ - 0
34
+ version: 2.6.0
35
+ type: :development
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: rcov
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 59
46
+ segments:
47
+ - 0
48
+ - 9
49
+ - 0
50
+ version: 0.9.0
51
+ type: :development
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: cachetastic
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 7
62
+ segments:
63
+ - 3
64
+ - 0
65
+ - 0
66
+ version: 3.0.0
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: memcache-client
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ hash: 61
78
+ segments:
79
+ - 1
80
+ - 8
81
+ - 5
82
+ version: 1.8.5
83
+ type: :runtime
84
+ version_requirements: *id004
85
+ description: Cachetastic Memcached Adapter with Connection Pooling
86
+ email:
87
+ - jason@gazelle.com
88
+ executables: []
89
+
90
+ extensions: []
91
+
92
+ extra_rdoc_files: []
93
+
94
+ files:
95
+ - .gitignore
96
+ - .rspec
97
+ - Gemfile
98
+ - README
99
+ - Rakefile
100
+ - cachetastic-memcache-pool.gemspec
101
+ - lib/cachetastic/adapters/memcache_pool.rb
102
+ - lib/cachetastic/adapters/memcache_pool/adapter.rb
103
+ - lib/cachetastic/adapters/memcache_pool/version.rb
104
+ - spec/cachetastic/adapters/memcache_pool/adapter_spec.rb
105
+ - spec/spec_helper.rb
106
+ has_rdoc: true
107
+ homepage: ""
108
+ licenses: []
109
+
110
+ post_install_message:
111
+ rdoc_options: []
112
+
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ hash: 3
121
+ segments:
122
+ - 0
123
+ version: "0"
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ hash: 3
130
+ segments:
131
+ - 0
132
+ version: "0"
133
+ requirements: []
134
+
135
+ rubyforge_project: cachetastic-memcache-pool
136
+ rubygems_version: 1.4.2
137
+ signing_key:
138
+ specification_version: 3
139
+ summary: Cachetastic Memcached Adapter with Connection Pooling
140
+ test_files:
141
+ - spec/cachetastic/adapters/memcache_pool/adapter_spec.rb
142
+ - spec/spec_helper.rb