dalli 2.7.8 → 3.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/{History.md → CHANGELOG.md} +168 -0
  3. data/Gemfile +5 -1
  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 +121 -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 +121 -0
  20. data/lib/dalli/protocol/meta/response_processor.rb +211 -0
  21. data/lib/dalli/protocol/meta.rb +178 -0
  22. data/lib/dalli/protocol/response_buffer.rb +54 -0
  23. data/lib/dalli/protocol/server_config_parser.rb +86 -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 +65 -28
  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