rails2_libmemcached_store 0.3.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/README ADDED
@@ -0,0 +1,47 @@
1
+ = LibmemcachedStore
2
+
3
+ An ActiveSupport cache store that uses the C-based libmemcached client through
4
+ Evan Weaver's Ruby/SWIG wrapper, memcached. libmemcached is fast, lightweight,
5
+ and supports consistent hashing, non-blocking IO, and graceful server failover.
6
+
7
+ == Prerequisites
8
+
9
+ You'll need both the libmemcached client and the memcached gem:
10
+
11
+ * http://tangent.org/552/libmemcached.html
12
+ * http://blog.evanweaver.com/files/doc/fauna/memcached
13
+
14
+ Make sure you install libmemcached first, before you try installing the gem. If
15
+ you're using OS X, the easiest way to install libmemcached is through MacPorts:
16
+
17
+ sudo port install libmemcached
18
+
19
+ For other platforms, download and extract the libmemcached tarball and install
20
+ manually:
21
+
22
+ ./configure
23
+ make && sudo make install
24
+
25
+ Once libmemcached is installed, install the memcached gem:
26
+
27
+ gem install memcached --no-rdoc --no-ri
28
+
29
+ == Usage
30
+
31
+ This is a drop-in replacement for the memcache store that ships with Rails. To
32
+ enable, set the <tt>config.cache_store</tt> option to <tt>:libmemcached_store</tt>
33
+ in the config for your environment
34
+
35
+ config.cache_store = :libmemcached_store
36
+
37
+ If no servers are specified, localhost is assumed. You can specify a list of
38
+ server addresses, either as hostnames or IP addresses, with or without a port
39
+ designation. If no port is given, 11211 is assumed:
40
+
41
+ config.cache_store = :libmemcached_store, %w(cache-01 cache-02 127.0.0.1:11212)
42
+
43
+ == Props
44
+
45
+ Thanks to Brian Aker (http://tangent.org) for creating libmemcached, and Evan
46
+ Weaver (http://blog.evanweaver.com) for the Ruby wrapper.
47
+
@@ -0,0 +1,15 @@
1
+ module ActiveSupport
2
+ module Cache
3
+ class CompressedLibmemcachedStore < LibmemcachedStore
4
+ def read(name, options = {})
5
+ if value = super(name, (options || {}).merge(:raw => true))
6
+ Marshal.load(ActiveSupport::Gzip.decompress(value))
7
+ end
8
+ end
9
+
10
+ def write(name, value, options = {})
11
+ super(name, ActiveSupport::Gzip.compress(Marshal.dump(value)), (options || {}).merge(:raw => true))
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,182 @@
1
+ require 'memcached'
2
+ require 'digest/sha1'
3
+
4
+ class Memcached
5
+ # The latest version of memcached (0.11) doesn't support hostnames with dashes
6
+ # in their names, so we overwrite it here to be more lenient.
7
+ def set_servers(servers)
8
+ [*servers].each_with_index do |server, index|
9
+ host, port = server.split(":")
10
+ Lib.memcached_server_add(@struct, host, port.to_i)
11
+ end
12
+ end
13
+ end
14
+
15
+ module ActiveSupport
16
+ module Cache
17
+ class LibmemcachedStore < Store
18
+ attr_reader :addresses
19
+
20
+ ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
21
+
22
+ DEFAULT_OPTIONS = {
23
+ :distribution => :consistent,
24
+ :no_block => true,
25
+ :failover => true
26
+ }
27
+
28
+ def initialize(*addresses)
29
+ addresses.flatten!
30
+ options = addresses.extract_options!
31
+ addresses = %w(localhost) if addresses.empty?
32
+
33
+ if options[:prefix_key]
34
+ @namespace_length = options[:prefix_key].length
35
+ @namespace_length += options[:prefix_delimiter].length if options[:prefix_delimiter]
36
+ else
37
+ @namespace_length = 0
38
+ end
39
+
40
+ @addresses = addresses
41
+ @cache = Memcached.new(@addresses, options.reverse_merge(DEFAULT_OPTIONS))
42
+ extend ActiveSupport::Cache::Strategy::LocalCache
43
+ end
44
+
45
+ def read(key, options = nil)
46
+ key = expanded_key(key)
47
+ super
48
+ @cache.get(escape_and_normalize(key), marshal?(options))
49
+ rescue Memcached::NotFound
50
+ nil
51
+ rescue Memcached::Error => e
52
+ log_error(e)
53
+ nil
54
+ end
55
+
56
+ def read_multi(*names)
57
+ names.flatten!
58
+ options = names.extract_options!
59
+
60
+ return {} if names.empty?
61
+
62
+ mapping = Hash[names.map {|name| [escape_and_normalize(expanded_key(name)), name] }]
63
+ raw_values = @cache.get(mapping.keys, marshal?(options))
64
+
65
+ values = {}
66
+ raw_values.each do |key, value|
67
+ values[mapping[key]] = value
68
+ end
69
+ values
70
+ end
71
+
72
+ # Set the key to the given value. Pass :unless_exist => true if you want to
73
+ # skip setting a key that already exists.
74
+ def write(key, value, options = nil)
75
+ key = expanded_key(key)
76
+ super
77
+ method = (options && options[:unless_exist]) ? :add : :set
78
+ @cache.send(method, escape_and_normalize(key), value, expires_in(options), marshal?(options))
79
+ true
80
+ rescue Memcached::Error => e
81
+ log_error(e)
82
+ false
83
+ end
84
+
85
+ def delete(key, options = nil)
86
+ key = expanded_key(key)
87
+ super
88
+ @cache.delete(escape_and_normalize(key))
89
+ true
90
+ rescue Memcached::NotFound
91
+ nil
92
+ rescue Memcached::Error => e
93
+ log_error(e)
94
+ false
95
+ end
96
+
97
+ def exist?(key, options = nil)
98
+ key = expanded_key(key)
99
+ !read(key, options).nil?
100
+ end
101
+
102
+ def increment(key, amount=1)
103
+ key = expanded_key(key)
104
+ log 'incrementing', key, amount
105
+ @cache.incr(escape_and_normalize(key), amount)
106
+ rescue Memcached::Error
107
+ nil
108
+ end
109
+
110
+ def decrement(key, amount=1)
111
+ key = expanded_key(key)
112
+ log 'decrementing', key, amount
113
+ @cache.decr(escape_and_normalize(key), amount)
114
+ rescue Memcached::Error
115
+ nil
116
+ end
117
+
118
+ def delete_matched(matcher, options = nil)
119
+ super
120
+ raise NotImplementedError
121
+ end
122
+
123
+ # Flushes all data in memory
124
+ def clear
125
+ @cache.flush
126
+ end
127
+
128
+ def stats
129
+ @cache.stats
130
+ end
131
+
132
+ # Resets server connections, forcing a reconnect. This is required in
133
+ # cases where processes fork, but continue sharing the same memcached
134
+ # connection. You want to call this after the fork to make sure the
135
+ # new process has its own connection.
136
+ def reset
137
+ @cache.reset
138
+ end
139
+
140
+ private
141
+
142
+ def escape_and_normalize(key)
143
+ key = key.to_s.dup.force_encoding("BINARY").gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
144
+ key_length = key.length
145
+
146
+ return key if @namespace_length + key_length <= 250
147
+
148
+ max_key_length = 213 - @namespace_length
149
+ "#{key[0, max_key_length]}:md5:#{Digest::MD5.hexdigest(key)}"
150
+ end
151
+
152
+ def expanded_key(key) # :nodoc:
153
+ return key.cache_key.to_s if key.respond_to?(:cache_key)
154
+
155
+ case key
156
+ when Array
157
+ if key.size > 1
158
+ key = key.collect { |element| expanded_key(element) }
159
+ else
160
+ key = key.first
161
+ end
162
+ when Hash
163
+ key = key.sort_by { |k,_| k.to_s }.collect { |k, v| "#{k}=#{v}" }
164
+ end
165
+
166
+ key.to_param
167
+ end
168
+
169
+ def expires_in(options)
170
+ (options || {})[:expires_in] || 0
171
+ end
172
+
173
+ def marshal?(options)
174
+ !(options || {})[:raw]
175
+ end
176
+
177
+ def log_error(exception)
178
+ logger.error "MemcachedError (#{exception.inspect}): #{exception.message}" if logger && !@logger_off
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,2 @@
1
+ require 'active_support/cache/libmemcached_store'
2
+ require 'active_support/cache/compressed_libmemcached_store'
@@ -0,0 +1,84 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'active_support'
4
+ require 'memcached'
5
+
6
+ require File.dirname(__FILE__) + '/../lib/libmemcached_store'
7
+
8
+ # Make it easier to get at the underlying cache options during testing.
9
+ class ActiveSupport::Cache::LibmemcachedStore
10
+ delegate :options, :to => '@cache'
11
+ end
12
+
13
+ class LibmemcachedStoreTest < Test::Unit::TestCase
14
+ def setup
15
+ @store = ActiveSupport::Cache.lookup_store :libmemcached_store
16
+ @store.clear
17
+ end
18
+
19
+ def test_should_identify_cache_store
20
+ assert_kind_of ActiveSupport::Cache::LibmemcachedStore, @store
21
+ end
22
+
23
+ def test_should_set_server_addresses_to_localhost_if_none_are_given
24
+ assert_equal %w(localhost), @store.addresses
25
+ end
26
+
27
+ def test_should_set_custom_server_addresses
28
+ store = ActiveSupport::Cache.lookup_store :libmemcached_store, 'localhost', '192.168.1.1'
29
+ assert_equal %w(localhost 192.168.1.1), store.addresses
30
+ end
31
+
32
+ def test_should_enable_consistent_hashing_by_default
33
+ assert_equal :consistent, @store.options[:distribution]
34
+ end
35
+
36
+ def test_should_enable_non_blocking_io_by_default
37
+ assert_equal true, @store.options[:no_block]
38
+ end
39
+
40
+ def test_should_enable_server_failover_by_default
41
+ assert_equal true, @store.options[:auto_eject_hosts]
42
+ end
43
+
44
+ def test_should_allow_configuration_of_custom_options
45
+ options = {
46
+ :prefix_key => 'test',
47
+ :distribution => :modula,
48
+ :no_block => false,
49
+ :auto_eject_hosts => false
50
+ }
51
+
52
+ store = ActiveSupport::Cache.lookup_store :libmemcached_store, 'localhost', options
53
+
54
+ assert_equal 'test', store.instance_variable_get(:@cache).prefix_key
55
+ assert_equal :modula, store.options[:distribution]
56
+ assert_equal false, store.options[:no_block]
57
+ assert_equal false, store.options[:auto_eject_hosts]
58
+ end
59
+
60
+ def test_should_use_local_cache
61
+ @store.with_local_cache do
62
+ @store.write('key', 'value')
63
+ assert_equal 'value', @store.send(:local_cache).read('key')
64
+ end
65
+
66
+ assert_equal 'value', @store.read('key')
67
+ end
68
+
69
+ def test_should_read_multiple_keys
70
+ @store.write('a', 1)
71
+ @store.write('b', 2)
72
+
73
+ assert_equal({ 'a' => 1, 'b' => 2 }, @store.read_multi('a', 'b', 'c'))
74
+ assert_equal({}, @store.read_multi())
75
+ end
76
+
77
+ def test_should_fix_long_keys
78
+ key = ("0123456789" * 100).freeze
79
+ assert key.size > 250
80
+ @store.write(key, 1)
81
+ assert_equal 1, @store.read(key)
82
+ end
83
+
84
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails2_libmemcached_store
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jeffrey Hardy
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: memcached
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: activesupport
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - <
52
+ - !ruby/object:Gem::Version
53
+ version: '3'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - <
60
+ - !ruby/object:Gem::Version
61
+ version: '3'
62
+ description: ! "An ActiveSupport cache store that uses the C-based libmemcached client
63
+ through\n Evan Weaver's Ruby/SWIG wrapper, memcached. libmemcached is fast,
64
+ lightweight,\n and supports consistent hashing, non-blocking IO, and graceful
65
+ server failover."
66
+ email:
67
+ - packagethief@gmail.com
68
+ executables: []
69
+ extensions: []
70
+ extra_rdoc_files: []
71
+ files:
72
+ - lib/active_support/cache/compressed_libmemcached_store.rb
73
+ - lib/active_support/cache/libmemcached_store.rb
74
+ - lib/libmemcached_store.rb
75
+ - README
76
+ - test/libmemcached_store_test.rb
77
+ homepage: http://github.com/37signals/libmemcached_store
78
+ licenses: []
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ segments:
90
+ - 0
91
+ hash: 2896348711499300858
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ segments:
99
+ - 0
100
+ hash: 2896348711499300858
101
+ requirements: []
102
+ rubyforge_project:
103
+ rubygems_version: 1.8.25
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: ActiveSupport::Cache wrapper for libmemcached
107
+ test_files:
108
+ - test/libmemcached_store_test.rb