dalli 2.7.8 → 3.2.1

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 +12 -1
  3. data/History.md +151 -0
  4. data/README.md +27 -223
  5. data/lib/dalli/cas/client.rb +1 -57
  6. data/lib/dalli/client.rb +227 -254
  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 +95 -95
  36. metadata +43 -48
  37. data/lib/action_dispatch/middleware/session/dalli_store.rb +0 -82
  38. data/lib/active_support/cache/dalli_store.rb +0 -429
  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,17 +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
- return {} if keys.flatten.compact.empty?
99
+ keys.flatten!
100
+ keys.compact!
101
+
102
+ return {} if keys.empty?
103
+
68
104
  if block_given?
69
- get_multi_yielder(keys) {|k, data| yield k, data.first}
105
+ pipelined_getter.process(keys) { |k, data| yield k, data.first }
70
106
  else
71
- Hash.new.tap do |hash|
72
- 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 }
73
109
  end
74
110
  end
75
111
  end
76
112
 
77
- 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
78
128
 
79
129
  # Fetch the value associated with the key.
80
130
  # If a value is found, then it is returned.
@@ -84,17 +134,14 @@ module Dalli
84
134
  # If a value is not found (or if the found value is nil and :cache_nils is false)
85
135
  # and a block is given, the block will be invoked and its return value
86
136
  # written to the cache and returned.
87
- def fetch(key, ttl=nil, options=nil)
88
- options = options.nil? ? CACHE_NILS : options.merge(CACHE_NILS) if @options[:cache_nils]
89
- val = get(key, options)
90
- not_found = @options[:cache_nils] ?
91
- val == Dalli::Server::NOT_FOUND :
92
- val.nil?
93
- if not_found && block_given?
94
- val = yield
95
- add(key, val, ttl_or_default(ttl), options)
96
- end
97
- 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
98
145
  end
99
146
 
100
147
  ##
@@ -108,8 +155,8 @@ module Dalli
108
155
  # - nil if the key did not exist.
109
156
  # - false if the value was changed by someone else.
110
157
  # - true if the value was successfully updated.
111
- def cas(key, ttl=nil, options=nil, &block)
112
- 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)
113
160
  end
114
161
 
115
162
  ##
@@ -119,30 +166,78 @@ module Dalli
119
166
  # Returns:
120
167
  # - false if the value was changed by someone else.
121
168
  # - true if the value was successfully updated.
122
- def cas!(key, ttl=nil, options=nil, &block)
123
- 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)
124
171
  end
125
172
 
126
- def set(key, value, ttl=nil, options=nil)
127
- 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)
128
209
  end
129
210
 
130
211
  ##
131
212
  # Conditionally add a key/value pair, if the key does not already exist
132
213
  # on the server. Returns truthy if the operation succeeded.
133
- def add(key, value, ttl=nil, options=nil)
134
- 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)
135
216
  end
136
217
 
137
218
  ##
138
219
  # Conditionally add a key/value pair, only if the key already exists
139
220
  # on the server. Returns truthy if the operation succeeded.
140
- def replace(key, value, ttl=nil, options=nil)
141
- 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)
142
237
  end
143
238
 
144
239
  def delete(key)
145
- perform(:delete, key, 0)
240
+ delete_cas(key, 0)
146
241
  end
147
242
 
148
243
  ##
@@ -159,13 +254,6 @@ module Dalli
159
254
  perform(:prepend, key, value.to_s)
160
255
  end
161
256
 
162
- def flush(delay=0)
163
- time = -delay
164
- ring.servers.map { |s| s.request(:flush, time += delay) }
165
- end
166
-
167
- alias_method :flush_all, :flush
168
-
169
257
  ##
170
258
  # Incr adds the given amount to the counter on the memcached server.
171
259
  # Amt must be a positive integer value.
@@ -177,8 +265,11 @@ module Dalli
177
265
  # Note that the ttl will only apply if the counter does not already
178
266
  # exist. To increase an existing counter and update its TTL, use
179
267
  # #cas.
180
- def incr(key, amt=1, ttl=nil, default=nil)
181
- 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
+
182
273
  perform(:incr, key, amt.to_i, ttl_or_default(ttl), default)
183
274
  end
184
275
 
@@ -196,29 +287,34 @@ module Dalli
196
287
  # Note that the ttl will only apply if the counter does not already
197
288
  # exist. To decrease an existing counter and update its TTL, use
198
289
  # #cas.
199
- def decr(key, amt=1, ttl=nil, default=nil)
200
- 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
+
201
295
  perform(:decr, key, amt.to_i, ttl_or_default(ttl), default)
202
296
  end
203
297
 
204
298
  ##
205
- # Touch updates expiration time for a given key.
206
- #
207
- # Returns true if key exists, otherwise nil.
208
- def touch(key, ttl=nil)
209
- resp = perform(:touch, key, ttl_or_default(ttl))
210
- 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) }
211
304
  end
305
+ alias flush_all flush
306
+
307
+ ALLOWED_STAT_KEYS = %i[items slabs settings].freeze
212
308
 
213
309
  ##
214
310
  # Collect the stats for each server.
