dalli 2.7.10 → 3.2.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of dalli might be problematic. Click here for more details.

Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -1
  3. data/History.md +148 -2
  4. data/README.md +26 -223
  5. data/lib/dalli/cas/client.rb +1 -57
  6. data/lib/dalli/client.rb +226 -258
  7. data/lib/dalli/compressor.rb +12 -2
  8. data/lib/dalli/key_manager.rb +113 -0
  9. data/lib/dalli/options.rb +6 -7
  10. data/lib/dalli/pipelined_getter.rb +177 -0
  11. data/lib/dalli/protocol/base.rb +241 -0
  12. data/lib/dalli/protocol/binary/request_formatter.rb +117 -0
  13. data/lib/dalli/protocol/binary/response_header.rb +36 -0
  14. data/lib/dalli/protocol/binary/response_processor.rb +239 -0
  15. data/lib/dalli/protocol/binary/sasl_authentication.rb +60 -0
  16. data/lib/dalli/protocol/binary.rb +173 -0
  17. data/lib/dalli/protocol/connection_manager.rb +252 -0
  18. data/lib/dalli/protocol/meta/key_regularizer.rb +31 -0
  19. data/lib/dalli/protocol/meta/request_formatter.rb +108 -0
  20. data/lib/dalli/protocol/meta/response_processor.rb +211 -0
  21. data/lib/dalli/protocol/meta.rb +177 -0
  22. data/lib/dalli/protocol/response_buffer.rb +54 -0
  23. data/lib/dalli/protocol/server_config_parser.rb +84 -0
  24. data/lib/dalli/protocol/ttl_sanitizer.rb +45 -0
  25. data/lib/dalli/protocol/value_compressor.rb +85 -0
  26. data/lib/dalli/protocol/value_marshaller.rb +59 -0
  27. data/lib/dalli/protocol/value_serializer.rb +91 -0
  28. data/lib/dalli/protocol.rb +8 -0
  29. data/lib/dalli/ring.rb +94 -83
  30. data/lib/dalli/server.rb +3 -746
  31. data/lib/dalli/servers_arg_normalizer.rb +54 -0
  32. data/lib/dalli/socket.rb +117 -137
  33. data/lib/dalli/version.rb +4 -1
  34. data/lib/dalli.rb +43 -15
  35. data/lib/rack/session/dalli.rb +103 -94
  36. metadata +64 -26
  37. data/lib/action_dispatch/middleware/session/dalli_store.rb +0 -82
  38. data/lib/active_support/cache/dalli_store.rb +0 -435
  39. data/lib/dalli/railtie.rb +0 -8
data/lib/dalli/client.rb CHANGED
@@ -1,17 +1,24 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'digest/md5'
3
4
  require 'set'
4
5
 
5
6
  # encoding: ascii
6
7
  module Dalli
8
+ ##
9
+ # Dalli::Client is the main class which developers will use to interact with
10
+ # Memcached.
11
+ ##
7
12
  class Client
8
-
9
13
  ##
10
14
  # Dalli::Client is the main class which developers will use to interact with
11
15
  # the memcached server. Usage:
12
16
  #
13
- # Dalli::Client.new(['localhost:11211:10', 'cache-2.example.com:11211:5', '192.168.0.1:22122:5', '/var/run/memcached/socket'],
14
- # :threadsafe => true, :failover => true, :expires_in => 300)
17
+ # Dalli::Client.new(['localhost:11211:10',
18
+ # 'cache-2.example.com:11211:5',
19
+ # '192.168.0.1:22122:5',
20
+ # '/var/run/memcached/socket'],
21
+ # failover: true, expires_in: 300)
15
22
  #
16
23
  # servers is an Array of "host:port:weight" where weight allows you to distribute cache unevenly.
17
24
  # Both weight and port are optional. If you pass in nil, Dalli will use the <tt>MEMCACHE_SERVERS</tt>
@@ -24,15 +31,25 @@ module Dalli
24
31
  # - :namespace - prepend each key with this value to provide simple namespacing.
25
32
  # - :failover - if a server is down, look for and store values on another server in the ring. Default: true.
26
33
  # - :threadsafe - ensure that only one thread is actively using a socket at a time. Default: true.
