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.
- checksums.yaml +5 -5
- data/Gemfile +5 -7
- data/History.md +151 -0
- data/LICENSE +1 -1
- data/README.md +56 -102
- data/lib/dalli/cas/client.rb +1 -58
- data/lib/dalli/client.rb +263 -139
- data/lib/dalli/compressor.rb +5 -3
- data/lib/dalli/options.rb +4 -4
- data/lib/dalli/protocol/binary.rb +703 -0
- data/lib/dalli/protocol/server_config_parser.rb +67 -0
- data/lib/dalli/protocol/ttl_sanitizer.rb +45 -0
- data/lib/dalli/protocol/value_compressor.rb +85 -0
- data/lib/dalli/protocol.rb +9 -0
- data/lib/dalli/ring.rb +17 -68
- data/lib/dalli/server.rb +3 -689
- data/lib/dalli/socket.rb +79 -83
- data/lib/dalli/version.rb +3 -1
- data/lib/dalli.rb +20 -16
- data/lib/rack/session/dalli.rb +124 -30
- metadata +33 -77
- 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 -361
- 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 -427
- data/test/test_cas_client.rb +0 -107
- data/test/test_compressor.rb +0 -53
- data/test/test_dalli.rb +0 -601
- 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 -321
- data/test/test_ring.rb +0 -85
- data/test/test_sasl.rb +0 -110
- data/test/test_serializer.rb +0 -30
data/lib/dalli/client.rb
CHANGED
@@ -1,32 +1,41 @@
|
|
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.
|
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 -
|
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
|
-
|
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
|
-
|
53
|
-
|
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
|
-
|
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
|
-
|
71
|
-
|
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
|
-
|
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
|
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
|
-
|
102
|
-
|
103
|
-
|
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
|
109
|
-
def add(key, value, ttl=nil, options=nil)
|
110
|
-
ttl
|
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
|
117
|
-
def replace(key, value, ttl=nil, options=nil)
|
118
|
-
ttl
|
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
|
-
|
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
|
-
|
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
|
-
|
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 :
|
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
|
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[
|
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[
|
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
|
-
|
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
|
-
|
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
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
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
|
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
|
-
|
390
|
+
deleted.append(server)
|
283
391
|
end
|
284
392
|
end
|
285
|
-
|
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.
|
290
|
-
# the
|
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
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
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
|
302
|
-
|
303
|
-
if s
|
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::
|
310
|
-
|
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
|
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
|
-
|
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
|
-
|
337
|
-
|
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
|
-
|
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
|
470
|
+
(ns = namespace) ? key.sub(%r{\A#{Regexp.escape ns}:}, "") : key
|
348
471
|
end
|
349
472
|
|
350
473
|
def namespace
|
351
|
-
|
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
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
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
|
-
|
401
|
-
|
402
|
-
|
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
|
-
|
409
|
-
|
410
|
-
|
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
|
-
|
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
|
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)
|