dalli 2.7.0 → 3.0.4

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.

data/lib/dalli/client.rb CHANGED
@@ -1,32 +1,41 @@
1
- require 'digest/md5'
2
- require 'set'
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/md5"
4
+ require "set"
3
5
 
4
6
  # encoding: ascii
5
7
  module Dalli
6
8
  class Client
7
-
8
9
  ##
9
10
  # Dalli::Client is the main class which developers will use to interact with
10
11
  # the memcached server. Usage:
11
12
  #
12
- # Dalli::Client.new(['localhost:11211:10', 'cache-2.example.com:11211:5', '192.168.0.1:22122:5'],
13
+ # Dalli::Client.new(['localhost:11211:10', 'cache-2.example.com:11211:5', '192.168.0.1:22122:5', '/var/run/memcached/socket'],
13
14
  # :threadsafe => true, :failover => true, :expires_in => 300)
14
15
  #
15
16
  # servers is an Array of "host:port:weight" where weight allows you to distribute cache unevenly.
16
17
  # Both weight and port are optional. If you pass in nil, Dalli will use the <tt>MEMCACHE_SERVERS</tt>
17
- # environment variable or default to 'localhost:11211' if it is not present.
18
+ # environment variable or default to 'localhost:11211' if it is not present. Dalli also supports
19
+ # the ability to connect to Memcached on localhost through a UNIX socket. To use this functionality,
20
+ # use a full pathname (beginning with a slash character '/') in place of the "host:port" pair in
21
+ # the server configuration.
18
22
  #
19
23
  # Options:
20
24
  # - :namespace - prepend each key with this value to provide simple namespacing.
21
25
  # - :failover - if a server is down, look for and store values on another server in the ring. Default: true.
22
26
  # - :threadsafe - ensure that only one thread is actively using a socket at a time. Default: true.
23
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
24
- # - :compress - defaults to false, if true Dalli will compress values larger than 1024 bytes before sending them to memcached.
28
+ # - :compress - if true Dalli will compress values larger than compression_min_size bytes before sending them to memcached. Default: true.
29
+ # - :compression_min_size - the minimum size (in bytes) for which Dalli will compress values sent to Memcached. Defaults to 4K.
25
30
  # - :serializer - defaults to Marshal
26
31
  # - :compressor - defaults to zlib
32
+ # - :cache_nils - defaults to false, if true Dalli will not treat cached nil values as 'not found' for #fetch operations.
33
+ # - :digest_class - defaults to Digest::MD5, allows you to pass in an object that responds to the hexdigest method, useful for injecting a FIPS compliant hash object.
34
+ # - :protocol_implementation - defaults to Dalli::Protocol::Binary which uses the binary protocol. Allows you to pass an alternative implementation using another protocol.
27
35
  #
28
- def initialize(servers=nil, options={})
29
- @servers = normalize_servers(servers || ENV["MEMCACHE_SERVERS"] || '127.0.0.1:11211')
36
+ def initialize(servers = nil, options = {})
37
+ validate_servers_arg(servers)
38
+ @servers = normalize_servers(servers || ENV["MEMCACHE_SERVERS"] || "127.0.0.1:11211")
30
39
  @options = normalize_options(options)
31
40
  @ring = nil
32
41
  end
@@ -49,8 +58,9 @@ module Dalli
49
58
 
50
59
  ##
51
60
  # Get the value associated with the key.
52
- def get(key, options=nil)
53
- perform(:get, key)
61
+ # If a value is not found, then +nil+ is returned.
62
+ def get(key, options = nil)
63
+ perform(:get, key, options)
54
64
  end
55
65
 
56
66
  ##
@@ -58,21 +68,38 @@ module Dalli
58
68
  # If a block is given, yields key/value pairs one at a time.
59
69
  # Otherwise returns a hash of { 'key' => 'value', 'key2' => 'value1' }
60
70
  def get_multi(*keys)