27
- # - :expires_in - default TTL in seconds if you do not pass TTL as a parameter to an individual operation, defaults to 0 or forever
28
- # - :compress - defaults to false, if true Dalli will compress values larger than 1024 bytes before sending them to memcached.
34
+ # - :expires_in - default TTL in seconds if you do not pass TTL as a parameter to an individual operation, defaults
35
+ # to 0 or forever.
36
+ # - :compress - if true Dalli will compress values larger than compression_min_size bytes before sending them
37
+ # to memcached. Default: true.
38
+ # - :compression_min_size - the minimum size (in bytes) for which Dalli will compress values sent to Memcached.
39
+ # Defaults to 4K.
29
40
  # - :serializer - defaults to Marshal
30
- # - :compressor - defaults to zlib
31
- # - :cache_nils - defaults to false, if true Dalli will not treat cached nil values as 'not found' for #fetch operations.
41
+ # - :compressor - defaults to Dalli::Compressor, a Zlib-based implementation
42
+ # - :cache_nils - defaults to false, if true Dalli will not treat cached nil values as 'not found' for
43
+ # #fetch operations.
44
+ # - :digest_class - defaults to Digest::MD5, allows you to pass in an object that responds to the hexdigest method,
45
+ # useful for injecting a FIPS compliant hash object.
46
+ # - :protocol - one of either :binary or :meta, defaulting to :binary. This sets the protocol that Dalli uses
47
+ # to communicate with memcached.
32
48
  #
33
- def initialize(servers=nil, options={})
34
- @servers = normalize_servers(servers || ENV["MEMCACHE_SERVERS"] || '127.0.0.1:11211')
49
+ def initialize(servers = nil, options = {})
50
+ @normalized_servers = ::Dalli::ServersArgNormalizer.normalize_servers(servers)
35
51
  @options = normalize_options(options)
52
+ @key_manager = ::Dalli::KeyManager.new(@options)
36
53
  @ring = nil
37
54
  end
38
55
 
@@ -41,22 +58,37 @@ module Dalli
41
58
  #
42
59
 
43
60
  ##
44
- # Turn on quiet aka noreply support.
45
- # All relevant operations within this block will be effectively
46
- # pipelined as Dalli will use 'quiet' operations where possible.
47
- # Currently supports the set, add, replace and delete operations.
48
- def multi
49
- old, Thread.current[:dalli_multi] = Thread.current[:dalli_multi], true
50
- yield
51
- ensure
52
- Thread.current[:dalli_multi] = old
61
+ # Get the value associated with the key.
62
+ # If a value is not found, then +nil+ is returned.
63
+ def get(key, req_options = nil)
64
+ perform(:get, key, req_options)
53
65
  end
54
66
 
55
67
  ##
56
- # Get the value associated with the key.
68
+ # Gat (get and touch) fetch an item and simultaneously update its expiration time.
69
+ #
57
70
  # If a value is not found, then +nil+ is returned.
58
- def get(key, options=nil)
59
- perform(:get, key, options)
71
+ def gat(key, ttl = nil)
72
+ perform(:gat, key, ttl_or_default(ttl))
73
+ end
74
+
75
+ ##
76
+ # Touch updates expiration time for a given key.
77
+ #
78
+ # Returns true if key exists, otherwise nil.
79
+ def touch(key, ttl = nil)
80
+ resp = perform(:touch, key, ttl_or_default(ttl))
81
+ resp.nil? ? nil : true
82
+ end
83
+
84
+ ##
85
+ # Get the value and CAS ID associated with the key. If a block is provided,
86
+ # value and CAS will be passed to the block.
87
+ def get_cas(key)
88
+ (value, cas) = perform(:cas, key)
89
+ return [value, cas] unless block_given?
90
+
91
+ yield value, cas
60
92
  end
61
93
 
62
94
  ##
@@ -64,20 +96,35 @@ module Dalli
64
96
  # If a block is given, yields key/value pairs one at a time.
65
97
  # Otherwise returns a hash of { 'key' => 'value', 'key2' => 'value1' }
66
98
  def get_multi(*keys)
