dalli 2.7.10 → 3.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/{History.md → CHANGELOG.md} +163 -2
  3. data/Gemfile +17 -1
  4. data/README.md +30 -221
  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 +121 -0
  9. data/lib/dalli/options.rb +6 -7
  10. data/lib/dalli/pid_cache.rb +40 -0
  11. data/lib/dalli/pipelined_getter.rb +177 -0
  12. data/lib/dalli/protocol/base.rb +239 -0
  13. data/lib/dalli/protocol/binary/request_formatter.rb +117 -0
  14. data/lib/dalli/protocol/binary/response_header.rb +36 -0
  15. data/lib/dalli/protocol/binary/response_processor.rb +239 -0
  16. data/lib/dalli/protocol/binary/sasl_authentication.rb +60 -0
  17. data/lib/dalli/protocol/binary.rb +173 -0
  18. data/lib/dalli/protocol/connection_manager.rb +254 -0
  19. data/lib/dalli/protocol/meta/key_regularizer.rb +31 -0
  20. data/lib/dalli/protocol/meta/request_formatter.rb +121 -0
  21. data/lib/dalli/protocol/meta/response_processor.rb +211 -0
  22. data/lib/dalli/protocol/meta.rb +178 -0
  23. data/lib/dalli/protocol/response_buffer.rb +54 -0
  24. data/lib/dalli/protocol/server_config_parser.rb +86 -0
  25. data/lib/dalli/protocol/ttl_sanitizer.rb +45 -0
  26. data/lib/dalli/protocol/value_compressor.rb +85 -0
  27. data/lib/dalli/protocol/value_marshaller.rb +59 -0
  28. data/lib/dalli/protocol/value_serializer.rb +91 -0
  29. data/lib/dalli/protocol.rb +8 -0
  30. data/lib/dalli/ring.rb +94 -83
  31. data/lib/dalli/server.rb +3 -746
  32. data/lib/dalli/servers_arg_normalizer.rb +54 -0
  33. data/lib/dalli/socket.rb +117 -137
  34. data/lib/dalli/version.rb +4 -1
  35. data/lib/dalli.rb +43 -15
  36. data/lib/rack/session/dalli.rb +103 -94
  37. metadata +34 -127
  38. data/lib/action_dispatch/middleware/session/dalli_store.rb +0 -82
  39. data/lib/active_support/cache/dalli_store.rb +0 -435
  40. 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