71
+ keys.flatten!
72
+ keys.compact!
73
+
74
+ return {} if keys.empty?
61
75
  if block_given?
62
- get_multi_yielder(keys) {|k, data| yield k, data.first}
76
+ get_multi_yielder(keys) { |k, data| yield k, data.first }
63
77
  else
64
- Hash.new.tap do |hash|
65
- get_multi_yielder(keys) {|k, data| hash[k] = data.first}
78
+ {}.tap do |hash|
79
+ get_multi_yielder(keys) { |k, data| hash[k] = data.first }
66
80
  end
67
81
  end
68
82
  end
69
83
 
70
- def fetch(key, ttl=nil, options=nil)
71
- ttl ||= @options[:expires_in].to_i
84
+ CACHE_NILS = {cache_nils: true}.freeze
85
+
86
+ # Fetch the value associated with the key.
87
+ # If a value is found, then it is returned.
88
+ #
89
+ # If a value is not found and no block is given, then nil is returned.
90
+ #
91
+ # If a value is not found (or if the found value is nil and :cache_nils is false)
92
+ # and a block is given, the block will be invoked and its return value
93
+ # written to the cache and returned.
94
+ def fetch(key, ttl = nil, options = nil)
95
+ options = options.nil? ? CACHE_NILS : options.merge(CACHE_NILS) if @options[:cache_nils]
72
96
  val = get(key, options)
73
- if val.nil? && block_given?
97
+ not_found = @options[:cache_nils] ?
98
+ val == Dalli::Protocol::NOT_FOUND :
99
+ val.nil?
100
+ if not_found && block_given?
74
101
  val = yield
75
- add(key, val, ttl, options)
102
+ add(key, val, ttl_or_default(ttl), options)
76
103
  end
77
104
  val
78
105
  end
@@ -88,35 +115,37 @@ module Dalli
88
115
  # - nil if the key did not exist.
89
116
  # - false if the value was changed by someone else.
90
117
  # - true if the value was successfully updated.
91
- def cas(key, ttl=nil, options=nil, &block)
92
- ttl ||= @options[:expires_in].to_i
93
- (value, cas) = perform(:cas, key)
94
- value = (!value || value == 'Not found') ? nil : value
95
- if value
96
- newvalue = block.call(value)
97
- perform(:set, key, newvalue, ttl, cas, options)
98
- end
118
+ def cas(key, ttl = nil, options = nil, &block)
119
+ cas_core(key, false, ttl, options, &block)
99
120
  end
100
121
 
101
- def set(key, value, ttl=nil, options=nil)
102
- ttl ||= @options[:expires_in].to_i
103
- perform(:set, key, value, ttl, 0, options)
122
+ ##
123
+ # like #cas, but will yield to the block whether or not the value
124
+ # already exists.
125
+ #
126
+ # Returns:
127
+ # - false if the value was changed by someone else.
128
+ # - true if the value was successfully updated.
129
+ def cas!(key, ttl = nil, options = nil, &block)
130
+ cas_core(key, true, ttl, options, &block)
131
+ end
132
+
133
+ def set(key, value, ttl = nil, options = nil)
134
+ perform(:set, key, value, ttl_or_default(ttl), 0, options)
104
135
  end
105
136
 
106
137
  ##
107
138
  # Conditionally add a key/value pair, if the key does not already exist
108
- # on the server. Returns true if the operation succeeded.
109
- def add(key, value, ttl=nil, options=nil)
110
- ttl ||= @options[:expires_in].to_i
111
- perform(:add, key, value, ttl, options)
139
+ # on the server. Returns truthy if the operation succeeded.
140
+ def add(key, value, ttl = nil, options = nil)
141
+ perform(:add, key, value, ttl_or_default(ttl), options)
112
142
  end
113
143
 
114
144
  ##
115
145
  # Conditionally add a key/value pair, only if the key already exists