67
- check_keys = keys.flatten
68
- check_keys.compact!
99
+ keys.flatten!
100
+ keys.compact!
101
+
102
+ return {} if keys.empty?
69
103
 
70
- return {} if check_keys.empty?
71
104
  if block_given?
72
- get_multi_yielder(keys) {|k, data| yield k, data.first}
105
+ pipelined_getter.process(keys) { |k, data| yield k, data.first }
73
106
  else
74
- Hash.new.tap do |hash|
75
- get_multi_yielder(keys) {|k, data| hash[k] = data.first}
107
+ {}.tap do |hash|
108
+ pipelined_getter.process(keys) { |k, data| hash[k] = data.first }
76
109
  end
77
110
  end
78
111
  end
79
112
 
80
- CACHE_NILS = {cache_nils: true}.freeze
113
+ ##
114
+ # Fetch multiple keys efficiently, including available metadata such as CAS.
115
+ # If a block is given, yields key/data pairs one a time. Data is an array:
116
+ # [value, cas_id]
117
+ # If no block is given, returns a hash of
118
+ # { 'key' => [value, cas_id] }
119
+ def get_multi_cas(*keys)
120
+ if block_given?
121
+ pipelined_getter.process(keys) { |*args| yield(*args) }
122
+ else
123
+ {}.tap do |hash|
124
+ pipelined_getter.process(keys) { |k, data| hash[k] = data }
125
+ end
126
+ end
127
+ end
81
128
 
82
129
  # Fetch the value associated with the key.
83
130
  # If a value is found, then it is returned.
@@ -87,17 +134,14 @@ module Dalli
87
134
  # If a value is not found (or if the found value is nil and :cache_nils is false)
88
135
  # and a block is given, the block will be invoked and its return value
89
136
  # written to the cache and returned.
90
- def fetch(key, ttl=nil, options=nil)
91
- options = options.nil? ? CACHE_NILS : options.merge(CACHE_NILS) if @options[:cache_nils]
92
- val = get(key, options)
93
- not_found = @options[:cache_nils] ?
94
- val == Dalli::Server::NOT_FOUND :
95
- val.nil?
96
- if not_found && block_given?
97
- val = yield
98
- add(key, val, ttl_or_default(ttl), options)
99
- end
100
- val
137
+ def fetch(key, ttl = nil, req_options = nil)
138
+ req_options = req_options.nil? ? CACHE_NILS : req_options.merge(CACHE_NILS) if cache_nils
139
+ val = get(key, req_options)
140
+ return val unless block_given? && not_found?(val)
141
+
142
+ new_val = yield
143
+ add(key, new_val, ttl_or_default(ttl), req_options)
144
+ new_val
101
145
  end
102
146
 
103
147
  ##
@@ -111,8 +155,8 @@ module Dalli
111
155
  # - nil if the key did not exist.
112
156
  # - false if the value was changed by someone else.
113
157
  # - true if the value was successfully updated.
114
- def cas(key, ttl=nil, options=nil, &block)
115
- cas_core(key, false, ttl, options, &block)
158
+ def cas(key, ttl = nil, req_options = nil, &block)
159
+ cas_core(key, false, ttl, req_options, &block)
116
160
  end
117
161
 
118
162
  ##
@@ -122,30 +166,78 @@ module Dalli
122
166
  # Returns:
123
167
  # - false if the value was changed by someone else.
124
168
  # - true if the value was successfully updated.
125
- def cas!(key, ttl=nil, options=nil, &block)
126
- cas_core(key, true, ttl, options, &block)
169
+ def cas!(key, ttl = nil, req_options = nil, &block)
170
+ cas_core(key, true, ttl, req_options, &block)
127
171
  end
128
172
 