215
311
  # You can optionally pass a type including :items, :slabs or :settings to get specific stats
216
312
  # Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
217
- def stats(type=nil)
218
- type = nil if ![nil, :items,:slabs,:settings].include? type
313
+ def stats(type = nil)
314
+ type = nil unless ALLOWED_STAT_KEYS.include? type
219
315
  values = {}
220
316
  ring.servers.each do |server|
221
- 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
222
318
  end
223
319
  values
224
320
  end
@@ -231,32 +327,40 @@ module Dalli
231
327
  end
232
328
  end
233
329
 
234
- ##
235
- ## Make sure memcache servers are alive, or raise an Dalli::RingError
236
- def alive!
237
- ring.server_for_key("")
238
- end
239
-
240
330
  ##
241
331
  ## Version of the memcache servers.
242
332
  def version
243
333
  values = {}
244
334
  ring.servers.each do |server|
245
- values["#{server.name}"] = server.alive? ? server.request(:version) : nil
335
+ values[server.name.to_s] = server.alive? ? server.request(:version) : nil
246
336
  end
247
337
  values
248
338
  end
249
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
+
250
346
  ##
251
347
  # Close our connection to each server.
252
348
  # If you perform another operation after this, the connections will be re-established.
253
349
  def close
254
- if @ring
255
- @ring.servers.each { |s| s.close }
256
- @ring = nil
257
- 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]
258
363
  end
259
- alias_method :reset, :close
260
364
 
261
365
  # Stub method so a bare Dalli client can pretend to be a connection pool.
262
366
  def with
@@ -265,207 +369,76 @@ module Dalli
265
369
 
266
370
  private
267
371
 
268
- 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)
269
377
  (value, cas) = perform(:cas, key)
270
- value = (!value || value == 'Not found') ? nil : value
271
378
  return if value.nil? && !always_set
379
+
272
380
  newvalue = yield(value)
273
- perform(:set, key, newvalue, ttl_or_default(ttl), cas, options)
381
+ perform(:set, key, newvalue, ttl_or_default(ttl), cas, req_options)
274
382
  end
275
383
 
384
+ ##
385
+ # Uses the argument TTL or the client-wide default. Ensures
386
+ # that the value is an integer
387
+ ##
276
388
  def ttl_or_default(ttl)
277
389
  (ttl || @options[:expires_in]).to_i
278
390
  rescue NoMethodError
279
391
  raise ArgumentError, "Cannot convert ttl (#{ttl}) to an integer"
280
392
  end
281
393
 
282
- def groups_for_keys(*keys)
283
- groups = mapped_keys(keys).flatten.group_by do |key|
284
- begin
285
- ring.server_for_key(key)
286
- rescue Dalli::RingError
287
- Dalli.logger.debug { "unable to get key #{key}" }
288
- nil
289
- end
290
- end
291
- return groups
292
- end
293
-
294
- def mapped_keys(keys)
295
- keys.flatten.map {|a| validate_key(a.to_s)}
296
- end
297
-
298
- def make_multi_get_requests(groups)
299
- groups.each do |server, keys_for_server|
300
- begin
301
- # TODO: do this with the perform chokepoint?
302
- # But given the fact that fetching the response doesn't take place
303
- # in that slot it's misleading anyway. Need to move all of this method
304
- # into perform to be meaningful
305
- server.request(:send_multiget, keys_for_server)
306
- rescue DalliError, NetworkError => e
307
- Dalli.logger.debug { e.inspect }
308
- Dalli.logger.debug { "unable to get keys for server #{server.name}" }
309
- end
310
- end
394
+ def ring
395
+ @ring ||= Dalli::Ring.new(@normalized_servers, protocol_implementation, @options)
311
396
  end
312
397
 
313
- def perform_multi_response_start(servers)
314
- servers.each do |server|
315
- next unless server.alive?
316
- begin
317
- server.multi_response_start
318
- rescue DalliError, NetworkError => e
319
- Dalli.logger.debug { e.inspect }
320
- Dalli.logger.debug { "results from this server will be missing" }
321
- servers.delete(server)
322
- end
323
- end
324
- 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
325
405
  end
326
406
 
327
407
  ##