116
- # on the server. Returns true if the operation succeeded.
117
- def replace(key, value, ttl=nil, options=nil)
118
- ttl ||= @options[:expires_in].to_i
119
- perform(:replace, key, value, ttl, 0, options)
146
+ # on the server. Returns truthy if the operation succeeded.
147
+ def replace(key, value, ttl = nil, options = nil)
148
+ perform(:replace, key, value, ttl_or_default(ttl), 0, options)
120
149
  end
121
150
 
122
151
  def delete(key)
@@ -137,7 +166,7 @@ module Dalli
137
166
  perform(:prepend, key, value.to_s)
138
167
  end
139
168
 
140
- def flush(delay=0)
169
+ def flush(delay = 0)
141
170
  time = -delay
142
171
  ring.servers.map { |s| s.request(:flush, time += delay) }
143
172
  end
@@ -155,10 +184,9 @@ module Dalli
155
184
  # Note that the ttl will only apply if the counter does not already
156
185
  # exist. To increase an existing counter and update its TTL, use
157
186
  # #cas.
158
- def incr(key, amt=1, ttl=nil, default=nil)
187
+ def incr(key, amt = 1, ttl = nil, default = nil)
159
188
  raise ArgumentError, "Positive values only: #{amt}" if amt < 0
160
- ttl ||= @options[:expires_in].to_i
161
- perform(:incr, key, amt.to_i, ttl, default)
189
+ perform(:incr, key, amt.to_i, ttl_or_default(ttl), default)
162
190
  end
163
191
 
164
192
  ##
@@ -175,31 +203,37 @@ module Dalli
175
203
  # Note that the ttl will only apply if the counter does not already
176
204
  # exist. To decrease an existing counter and update its TTL, use
177
205
  # #cas.
178
- def decr(key, amt=1, ttl=nil, default=nil)
206
+ def decr(key, amt = 1, ttl = nil, default = nil)
179
207
  raise ArgumentError, "Positive values only: #{amt}" if amt < 0
180
- ttl ||= @options[:expires_in].to_i
181
- perform(:decr, key, amt.to_i, ttl, default)
208
+ perform(:decr, key, amt.to_i, ttl_or_default(ttl), default)
182
209
  end
183
210
 
184
211
  ##
185
212
  # Touch updates expiration time for a given key.
186
213
  #
187
214
  # Returns true if key exists, otherwise nil.
188
- def touch(key, ttl=nil)
189
- ttl ||= @options[:expires_in].to_i
190
- resp = perform(:touch, key, ttl)
215
+ def touch(key, ttl = nil)
216
+ resp = perform(:touch, key, ttl_or_default(ttl))
191
217
  resp.nil? ? nil : true
192
218
  end
193
219
 
220
+ ##
221
+ # Gat (get and touch) fetch an item and simultaneously update its expiration time.
222
+ #
223
+ # If a value is not found, then +nil+ is returned.
224
+ def gat(key, ttl = nil)
225
+ perform(:gat, key, ttl_or_default(ttl))
226
+ end
227
+
194
228
  ##
195
229
  # Collect the stats for each server.
196
- # You can optionally pass a type including :items or :slabs to get specific stats
230
+ # You can optionally pass a type including :items, :slabs or :settings to get specific stats
197
231
  # Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
198
- def stats(type=nil)
199
- type = nil if ![nil, :items,:slabs].include? type
232
+ def stats(type = nil)
233
+ type = nil unless [nil, :items, :slabs, :settings].include? type
200
234
  values = {}
201
235
  ring.servers.each do |server|
202
- values["#{server.hostname}:#{server.port}"] = server.alive? ? server.request(:stats,type.to_s) : nil
236
+ values[server.name.to_s] = server.alive? ? server.request(:stats, type.to_s) : nil
203
237
  end
204
238
  values
205
239
  end
@@ -212,16 +246,74 @@ module Dalli
212
246
  end