129
- def set(key, value, ttl=nil, options=nil)
130
- perform(:set, key, value, ttl_or_default(ttl), 0, options)
173
+ ##
174
+ # Turn on quiet aka noreply support for a number of
175
+ # memcached operations.
176
+ #
177
+ # All relevant operations within this block will be effectively
178
+ # pipelined as Dalli will use 'quiet' versions. The invoked methods
179
+ # will all return nil, rather than their usual response. Method
180
+ # latency will be substantially lower, as the caller will not be
181
+ # blocking on responses.
182
+ #
183
+ # Currently supports storage (set, add, replace, append, prepend),
184
+ # arithmetic (incr, decr), flush and delete operations. Use of
185
+ # unsupported operations inside a block will raise an error.
186
+ #
187
+ # Any error replies will be discarded at the end of the block, and
188
+ # Dalli client methods invoked inside the block will not
189
+ # have return values
190
+ def quiet
191
+ old = Thread.current[::Dalli::QUIET]
192
+ Thread.current[::Dalli::QUIET] = true
193
+ yield
194
+ ensure
195
+ @ring&.pipeline_consume_and_ignore_responses
196
+ Thread.current[::Dalli::QUIET] = old
197
+ end
198
+ alias multi quiet
199
+
200
+ def set(key, value, ttl = nil, req_options = nil)
201
+ set_cas(key, value, 0, ttl, req_options)
202
+ end
203
+
204
+ ##
205
+ # Set the key-value pair, verifying existing CAS.
206
+ # Returns the resulting CAS value if succeeded, and falsy otherwise.
207
+ def set_cas(key, value, cas, ttl = nil, req_options = nil)
208
+ perform(:set, key, value, ttl_or_default(ttl), cas, req_options)
131
209
  end
132
210
 
133
211
  ##
134
212
  # Conditionally add a key/value pair, if the key does not already exist
135
213
  # on the server. Returns truthy if the operation succeeded.
136
- def add(key, value, ttl=nil, options=nil)
137
- perform(:add, key, value, ttl_or_default(ttl), options)
214
+ def add(key, value, ttl = nil, req_options = nil)
215
+ perform(:add, key, value, ttl_or_default(ttl), req_options)
138
216
  end
139
217
 
140
218
  ##
141
219
  # Conditionally add a key/value pair, only if the key already exists
142
220
  # on the server. Returns truthy if the operation succeeded.
143
- def replace(key, value, ttl=nil, options=nil)
144
- perform(:replace, key, value, ttl_or_default(ttl), 0, options)
221
+ def replace(key, value, ttl = nil, req_options = nil)
222
+ replace_cas(key, value, 0, ttl, req_options)
223
+ end
224
+
225
+ ##
226
+ # Conditionally add a key/value pair, verifying existing CAS, only if the
227
+ # key already exists on the server. Returns the new CAS value if the
228
+ # operation succeeded, or falsy otherwise.
229
+ def replace_cas(key, value, cas, ttl = nil, req_options = nil)
230
+ perform(:replace, key, value, ttl_or_default(ttl), cas, req_options)
231
+ end
232
+
233
+ # Delete a key/value pair, verifying existing CAS.
234
+ # Returns true if succeeded, and falsy otherwise.
235
+ def delete_cas(key, cas = 0)
236
+ perform(:delete, key, cas)
145
237
  end
146
238
 
147
239
  def delete(key)
148
- perform(:delete, key, 0)
240
+ delete_cas(key, 0)
149
241
  end
150
242
 
151
243
  ##
@@ -162,13 +254,6 @@ module Dalli
162
254
  perform(:prepend, key, value.to_s)
163
255
  end
164
256
 
165
- def flush(delay=0)
166
- time = -delay
167
- ring.servers.map { |s| s.request(:flush, time += delay) }
168
- end
169
-
170
- alias_method :flush_all, :flush
171
-
172
257
  ##
173
258
  # Incr adds the given amount to the counter on the memcached server.
174
259
  # Amt must be a positive integer value.
@@ -180,8 +265,11 @@ module Dalli
180
265
  # Note that the ttl will only apply if the counter does not already
181
266
  # exist. To increase an existing counter and update its TTL, use
182
267
  # #cas.
183
- def incr(key, amt=1, ttl=nil, default=nil)
184
- raise ArgumentError, "Positive values only: #{amt}" if amt < 0
268
+ #
269
+ # If the value already exists, it must have been set with raw: true
270
+ def incr(key, amt = 1, ttl = nil, default = nil)
271
+ check_positive!(amt)
272
+
185
273
  perform(:incr, key, amt.to_i, ttl_or_default(ttl), default)
186
274
  end
187
275
 
@@ -199,29 +287,34 @@ module Dalli
199
287
  # Note that the ttl will only apply if the counter does not already
