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