213
247
  end
214
248
 
249
+ ##
250
+ ## Make sure memcache servers are alive, or raise an Dalli::RingError
251
+ def alive!
252
+ ring.server_for_key("")
253
+ end
254
+
215
255
  ##
216
256
  ## Version of the memcache servers.
217
257
  def version
218
258
  values = {}
219
259
  ring.servers.each do |server|
220
- values["#{server.hostname}:#{server.port}"] = server.alive? ? server.request(:version) : nil
260
+ values[server.name.to_s] = server.alive? ? server.request(:version) : nil
221
261
  end
222
262
  values
223
263
  end
224
264
 
265
+ ##
266
+ # Get the value and CAS ID associated with the key. If a block is provided,
267
+ # value and CAS will be passed to the block.
268
+ def get_cas(key)
269
+ (value, cas) = perform(:cas, key)
270
+ value = !value || value == "Not found" ? nil : value
271
+ if block_given?
272
+ yield value, cas
273
+ else
274
+ [value, cas]
275
+ end
276
+ end
277
+
278
+ ##
279
+ # Fetch multiple keys efficiently, including available metadata such as CAS.
280
+ # If a block is given, yields key/data pairs one a time. Data is an array:
281
+ # [value, cas_id]
282
+ # If no block is given, returns a hash of
283
+ # { 'key' => [value, cas_id] }
284
+ def get_multi_cas(*keys)
285
+ if block_given?
286
+ get_multi_yielder(keys) { |*args| yield(*args) }
287
+ else
288
+ {}.tap do |hash|
289
+ get_multi_yielder(keys) { |k, data| hash[k] = data }
290
+ end
291
+ end
292
+ end
293
+
294
+ ##
295
+ # Set the key-value pair, verifying existing CAS.
296
+ # Returns the resulting CAS value if succeeded, and falsy otherwise.
297
+ def set_cas(key, value, cas, ttl = nil, options = nil)
298
+ ttl ||= @options[:expires_in].to_i
299
+ perform(:set, key, value, ttl, cas, options)
300
+ end
301
+
302
+ ##
303
+ # Conditionally add a key/value pair, verifying existing CAS, only if the
304
+ # key already exists on the server. Returns the new CAS value if the
305
+ # operation succeeded, or falsy otherwise.
306
+ def replace_cas(key, value, cas, ttl = nil, options = nil)
307
+ ttl ||= @options[:expires_in].to_i
308
+ perform(:replace, key, value, ttl, cas, options)
309
+ end
310
+
311
+ # Delete a key/value pair, verifying existing CAS.
312
+ # Returns true if succeeded, and falsy otherwise.
313
+ def delete_cas(key, cas = 0)
314
+ perform(:delete, key, cas)
315
+ end
316
+
225
317
  ##
226
318
  # Close our connection to each server.
227
319
  # If you perform another operation after this, the connections will be re-established.
@@ -240,88 +332,118 @@ module Dalli
240
332
 
241
333
  private
242
334
 
335
+ def cas_core(key, always_set, ttl = nil, options = nil)
336
+ (value, cas) = perform(:cas, key)
337
+ value = !value || value == "Not found" ? nil : value
338
+ return if value.nil? && !always_set
339
+ newvalue = yield(value)
340
+ perform(:set, key, newvalue, ttl_or_default(ttl), cas, options)
341
+ end
342
+
343
+ def ttl_or_default(ttl)
344
+ (ttl || @options[:expires_in]).to_i
345
+ rescue NoMethodError
346
+ raise ArgumentError, "Cannot convert ttl (#{ttl}) to an integer"
347
+ end
348
+
243
349
  def groups_for_keys(*keys)