200
288
  # exist. To decrease an existing counter and update its TTL, use
201
289
  # #cas.
202
- def decr(key, amt=1, ttl=nil, default=nil)
203
- raise ArgumentError, "Positive values only: #{amt}" if amt < 0
290
+ #
291
+ # If the value already exists, it must have been set with raw: true
292
+ def decr(key, amt = 1, ttl = nil, default = nil)
293
+ check_positive!(amt)
294
+
204
295
  perform(:decr, key, amt.to_i, ttl_or_default(ttl), default)
205
296
  end
206
297
 
207
298
  ##
208
- # Touch updates expiration time for a given key.
209
- #
210
- # Returns true if key exists, otherwise nil.
211
- def touch(key, ttl=nil)
212
- resp = perform(:touch, key, ttl_or_default(ttl))
213
- resp.nil? ? nil : true
299
+ # Flush the memcached server, at 'delay' seconds in the future.
300
+ # Delay defaults to zero seconds, which means an immediate flush.
301
+ ##
302
+ def flush(delay = 0)
303
+ ring.servers.map { |s| s.request(:flush, delay) }
214
304
  end
305
+ alias flush_all flush
306
+
307
+ ALLOWED_STAT_KEYS = %i[items slabs settings].freeze
215
308
 
216
309
  ##
217
310
  # Collect the stats for each server.
218
311
  # You can optionally pass a type including :items, :slabs or :settings to get specific stats
219
312
  # Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
220
- def stats(type=nil)
221
- type = nil if ![nil, :items,:slabs,:settings].include? type
313
+ def stats(type = nil)
314
+ type = nil unless ALLOWED_STAT_KEYS.include? type
222
315
  values = {}
223
316
  ring.servers.each do |server|
224
- values["#{server.name}"] = server.alive? ? server.request(:stats,type.to_s) : nil
317
+ values[server.name.to_s] = server.alive? ? server.request(:stats, type.to_s) : nil
225
318
  end
226
319
  values
227
320
  end
@@ -234,32 +327,40 @@ module Dalli
234
327
  end
235
328
  end
236
329
 
237
- ##
238
- ## Make sure memcache servers are alive, or raise an Dalli::RingError
239
- def alive!
240
- ring.server_for_key("")
241
- end
242
-
243
330
  ##
244
331
  ## Version of the memcache servers.
245
332
  def version
246
333
  values = {}
247
334
  ring.servers.each do |server|
248
- values["#{server.name}"] = server.alive? ? server.request(:version) : nil
335
+ values[server.name.to_s] = server.alive? ? server.request(:version) : nil
249
336
  end
250
337
  values
251
338
  end
252
339
 
340
+ ##
341
+ ## Make sure memcache servers are alive, or raise an Dalli::RingError
342
+ def alive!
343
+ ring.server_for_key('')
344
+ end
345
+
253
346
  ##
254
347
  # Close our connection to each server.
255
348
  # If you perform another operation after this, the connections will be re-established.
256
349
  def close
257
- if @ring
258
- @ring.servers.each { |s| s.close }
259
- @ring = nil
260
- end
350
+ @ring&.close
351
+ @ring = nil
352
+ end
353
+ alias reset close
354
+
355
+ CACHE_NILS = { cache_nils: true }.freeze
356
+
357
+ def not_found?(val)
358
+ cache_nils ? val == ::Dalli::NOT_FOUND : val.nil?
359
+ end
360
+
361
+ def cache_nils
362
+ @options[:cache_nils]
261
363
  end
262
- alias_method :reset, :close
263
364
 
264
365
  # Stub method so a bare Dalli client can pretend to be a connection pool.
265
366
  def with
@@ -268,209 +369,76 @@ module Dalli
268
369
 
269
370
  private
270
371
 
271
- def cas_core(key, always_set, ttl=nil, options=nil)
372
+ def check_positive!(amt)
373
+ raise ArgumentError, "Positive values only: #{amt}" if amt.negative?
374
+ end
375
+
376
+ def cas_core(key, always_set, ttl = nil, req_options = nil)
272
377
  (value, cas) = perform(:cas, key)