328
- # Normalizes the argument into an array of servers.
329
- # If the argument is a string, it's expected that the URIs are comma separated e.g.
330
- # "memcache1.example.com:11211,memcache2.example.com:11211,memcache3.example.com:11211"
331
- def normalize_servers(servers)
332
- if servers.is_a? String
333
- return servers.split(",")
334
- else
335
- return servers
336
- end
337
- end
338
-
339
- def ring
340
- @ring ||= Dalli::Ring.new(
341
- @servers.map do |s|
342
- server_options = {}
343
- if s =~ %r{\Amemcached://}
344
- uri = URI.parse(s)
345
- server_options[:username] = uri.user
346
- server_options[:password] = uri.password
347
- s = "#{uri.host}:#{uri.port}"
348
- end
349
- Dalli::Server.new(s, @options.merge(server_options))
350
- end, @options
351
- )
352
- end
353
-
354
- # 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
+ ##
355
417
  def perform(*all_args)
356
418
  return yield if block_given?
357
- op, key, *args = *all_args
358
-
359
- key = key.to_s
360
- key = validate_key(key)
361
- begin
362
- server = ring.server_for_key(key)
363
- ret = server.request(op, key, *args)
364
- ret
365
- rescue NetworkError => e
366
- Dalli.logger.debug { e.inspect }
367
- Dalli.logger.debug { "retrying request with new server" }
368
- retry
369
- end
370
- end
371
-
372
- def validate_key(key)
373
- raise ArgumentError, "key cannot be blank" if !key || key.length == 0
374
- key = key_with_namespace(key)
375
- if key.length > 250
376
- max_length_before_namespace = 212 - (namespace || '').size
377
- key = "#{key[0, max_length_before_namespace]}:md5:#{Digest::MD5.hexdigest(key)}"
378
- end
379
- return key
380
- end
381
419
 
382
- def key_with_namespace(key)
383
- (ns = namespace) ? "#{ns}:#{key}" : key
384
- end
420
+ op, key, *args = all_args
385
421
 
386
- def key_without_namespace(key)
387
- (ns = namespace) ? key.sub(%r(\A#{Regexp.escape ns}:), '') : key
388
- end
422
+ key = key.to_s
423
+ key = @key_manager.validate_key(key)
389
424
 
390
- def namespace
391
- return nil unless @options[:namespace]
392
- @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
393
431
  end
394
432
 
395
433
  def normalize_options(opts)
396
- if opts[:compression]
397
- Dalli.logger.warn "DEPRECATED: Dalli's :compression option is now just :compress => true. Please update your configuration."
398
- opts[:compress] = opts.delete(:compression)
399
- end
400
- begin
401
- opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in]
402
- rescue NoMethodError
403
- raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
404
- end
434
+ opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in]
405
435
  opts
436
+ rescue NoMethodError
437
+ raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
406
438
  end
407
439
 
408
- ##
409
- # Yields, one at a time, keys and their values+attributes.
410
- def get_multi_yielder(keys)
411
- perform do
412
- return {} if keys.empty?
413
- ring.lock do
414
- begin
415
- groups = groups_for_keys(keys)
416
- if unfound_keys = groups.delete(nil)
417
- Dalli.logger.debug { "unable to get keys for #{unfound_keys.length} keys because no matching server was found" }
418
- end
419
- make_multi_get_requests(groups)
420
-
421
- servers = groups.keys
422
- return if servers.empty?
423
- servers = perform_multi_response_start(servers)
424
-
425
- start = Time.now
426
- while true
427
- # remove any dead servers
428
- servers.delete_if { |s| s.sock.nil? }
429
- break if servers.empty?
430
-
431
- # calculate remaining timeout
432
- elapsed = Time.now - start
433
- timeout = servers.first.options[:socket_timeout]
434
- time_left = (elapsed > timeout) ? 0 : timeout - elapsed
435
-
436
- sockets = servers.map(&:sock)
437
- readable, _ = IO.select(sockets, nil, nil, time_left)
438
-
439
- if readable.nil?
440
- # no response within timeout; abort pending connections
441
- servers.each do |server|
442
- Dalli.logger.debug { "memcached at #{server.name} did not response within timeout" }
443
- server.multi_response_abort
444
- end
445
- break
446
-
447
- else
448
- readable.each do |sock|
449
- server = sock.server
450
-
451
- begin
452
- server.multi_response_nonblock.each_pair do |key, value_list|
453
- yield key_without_namespace(key), value_list
454
- end
455
-
456
- if server.multi_response_completed?
457
- servers.delete(server)
458
- end
459
- rescue NetworkError
460
- servers.delete(server)
461
- end
462
- end
463
- end
464
- end
465
- end
466
- end
467
- end
440
+ def pipelined_getter
441
+ PipelinedGetter.new(ring, @key_manager)
468
442
  end
469
-
470
443
  end
471
444
  end
@@ -1,8 +1,13 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'zlib'
3
4
  require 'stringio'
4
5
 
5
6
  module Dalli
7
+ ##
8
+ # Default compressor used by Dalli, that uses
9
+ # Zlib DEFLATE to compress data.
10
+ ##
6
11
  class Compressor
7
12
  def self.compress(data)
8
13
  Zlib::Deflate.deflate(data)
@@ -13,9 +18,14 @@ module Dalli
13
18
  end
14
19
  end
15
20
 
21
+ ##
22
+ # Alternate compressor for Dalli, that uses
23
+ # Gzip. Gzip adds a checksum to each compressed
24
+ # entry.
25
+ ##
16
26
  class GzipCompressor
17
27
  def self.compress(data)
18
- io = StringIO.new(String.new(""), "w")
28
+ io = StringIO.new(+'', 'w')
19
29
  gz = Zlib::GzipWriter.new(io)
20
30
  gz.write(data)
21
31
  gz.close
@@ -23,7 +33,7 @@ module Dalli
23
33
  end
24
34
 
25
35
  def self.decompress(data)
26
- io = StringIO.new(data, "rb")
36
+ io = StringIO.new(data, 'rb')
27
37
  Zlib::GzipReader.new(io).read
28
38
  end
29
39
  end