244
- groups = mapped_keys(keys).flatten.group_by do |key|
350
+ keys.flatten!
351
+ keys.map! { |a| validate_key(a.to_s) }
352
+
353
+ keys.group_by { |key|
245
354
  begin
246
355
  ring.server_for_key(key)
247
356
  rescue Dalli::RingError
248
357
  Dalli.logger.debug { "unable to get key #{key}" }
249
358
  nil
250
359
  end
251
- end
252
- return groups
253
- end
254
-
255
- def mapped_keys(keys)
256
- keys.flatten.map {|a| validate_key(a.to_s)}
360
+ }
257
361
  end
258
362
 
259
363
  def make_multi_get_requests(groups)
260
364
  groups.each do |server, keys_for_server|
261
- begin
262
- # TODO: do this with the perform chokepoint?
263
- # But given the fact that fetching the response doesn't take place
264
- # in that slot it's misleading anyway. Need to move all of this method
265
- # into perform to be meaningful
266
- server.request(:send_multiget, keys_for_server)
267
- rescue DalliError, NetworkError => e
268
- Dalli.logger.debug { e.inspect }
269
- Dalli.logger.debug { "unable to get keys for server #{server.hostname}:#{server.port}" }
270
- end
365
+ # TODO: do this with the perform chokepoint?
366
+ # But given the fact that fetching the response doesn't take place
367
+ # in that slot it's misleading anyway. Need to move all of this method
368
+ # into perform to be meaningful
369
+ server.request(:send_multiget, keys_for_server)
370
+ rescue DalliError, NetworkError => e
371
+ Dalli.logger.debug { e.inspect }
372
+ Dalli.logger.debug { "unable to get keys for server #{server.name}" }
271
373
  end
272
374
  end
273
375
 
274
376
  def perform_multi_response_start(servers)
377
+ deleted = []
378
+
275
379
  servers.each do |server|
276
380
  next unless server.alive?
381
+
277
382
  begin
278
383
  server.multi_response_start
279
- rescue DalliError, NetworkError => e
384
+ rescue Dalli::NetworkError
385
+ servers.each { |s| s.multi_response_abort unless s.sock.nil? }
386
+ raise
387
+ rescue Dalli::DalliError => e
280
388
  Dalli.logger.debug { e.inspect }
281
389
  Dalli.logger.debug { "results from this server will be missing" }
282
- servers.delete(server)
390
+ deleted.append(server)
283
391
  end
284
392
  end
285
- servers
393
+
394
+ servers.delete_if { |server| deleted.include?(server) }
395
+ end
396
+
397
+ ##
398
+ # Ensures that the servers arg is either an array or a string.
399
+ def validate_servers_arg(servers)
400
+ return if servers.nil?
401
+ return if servers.is_a?(Array)
402
+ return if servers.is_a?(String)
403
+
404
+ raise ArgumentError, "An explicit servers argument must be a comma separated string or an array containing strings."
286
405
  end
287
406
 
288
407
  ##
289
- # Normalizes the argument into an array of servers. If the argument is a string, it's expected to be of
290
- # the format "memcache1.example.com:11211[,memcache2.example.com:11211[,memcache3.example.com:11211[...]]]
408
+ # Normalizes the argument into an array of servers.
409
+ # If the argument is a string, or an array containing strings, it's expected that the URIs are comma separated e.g.
410
+ # "memcache1.example.com:11211,memcache2.example.com:11211,memcache3.example.com:11211"
291
411
  def normalize_servers(servers)
292
- if servers.is_a? String
293
- return servers.split(",")
294
- else
295
- return servers
412
+ Array(servers).flat_map do |server|
413
+ if server.is_a? String
414
+ server.split(",")
415
+ else
416
+ server
417
+ end
296
418
  end
297
419
  end
298
420
 
299
421
  def ring