273
- value = (!value || value == 'Not found') ? nil : value
274
378
  return if value.nil? && !always_set
379
+
275
380
  newvalue = yield(value)
276
- perform(:set, key, newvalue, ttl_or_default(ttl), cas, options)
381
+ perform(:set, key, newvalue, ttl_or_default(ttl), cas, req_options)
277
382
  end
278
383
 
384
+ ##
385
+ # Uses the argument TTL or the client-wide default. Ensures
386
+ # that the value is an integer
387
+ ##
279
388
  def ttl_or_default(ttl)
280
389
  (ttl || @options[:expires_in]).to_i
281
390
  rescue NoMethodError
282
391
  raise ArgumentError, "Cannot convert ttl (#{ttl}) to an integer"
283
392
  end
284
393
 
285
- def groups_for_keys(*keys)
286
- groups = mapped_keys(keys).flatten.group_by do |key|
287
- begin
288
- ring.server_for_key(key)
289
- rescue Dalli::RingError
290
- Dalli.logger.debug { "unable to get key #{key}" }
291
- nil
292
- end
293
- end
294
- return groups
295
- end
296
-
297
- def mapped_keys(keys)
298
- keys_array = keys.flatten
299
- keys_array.map! { |a| validate_key(a.to_s) }
300
- keys_array
301
- end
302
-
303
- def make_multi_get_requests(groups)
304
- groups.each do |server, keys_for_server|
305
- begin
306
- # TODO: do this with the perform chokepoint?
307
- # But given the fact that fetching the response doesn't take place
308
- # in that slot it's misleading anyway. Need to move all of this method
309
- # into perform to be meaningful
310
- server.request(:send_multiget, keys_for_server)
311
- rescue DalliError, NetworkError => e
312
- Dalli.logger.debug { e.inspect }
313
- Dalli.logger.debug { "unable to get keys for server #{server.name}" }
314
- end
315
- end
394
+ def ring
395
+ @ring ||= Dalli::Ring.new(@normalized_servers, protocol_implementation, @options)
316
396
  end
317
397
 
318
- def perform_multi_response_start(servers)
319
- servers.each do |server|
320
- next unless server.alive?
321
- begin
322
- server.multi_response_start
323
- rescue DalliError, NetworkError => e
324
- Dalli.logger.debug { e.inspect }
325
- Dalli.logger.debug { "results from this server will be missing" }
326
- servers.delete(server)
327
- end
328
- end
329
- servers
398
+ def protocol_implementation
399
+ @protocol_implementation ||= case @options[:protocol]&.to_s
400
+ when 'meta'
401
+ Dalli::Protocol::Meta
402
+ else
403
+ Dalli::Protocol::Binary
404
+ end
330
405
  end
331
406
 
332
407
  ##
