libmemcached_store 0.2.3 → 0.6.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fd3263466b00f991543c49eae9ed95cf60808d48
4
+ data.tar.gz: 3f8780d95967e725cc6b85a2d11236616ee34baa
5
+ SHA512:
6
+ metadata.gz: 60147888a5c9aedc996e3597dc99deba43fac634c6e57febba5447208da63147fdd5387ede6cff4c3f6e6d5edccd25a137b6fe1840b6ca692e70c1cb68764a0a
7
+ data.tar.gz: 8be98b2735c5c6f9a0d004463622523defce2ca0e76263aae6f616fffc19dfedf7ffd3eedf026796bf46455b17843de910efd5ee4702d9b8c94d930dbffdd2bc
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .DS_Store
2
+ *.swp
3
+ Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ gemfile:
5
+ - gemfiles/rails30.gemfile
6
+ - gemfiles/rails31.gemfile
7
+ - gemfiles/rails32.gemfile
data/BENCHMARKS ADDED
@@ -0,0 +1,37 @@
1
+ Testing with
2
+ ruby 1.9.3p286 (2012-10-12 revision 37165) [x86_64-darwin12.2.0]
3
+ Dalli 2.6.3
4
+ Libmemcached_store 0.6.0
5
+ user system total real
6
+ write:short:dalli 0.380000 0.040000 0.420000 ( 0.422342)
7
+ write:short:libm 0.040000 0.020000 0.060000 ( 0.125838)
8
+ write:long:dalli 0.370000 0.040000 0.410000 ( 0.407400)
9
+ write:long:libm 0.050000 0.030000 0.080000 ( 0.130482)
10
+ write:raw:dalli 0.330000 0.030000 0.360000 ( 0.371981)
11
+ write:raw:libm 0.050000 0.030000 0.080000 ( 0.122899)
12
+
13
+ read:miss:dalli 0.310000 0.040000 0.350000 ( 0.352729)
14
+ read:miss:libm 0.080000 0.040000 0.120000 ( 0.182143)
15
+ read:miss2:dalli 0.350000 0.040000 0.390000 ( 0.394071)
16
+ read:miss2:libm 0.090000 0.050000 0.140000 ( 0.190611)
17
+ read:exist:dalli 0.350000 0.040000 0.390000 ( 0.391384)
18
+ read:exist:libm 0.060000 0.040000 0.100000 ( 0.161597)
19
+ read:expired:dalli 0.350000 0.040000 0.390000 ( 0.399342)
20
+ read:expired:libm 0.080000 0.050000 0.130000 ( 0.175674)
21
+ read:raw:dalli 0.360000 0.040000 0.400000 ( 0.400142)
22
+ read:raw:libm 0.050000 0.040000 0.090000 ( 0.160905)
23
+
24
+ exist:miss:dalli 0.310000 0.040000 0.350000 ( 0.342454)
25
+ exist:miss:libm 0.040000 0.030000 0.070000 ( 0.141901)
26
+ exist:hit:dalli 0.320000 0.040000 0.360000 ( 0.354205)
27
+ exist:hit:libm 0.030000 0.020000 0.050000 ( 0.107114)
28
+
29
+ delete:miss:dalli 0.310000 0.040000 0.350000 ( 0.342343)
30
+ delete:miss:libm 0.060000 0.030000 0.090000 ( 0.141960)
31
+ delete:hit:dalli 0.350000 0.040000 0.390000 ( 0.389187)
32
+ delete:hit:libm 0.050000 0.020000 0.070000 ( 0.130604)
33
+
34
+ increment:dalli 0.380000 0.050000 0.430000 ( 0.422802)
35
+ increment:libm 0.040000 0.020000 0.060000 ( 0.119200)
36
+ decrement:dalli 0.390000 0.040000 0.430000 ( 0.426890)
37
+ decrement:libm 0.030000 0.030000 0.060000 ( 0.118954)
data/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ ## 0.6.0
4
+ * New gem name _libmemcached_store_
5
+ * Handle Memcached::Error in read_multi (staugaard)
6
+
7
+ ## 0.5.1
8
+ * Remove warning from latest version of mocha
9
+ * Make #clear compatible with Rails.cache#clear (grosser)
10
+
11
+ ## 0.5.0
12
+ * Use Memcached#exist if available (performance improvement ~25%)
13
+ * Correctly escape bad characters and too long keys
14
+ * Add benchmarks
15
+ * Remove the use of ActiveSupport::Entry which was a performance bottleneck #3
16
+
17
+ ## 0.4.0
18
+ * Optimize read_multi to only make one call to memecached server
19
+ * Update test suite to reflect Rails' one
20
+ * Add session store tests
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'dalli'
7
+ end
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 37signals
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # LibmemcachedStore
2
+
3
+ An ActiveSupport cache store that uses the C-based libmemcached client through Evan Weaver's Ruby/SWIG wrapper, [memcached](https://github.com/evan/memcached). libmemcached is fast (fastest memcache client for Ruby), lightweight, and supports consistent hashing, non-blocking IO, and graceful server failover.
4
+
5
+ This cache is designed for Rails 3+ applications.
6
+
7
+ ## Prerequisites
8
+
9
+ You'll need the memcached gem installed:
10
+
11
+ ```ruby
12
+ gem install memcached
13
+ ```
14
+
15
+ or in your Gemfile
16
+
17
+ ```ruby
18
+ gem 'memcached'
19
+ ```
20
+
21
+ There are no other dependencies.
22
+
23
+ ## Installation
24
+
25
+ Just add to your Gemfile
26
+
27
+ ```ruby
28
+ gem 'libmemcached_store', '~> 0.6.0'
29
+ ```
30
+
31
+ and you're set.
32
+
33
+ ## Usage
34
+
35
+ This is a drop-in replacement for the memcache store that ships with Rails. To
36
+ enable, set the `config.cache_store` option to `libmemcached_store`
37
+ in the config for your environment
38
+
39
+ ```ruby
40
+ config.cache_store = :libmemcached_store
41
+ ```
42
+
43
+ If no servers are specified, localhost is assumed. You can specify a list of
44
+ server addresses, either as hostnames or IP addresses, with or without a port
45
+ designation. If no port is given, 11211 is assumed:
46
+
47
+ ```ruby
48
+ config.cache_store = :libmemcached_store, %w(cache-01 cache-02 127.0.0.1:11212)
49
+ ```
50
+
51
+ Standard Rails cache store options can be used
52
+
53
+ ```ruby
54
+ config.cache_store = :libmemcached_store, '127.0.0.1:11211', {:compress => true, :expires_in => 3600}
55
+ ```
56
+
57
+ More advanced options can be passed directly to the client
58
+
59
+ ```ruby
60
+ config.cache_store = :libmemcached_store, '127.0.0.1:11211', {:client => { :binary_protocol => true, :no_block => true }}
61
+ ```
62
+
63
+ You can also use `:libmemcached_store` to store your application sessions
64
+
65
+ ```ruby
66
+ require 'action_dispatch/session/libmemcached_store'
67
+ config.session_store = :libmemcached_store, :namespace => '_session', :expire_after => 1800
68
+ ```
69
+
70
+ ## Performance
71
+
72
+ Used with Rails, __libmemcached_store__ is at least 1.5x faster than __dalli__. See [BENCHMARKS](https://github.com/ccocchi/libmemcached_store/blob/master/BENCHMARKS)
73
+ for details
74
+
75
+ ## Props
76
+
77
+ Thanks to Brian Aker ([http://tangent.org](http://tangent.org)) for creating libmemcached, and Evan
78
+ Weaver ([http://blog.evanweaver.com](http://blog.evanweaver.com)) for the Ruby wrapper.
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+ require 'rdoc/task'
7
+
8
+ task :default => :test
9
+
10
+ Rake::TestTask.new do |t|
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.warning = false
14
+ t.verbose = true
15
+ end
16
+
17
+ desc 'Generate documentation for the libmemcached_store plugin.'
18
+ Rake::RDocTask.new(:rdoc) do |rdoc|
19
+ rdoc.rdoc_dir = 'rdoc'
20
+ rdoc.title = 'LibmemcachedStore'
21
+ rdoc.options << '--line-numbers' << '--inline-source'
22
+ rdoc.rdoc_files.include('README')
23
+ rdoc.rdoc_files.include('lib/**/*.rb')
24
+ end
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'activesupport', '~> 3.0.0'
4
+ gem 'actionpack', '~> 3.0.0'
5
+ gem 'mocha'
6
+
7
+ gemspec :path => '../'
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'activesupport', '~> 3.1.0'
4
+ gem 'actionpack', '~> 3.1.0'
5
+ gem 'mocha'
6
+
7
+ gemspec :path => '../'
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'activesupport', '~> 3.2.0'
4
+ gem 'actionpack', '~> 3.2.0'
5
+ gem 'mocha'
6
+
7
+ gemspec :path => '../'
@@ -0,0 +1,80 @@
1
+ require 'memcached'
2
+ require 'action_dispatch/middleware/session/abstract_store'
3
+
4
+ module ActionDispatch
5
+ module Session
6
+ class LibmemcachedStore < AbstractStore
7
+
8
+ def initialize(app, options = {})
9
+ options[:expire_after] ||= options[:expires]
10
+ super
11
+ client_options = { default_ttl: options.fetch(:expire_after, 0) }
12
+ client_options[:namespace] = options[:namespace] || 'rack:session'
13
+ @mutex = Mutex.new
14
+ @pool = options[:cache] || Memcached.new(@default_options[:memcache_server], client_options)
15
+ end
16
+
17
+ private
18
+
19
+ def generate_sid
20
+ loop do
21
+ sid = super
22
+ begin
23
+ @pool.exist(sid)
24
+ rescue Memcached::NotFound
25
+ break sid
26
+ end
27
+ end
28
+ end
29
+
30
+ def get_session(env, sid)
31
+ sid ||= generate_sid
32
+ session = with_lock(env, {}) do
33
+ begin
34
+ @pool.get(sid)
35
+ rescue Memcached::NotFound
36
+ {}
37
+ end
38
+ end
39
+ [sid, session]
40
+ end
41
+
42
+ def set_session(env, session_id, new_session, options = {})
43
+ expiry = options[:expire_after].to_i
44
+
45
+ with_lock(env, false) do
46
+ @pool.set(session_id, new_session, expiry)
47
+ session_id
48
+ end
49
+ end
50
+
51
+ def destroy_session(env, session_id, options = {})
52
+ with_lock(env, nil) do
53
+ @pool.delete(session_id)
54
+ generate_sid unless options[:drop]
55
+ end
56
+ end
57
+
58
+ #
59
+ # Deprecated since Rails 3.1.0
60
+ #
61
+ def destroy(env)
62
+ if sid = current_session_id(env)
63
+ with_lock(env, false) do
64
+ @pool.delete(sid)
65
+ end
66
+ end
67
+ end
68
+
69
+ def with_lock(env, default)
70
+ @mutex.lock if env['rack.multithread']
71
+ yield
72
+ rescue Memcached::Error => e
73
+ default
74
+ ensure
75
+ @mutex.unlock if @mutex.locked?
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -1,53 +1,138 @@
1
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
2
+ require 'memcached/get_with_flags'
3
+
4
+ require 'digest/md5'
14
5
 
15
6
  module ActiveSupport
16
7
  module Cache
17
- class LibmemcachedStore < Store
8
+
9
+ #
10
+ # Store using memcached gem as client
11
+ #
12
+ # Global options can be passed to be applied to each method by default.
13
+ # Supported options are
14
+ # * <tt>:compress</tt> : if set to true, data will be compress before stored
15
+ # * <tt>:compress_threshold</tt> : specify the threshold at which to compress
16
+ # value, default is 4K
17
+ # * <tt>:namespace</tt> : prepend each key with this value for simple namespacing
18
+ # * <tt>:expires_in</tt> : default TTL in seconds for each. Default value is 0, i.e. forever
19
+ # Specific value can be passed per key with write and fetch command.
20
+ #
21
+ # Options can also be passed direclty to the memcache client, via the <tt>:client</tt>
22
+ # option. For example, if you want to use pipelining, you can use
23
+ # :client => { :no_block => true }
24
+ #
25
+ class LibmemcachedStore
18
26
  attr_reader :addresses
19
27
 
20
- DEFAULT_OPTIONS = {
21
- :distribution => :consistent,
22
- :no_block => true,
23
- :failover => true
24
- }
28
+ DEFAULT_CLIENT_OPTIONS = { distribution: :consistent_ketama, binary_protocol: true, default_ttl: 0 }
29
+ ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
30
+ DEFAULT_COMPRESS_THRESHOLD = 4096
31
+ FLAG_COMPRESSED = 0x2
32
+
33
+ attr_reader :silence, :options
34
+ alias_method :silence?, :silence
35
+
36
+ # Silence the logger.
37
+ def silence!
38
+ @silence = true
39
+ self
40
+ end
41
+
42
+ # Silence the logger within a block.
43
+ def mute
44
+ previous_silence, @silence = defined?(@silence) && @silence, true
45
+ yield
46
+ ensure
47
+ @silence = previous_silence
48
+ end
25
49
 
26
50
  def initialize(*addresses)
27
51
  addresses.flatten!
28
52
  options = addresses.extract_options!
29
- addresses = %w(localhost) if addresses.empty?
53
+ client_options = options.delete(:client) || {}
54
+ if options[:namespace]
55
+ client_options[:prefix_key] = options.delete(:namespace)
56
+ client_options[:prefix_delimiter] = ':'
57
+ @namespace_length = client_options[:prefix_key].length + 1
58
+ else
59
+ @namespace_length = 0
60
+ end
61
+ client_options[:default_ttl] = options.delete(:expires_in).to_i if options[:expires_in]
30
62
 
63
+ @options = options.reverse_merge(compress_threshold: DEFAULT_COMPRESS_THRESHOLD)
31
64
  @addresses = addresses
32
- @cache = Memcached.new(@addresses, options.reverse_merge(DEFAULT_OPTIONS))
33
- extend ActiveSupport::Cache::Strategy::LocalCache
65
+ @cache = Memcached.new(@addresses, client_options.reverse_merge(DEFAULT_CLIENT_OPTIONS))
66
+ @cache.instance_eval { send(:extend, GetWithFlags) }
34
67
  end
35
68
 
36
- def valid_key(key)
37
- if key.is_a?(Array)
38
- key.map {|k| valid_key(k) }
39
- else
40
- if key && key.size > 250
41
- "#{Digest::SHA1.hexdigest(key)}-autofixed"
69
+ def fetch(key, options = nil)
70
+ if block_given?
71
+ key = expanded_key(key)
72
+ unless options && options[:force]
73
+ entry = instrument(:read, key, options) do |payload|
74
+ payload[:super_operation] = :fetch if payload
75
+ read_entry(key, options)
76
+ end
77
+ end
78
+
79
+ if entry.nil?
80
+ result = instrument(:generate, key, options) do |payload|
81
+ yield
82
+ end
83
+ write_entry(key, result, options)
84
+ result
42
85
  else
43
- key
86
+ instrument(:fetch_hit, key, options) { |payload| }
87
+ entry
44
88
  end
89
+ else
90
+ read(key, options)
45
91
  end
46
92
  end
47
93
 
48
94
  def read(key, options = nil)
49
- super
50
- @cache.get(valid_key(key), marshal?(options))
95
+ key = expanded_key(key)
96
+ instrument(:read, key, options) do |payload|
97
+ entry = read_entry(key, options)
98
+ payload[:hit] = !!entry if payload
99
+ entry
100
+ end
101
+ end
102
+
103
+ def write(key, value, options = nil)
104
+ key = expanded_key(key)
105
+ instrument(:write, key, options) do |payload|
106
+ write_entry(key, value, options)
107
+ end
108
+ end
109
+
110
+ def delete(key, options = nil)
111
+ key = expanded_key(key)
112
+ instrument(:delete, key) do |payload|
113
+ delete_entry(key, options)
114
+ end
115
+ end
116
+
117
+ def exist?(key, options = nil)
118
+ key = expanded_key(key)
119
+ instrument(:exist?, key) do |payload|
120
+ if @cache.respond_to?(:exist)
121
+ @cache.exist(escape_and_normalize(key))
122
+ true
123
+ else
124
+ read_entry(key, options) != nil
125
+ end
126
+ end
127
+ rescue Memcached::NotFound
128
+ false
129
+ end
130
+
131
+ def increment(key, amount = 1, options = nil)
132
+ key = expanded_key(key)
133
+ instrument(:increment, key, amount: amount) do
134
+ @cache.incr(escape_and_normalize(key), amount)
135
+ end
51
136
  rescue Memcached::NotFound
52
137
  nil
53
138
  rescue Memcached::Error => e
@@ -55,86 +140,147 @@ module ActiveSupport
55
140
  nil
56
141
  end
57
142
 
58
- def read_multi(*keys)
59
- read(keys) || {}
143
+ def decrement(key, amount = 1, options = nil)
144
+ key = expanded_key(key)
145
+ instrument(:decrement, key, amount: amount) do
146
+ @cache.decr(escape_and_normalize(key), amount)
147
+ end
148
+ rescue Memcached::NotFound
149
+ nil
150
+ rescue Memcached::Error => e
151
+ log_error(e)
152
+ nil
60
153
  end
61
154
 
62
- # Set the key to the given value. Pass :unless_exist => true if you want to
63
- # skip setting a key that already exists.
64
- def write(key, value, options = nil)
65
- super
66
- method = (options && options[:unless_exist]) ? :add : :set
67
- @cache.send(method, valid_key(key), value, expires_in(options), marshal?(options))
155
+ def read_multi(*names)
156
+ names.flatten!
157
+ options = names.extract_options!
158
+
159
+ return {} if names.empty?
160
+
161
+ mapping = Hash[names.map {|name| [escape_and_normalize(expanded_key(name)), name] }]
162
+ raw_values, flags = @cache.get(mapping.keys, false, true)
163
+
164
+ values = {}
165
+ raw_values.each do |key, value|
166
+ values[mapping[key]] = deserialize(value, options[:raw], flags[key])
167
+ end
168
+ values
169
+ rescue Memcached::Error => e
170
+ log_error(e)
171
+ {}
172
+ end
173
+
174
+ def clear(options = nil)
175
+ @cache.flush
176
+ end
177
+
178
+ def stats
179
+ @cache.stats
180
+ end
181
+
182
+ protected
183
+
184
+ def read_entry(key, options = nil)
185
+ options ||= {}
186
+ raw_value, flags = @cache.get(escape_and_normalize(key), false, true)
187
+ deserialize(raw_value, options[:raw], flags)
188
+ rescue Memcached::NotFound
189
+ nil
190
+ rescue Memcached::Error => e
191
+ log_error(e)
192
+ nil
193
+ end
194
+
195
+ def write_entry(key, entry, options = nil)
196
+ options = options ? @options.merge(options) : @options
197
+ method = options[:unless_exist] ? :add : :set
198
+ entry = options[:raw] ? entry.to_s : Marshal.dump(entry)
199
+ flags = 0
200
+
201
+ if options[:compress] && entry.bytesize >= options[:compress_threshold]
202
+ entry = Zlib::Deflate.deflate(entry)
203
+ flags |= FLAG_COMPRESSED
204
+ end
205
+
206
+ @cache.send(method, escape_and_normalize(key), entry, options[:expires_in].to_i, false, flags)
68
207
  true
69
208
  rescue Memcached::Error => e
70
209
  log_error(e)
71
210
  false
72
211
  end
73
212
 
74
- def delete(key, options = nil)
75
- super
76
- @cache.delete(valid_key(key))
213
+ def delete_entry(key, options = nil)
214
+ @cache.delete(escape_and_normalize(key))
77
215
  true
78
216
  rescue Memcached::NotFound
79
- nil
217
+ false
80
218
  rescue Memcached::Error => e
81
219
  log_error(e)
82
220
  false
83
221
  end
84
222
 
85
- def exist?(key, options = nil)
86
- !read(key, options).nil?
87
- end
223
+ private
88
224
 
89
- def increment(key, amount=1)
90
- log 'incrementing', key, amount
91
- @cache.incr(valid_key(key), amount)
92
- rescue Memcached::Error
93
- nil
225
+ def deserialize(value, raw = false, flags = 0)
226
+ value = Zlib::Inflate.inflate(value) if (flags & FLAG_COMPRESSED) != 0
227
+ raw ? value : Marshal.load(value)
228
+ rescue TypeError, ArgumentError
229
+ value
94
230
  end
95
231
 
96
- def decrement(key, amount=1)
97
- log 'decrementing', key, amount
98
- @cache.decr(valid_key(key), amount)
99
- rescue Memcached::Error
100
- nil
101
- end
232
+ def escape_and_normalize(key)
233
+ key = key.to_s.force_encoding("BINARY").gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
234
+ key_length = key.length
102
235
 
103
- def delete_matched(matcher, options = nil)
104
- super
105
- raise NotImplementedError
106
- end
236
+ return key if @namespace_length + key_length <= 250
107
237
 
108
- # Flushes all data in memory
109
- def clear
110
- @cache.flush
238
+ max_key_length = 213 - @namespace_length
239
+ "#{key[0, max_key_length]}:md5:#{Digest::MD5.hexdigest(key)}"
111
240
  end
112
241
 
113
- def stats
114
- @cache.stats
115
- end
242
+ def expanded_key(key) # :nodoc:
243
+ return key.cache_key.to_s if key.respond_to?(:cache_key)
244
+
245
+ case key
246
+ when Array
247
+ if key.size > 1
248
+ key = key.collect { |element| expanded_key(element) }
249
+ else
250
+ key = key.first
251
+ end
252
+ when Hash
253
+ key = key.sort_by { |k,_| k.to_s }.collect { |k, v| "#{k}=#{v}" }
254
+ end
116
255
 
117
- # Resets server connections, forcing a reconnect. This is required in
118
- # cases where processes fork, but continue sharing the same memcached
119
- # connection. You want to call this after the fork to make sure the
120
- # new process has its own connection.
121
- def reset
122
- @cache.reset
256
+ key.to_param
123
257
  end
124
258
 
125
- private
259
+ def instrument(operation, key, options=nil)
260
+ log(operation, key, options)
126
261
 
127
- def expires_in(options)
128
- (options || {})[:expires_in] || 0
262
+ if ActiveSupport::Cache::Store.instrument
263
+ payload = { :key => key }
264
+ payload.merge!(options) if options.is_a?(Hash)
265
+ ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
266
+ else
267
+ yield(nil)
129
268
  end
269
+ end
130
270
 
131
- def marshal?(options)
132
- !(options || {})[:raw]
133
- end
271
+ def log(operation, key, options=nil)
272
+ return unless !silence? && logger && logger.debug?
273
+ logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}")
274
+ end
134
275
 
135
- def log_error(exception)
136
- logger.error "MemcachedError (#{exception.inspect}): #{exception.message}" if logger && !@logger_off
137
- end
276
+ def log_error(exception)
277
+ return unless !silence? && logger && logger.error?
278
+ logger.error "MemcachedError (#{exception.inspect}): #{exception.message}"
279
+ end
280
+
281
+ def logger
282
+ Rails.logger
283
+ end
138
284
  end
139
285
  end
140
286
  end