300
422
  @ring ||= Dalli::Ring.new(
301
- @servers.map do |s|
302
- server_options = {}
303
- if s =~ %r{\Amemcached://}
423
+ @servers.map { |s|
424
+ server_options = {}
425
+ if s.start_with?("memcached://")
304
426
  uri = URI.parse(s)
305
427
  server_options[:username] = uri.user
306
428
  server_options[:password] = uri.password
307
429
  s = "#{uri.host}:#{uri.port}"
308
430
  end
309
- Dalli::Server.new(s, @options.merge(server_options))
310
- end, @options
431
+ @options.fetch(:protocol_implementation, Dalli::Protocol::Binary).new(s, @options.merge(server_options))
432
+ }, @options
311
433
  )
312
434
  end
313
435
 
314
436
  # Chokepoint method for instrumentation
315
- def perform(*all_args, &blk)
316
- return blk.call if blk
317
- op, key, *args = *all_args
318
-
319
- key = key.to_s
320
- key = validate_key(key)
437
+ def perform(*all_args)
321
438
  begin
439
+ return yield if block_given?
440
+ op, key, *args = all_args
441
+
442
+ key = key.to_s
443
+ key = validate_key(key)
444
+
322
445
  server = ring.server_for_key(key)
323
- ret = server.request(op, key, *args)
324
- ret
446
+ server.request(op, key, *args)
325
447
  rescue NetworkError => e
326
448
  Dalli.logger.debug { e.inspect }
327
449
  Dalli.logger.debug { "retrying request with new server" }
@@ -333,10 +455,11 @@ module Dalli
333
455
  raise ArgumentError, "key cannot be blank" if !key || key.length == 0
334
456
  key = key_with_namespace(key)
335
457
  if key.length > 250
336
- max_length_before_namespace = 212 - (namespace || '').size
337
- key = "#{key[0, max_length_before_namespace]}:md5:#{Digest::MD5.hexdigest(key)}"
458
+ digest_class = @options[:digest_class] || ::Digest::MD5
459
+ max_length_before_namespace = 212 - (namespace || "").size
460
+ key = "#{key[0, max_length_before_namespace]}:md5:#{digest_class.hexdigest(key)}"
338
461
  end
339
- return key
462
+ key
340
463
  end
341
464
 
342
465
  def key_with_namespace(key)
@@ -344,11 +467,12 @@ module Dalli
344
467
  end
345
468
 
346
469
  def key_without_namespace(key)
347
- (ns = namespace) ? key.sub(%r(\A#{ns}:), '') : key
470
+ (ns = namespace) ? key.sub(%r{\A#{Regexp.escape ns}:}, "") : key
348
471
  end
349
472
 
350
473
  def namespace
351
- @options[:namespace].is_a?(Proc) ? @options[:namespace].call : @options[:namespace]
474
+ return nil unless @options[:namespace]
475
+ @options[:namespace].is_a?(Proc) ? @options[:namespace].call.to_s : @options[:namespace].to_s
352
476
  end
353
477
 
354
478
  def normalize_options(opts)
@@ -361,6 +485,9 @@ module Dalli
361
485
  rescue NoMethodError
362
486
  raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
363
487
  end
488
+ if opts[:digest_class] && !opts[:digest_class].respond_to?(:hexdigest)
489
+ raise ArgumentError, "The digest_class object must respond to the hexdigest method"
490
+ end
364
491
  opts
365
492
  end
366
493
 
@@ -370,56 +497,53 @@ module Dalli
370
497
  perform do
371
498
  return {} if keys.empty?
372
499
  ring.lock do
373
- begin
374
- groups = groups_for_keys(keys)
375
- if unfound_keys = groups.delete(nil)
376
- Dalli.logger.debug { "unable to get keys for #{unfound_keys.length} keys because no matching server was found" }
377
- end
378
- make_multi_get_requests(groups)
379
-
380
- servers = groups.keys
381
- return if servers.empty?
382
- servers = perform_multi_response_start(servers)
383
-
384
- start = Time.now
385
- loop do
386
- # remove any dead servers
387
- servers.delete_if { |s| s.sock.nil? }
388
- break if servers.empty?
389
-
390
- # calculate remaining timeout
391
- elapsed = Time.now - start
392
- timeout = servers.first.options[:socket_timeout]
393
- if elapsed > timeout
394
- readable = nil
395
- else
396
- sockets = servers.map(&:sock)
397
- readable, _ = IO.select(sockets, nil, nil, timeout - elapsed)
500
+ groups = groups_for_keys(keys)
501
+ if (unfound_keys = groups.delete(nil))
502
+ Dalli.logger.debug { "unable to get keys for #{unfound_keys.length} keys because no matching server was found" }
503
+ end
504
+ make_multi_get_requests(groups)
505
+
506
+ servers = groups.keys
507
+ return if servers.empty?
508
+ servers = perform_multi_response_start(servers)
509
+
510
+ start = Time.now
511
+ loop do
512
+ # remove any dead servers
513
+ servers.delete_if { |s| s.sock.nil? }
514
+ break if servers.empty?
515
+
516
+ # calculate remaining timeout
517
+ elapsed = Time.now - start
518
+ timeout = servers.first.options[:socket_timeout]
519
+ time_left = elapsed > timeout ? 0 : timeout - elapsed
520
+
521
+ sockets = servers.map(&:sock)
522
+ readable, _ = IO.select(sockets, nil, nil, time_left)
523
+
524
+ if readable.nil?
525
+ # no response within timeout; abort pending connections
526
+ servers.each do |server|
527
+ Dalli.logger.debug { "memcached at #{server.name} did not response within timeout" }
528
+ server.multi_response_abort
398
529
  end
530
+ break
399
531
 
400
- if readable.nil?
401
- # no response within timeout; abort pending connections
402
- servers.each do |server|
403
- Dalli.logger.debug { "memcached at #{server.name} did not response within timeout" }
404
- server.multi_response_abort
405
- end
406
- break
532
+ else
533
+ readable.each do |sock|
534
+ server = sock.server
407
535
 
408
- else
409
- readable.each do |sock|
410
- server = sock.server
411
-
412
- begin
413
- server.multi_response_nonblock.each_pair do |key, value_list|
414
- yield key_without_namespace(key), value_list
415
- end
536
+ begin
537
+ server.multi_response_nonblock.each_pair do |key, value_list|
538
+ yield key_without_namespace(key), value_list
539
+ end
416
540
 
417
- if server.multi_response_completed?
418
- servers.delete(server)
419
- end
420
- rescue NetworkError
541
+ if server.multi_response_completed?
421
542
  servers.delete(server)
422
543
  end
544
+ rescue NetworkError
545
+ servers.each { |s| s.multi_response_abort unless s.sock.nil? }
546
+ raise
423
547
  end
424
548
  end
425
549
  end
@@ -1,5 +1,7 @@
1
- require 'zlib'
2
- require 'stringio'
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+ require "stringio"
3
5
 
4
6
  module Dalli
5
7
  class Compressor
@@ -14,7 +16,7 @@ module Dalli
14
16
 
15
17
  class GzipCompressor
16
18
  def self.compress(data)
17
- io = StringIO.new("w")
19
+ io = StringIO.new(+"", "w")
18
20
  gz = Zlib::GzipWriter.new(io)
19
21
  gz.write(data)
20
22
  gz.close
data/lib/dalli/options.rb CHANGED
@@ -1,12 +1,12 @@
1
- require 'thread'
2
- require 'monitor'
1
+ # frozen_string_literal: true
3
2
 
4
- module Dalli
3
+ require "monitor"
5
4
 
5
+ module Dalli
6
6
  # Make Dalli threadsafe by using a lock around all
7
7
  # public server methods.
8
8
  #
9
- # Dalli::Server.extend(Dalli::Threadsafe)
9
+ # Dalli::Protocol::Binary.extend(Dalli::Threadsafe)
10
10
  #
11
11
  module Threadsafe
12
12
  def self.extended(obj)