333
- # Normalizes the argument into an array of servers.
334
- # If the argument is a string, it's expected that the URIs are comma separated e.g.
335
- # "memcache1.example.com:11211,memcache2.example.com:11211,memcache3.example.com:11211"
336
- def normalize_servers(servers)
337
- if servers.is_a? String
338
- return servers.split(",")
339
- else
340
- return servers
341
- end
342
- end
343
-
344
- def ring
345
- @ring ||= Dalli::Ring.new(
346
- @servers.map do |s|
347
- server_options = {}
348
- if s =~ %r{\Amemcached://}
349
- uri = URI.parse(s)
350
- server_options[:username] = uri.user
351
- server_options[:password] = uri.password
352
- s = "#{uri.host}:#{uri.port}"
353
- end
354
- Dalli::Server.new(s, @options.merge(server_options))
355
- end, @options
356
- )
357
- end
358
-
359
- # Chokepoint method for instrumentation
408
+ # Chokepoint method for memcached methods with a key argument.
409
+ # Validates the key, resolves the key to the appropriate server
410
+ # instance, and invokes the memcached method on the appropriate
411
+ # server.
412
+ #
413
+ # This method also forces retries on network errors - when
414
+ # a particular memcached instance becomes unreachable, or the
415
+ # operational times out.
416
+ ##
360
417
  def perform(*all_args)
361
418
  return yield if block_given?
362
- op, key, *args = *all_args
363
-
364
- key = key.to_s
365
- key = validate_key(key)
366
- begin
367
- server = ring.server_for_key(key)
368
- ret = server.request(op, key, *args)
369
- ret
370
- rescue NetworkError => e
371
- Dalli.logger.debug { e.inspect }
372
- Dalli.logger.debug { "retrying request with new server" }
373
- retry
374
- end
375
- end
376
419
 
377
- def validate_key(key)
378
- raise ArgumentError, "key cannot be blank" if !key || key.length == 0
379
- key = key_with_namespace(key)
380
- if key.length > 250
381
- max_length_before_namespace = 212 - (namespace || '').size
382
- key = "#{key[0, max_length_before_namespace]}:md5:#{Digest::MD5.hexdigest(key)}"
383
- end
384
- return key
385
- end
420
+ op, key, *args = all_args
386
421
 
387
- def key_with_namespace(key)
388
- (ns = namespace) ? "#{ns}:#{key}" : key
389
- end
390
-
391
- def key_without_namespace(key)
392
- (ns = namespace) ? key.sub(%r(\A#{Regexp.escape ns}:), '') : key
393
- end
422
+ key = key.to_s
423
+ key = @key_manager.validate_key(key)
394
424
 
395
- def namespace
396
- return nil unless @options[:namespace]
397
- @options[:namespace].is_a?(Proc) ? @options[:namespace].call.to_s : @options[:namespace].to_s
425
+ server = ring.server_for_key(key)
426
+ server.request(op, key, *args)
427
+ rescue NetworkError => e
428
+ Dalli.logger.debug { e.inspect }
429
+ Dalli.logger.debug { 'retrying request with new server' }
430
+ retry
398
431
  end
399
432
 
400
433
  def normalize_options(opts)
401
- if opts[:compression]
402
- Dalli.logger.warn "DEPRECATED: Dalli's :compression option is now just :compress => true. Please update your configuration."
403
- opts[:compress] = opts.delete(:compression)
404
- end
405
- begin
406
- opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in]
407
- rescue NoMethodError
408
- raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
409
- end
434
+ opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in]
410
435
  opts
436
+ rescue NoMethodError
437
+ raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
411
438
  end
412
439
 
413
- ##
414
- # Yields, one at a time, keys and their values+attributes.
415
- def get_multi_yielder(keys)
416
- perform do
417
- return {} if keys.empty?
418
- ring.lock do
419
- begin
420
- groups = groups_for_keys(keys)
421
- if unfound_keys = groups.delete(nil)
422
- Dalli.logger.debug { "unable to get keys for #{unfound_keys.length} keys because no matching server was found" }
423
- end
424
- make_multi_get_requests(groups)
425
-
426
- servers = groups.keys
427
- return if servers.empty?
428
- servers = perform_multi_response_start(servers)
429
-
430
- start = Time.now
431
- while true
432
- # remove any dead servers
433
- servers.delete_if { |s| s.sock.nil? }
434
- break if servers.empty?
435
-
436
- # calculate remaining timeout
437
- elapsed = Time.now - start
438
- timeout = servers.first.options[:socket_timeout]
439
- time_left = (elapsed > timeout) ? 0 : timeout - elapsed
440
-
441
- sockets = servers.map(&:sock)
442
- readable, _ = IO.select(sockets, nil, nil, time_left)
443
-
444
- if readable.nil?
445
- # no response within timeout; abort pending connections
446
- servers.each do |server|
447
- Dalli.logger.debug { "memcached at #{server.name} did not response within timeout" }
448
- server.multi_response_abort
449
- end
450
- break
451
-
452
- else
453
- readable.each do |sock|
454
- server = sock.server
455
-
456
- begin
457
- server.multi_response_nonblock.each_pair do |key, value_list|
458
- yield key_without_namespace(key), value_list
459
- end
460
-
461
- if server.multi_response_completed?
462
- servers.delete(server)
463
- end
464
- rescue NetworkError
465
- servers.delete(server)
466
- end
467
- end
468
- end
469
- end
470
- end
471
- end
472
- end
440
+ def pipelined_getter
441
+ PipelinedGetter.new(ring, @key_manager)
473
442
  end
474
-
475
443
  end
476
444
  end