dalli 2.7.0 → 2.7.11
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 +6 -3
- data/History.md +94 -0
- data/LICENSE +1 -1
- data/README.md +62 -43
- data/lib/action_dispatch/middleware/session/dalli_store.rb +2 -1
- data/lib/active_support/cache/dalli_store.rb +121 -41
- data/lib/dalli/cas/client.rb +5 -4
- data/lib/dalli/client.rb +103 -52
- data/lib/dalli/compressor.rb +2 -1
- data/lib/dalli/options.rb +1 -0
- data/lib/dalli/railtie.rb +1 -0
- data/lib/dalli/ring.rb +6 -6
- data/lib/dalli/server.rb +151 -91
- data/lib/dalli/socket.rb +104 -42
- data/lib/dalli/version.rb +2 -1
- data/lib/dalli.rb +3 -0
- data/lib/rack/session/dalli.rb +140 -27
- metadata +22 -95
- data/Performance.md +0 -42
- data/Rakefile +0 -42
- data/dalli.gemspec +0 -29
- 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,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'digest/md5'
|
2
3
|
require 'set'
|
3
4
|
|
@@ -9,12 +10,15 @@ module Dalli
|
|
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,6 +28,8 @@ 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.
|
27
33
|
#
|
28
34
|
def initialize(servers=nil, options={})
|
29
35
|
@servers = normalize_servers(servers || ENV["MEMCACHE_SERVERS"] || '127.0.0.1:11211')
|
@@ -49,8 +55,9 @@ module Dalli
|
|
49
55
|
|
50
56
|
##
|
51
57
|
# Get the value associated with the key.
|
58
|
+
# If a value is not found, then +nil+ is returned.
|
52
59
|
def get(key, options=nil)
|
53
|
-
perform(:get, key)
|
60
|
+
perform(:get, key, options)
|
54
61
|
end
|
55
62
|
|
56
63
|
##
|
@@ -58,21 +65,38 @@ module Dalli
|
|
58
65
|
# If a block is given, yields key/value pairs one at a time.
|
59
66
|
# Otherwise returns a hash of { 'key' => 'value', 'key2' => 'value1' }
|
60
67
|
def get_multi(*keys)
|
68
|
+
check_keys = keys.flatten
|
69
|
+
check_keys.compact!
|
70
|
+
|
71
|
+
return {} if check_keys.empty?
|
61
72
|
if block_given?
|
62
73
|
get_multi_yielder(keys) {|k, data| yield k, data.first}
|
63
74
|
else
|
64
|
-
|
75
|
+
Hash.new.tap do |hash|
|
65
76
|
get_multi_yielder(keys) {|k, data| hash[k] = data.first}
|
66
77
|
end
|
67
78
|
end
|
68
79
|
end
|
69
80
|
|
81
|
+
CACHE_NILS = {cache_nils: true}.freeze
|
82
|
+
|
83
|
+
# Fetch the value associated with the key.
|
84
|
+
# If a value is found, then it is returned.
|
85
|
+
#
|
86
|
+
# If a value is not found and no block is given, then nil is returned.
|
87
|
+
#
|
88
|
+
# If a value is not found (or if the found value is nil and :cache_nils is false)
|
89
|
+
# and a block is given, the block will be invoked and its return value
|
90
|
+
# written to the cache and returned.
|
70
91
|
def fetch(key, ttl=nil, options=nil)
|
71
|
-
|
92
|
+
options = options.nil? ? CACHE_NILS : options.merge(CACHE_NILS) if @options[:cache_nils]
|
72
93
|
val = get(key, options)
|
73
|
-
|
94
|
+
not_found = @options[:cache_nils] ?
|
95
|
+
val == Dalli::Server::NOT_FOUND :
|
96
|
+
val.nil?
|
97
|
+
if not_found && block_given?
|
74
98
|
val = yield
|
75
|
-
add(key, val, ttl, options)
|
99
|
+
add(key, val, ttl_or_default(ttl), options)
|
76
100
|
end
|
77
101
|
val
|
78
102
|
end
|
@@ -89,34 +113,36 @@ module Dalli
|
|
89
113
|
# - false if the value was changed by someone else.
|
90
114
|
# - true if the value was successfully updated.
|
91
115
|
def cas(key, ttl=nil, options=nil, &block)
|
92
|
-
ttl
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
116
|
+
cas_core(key, false, ttl, options, &block)
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
# like #cas, but will yield to the block whether or not the value
|
121
|
+
# already exists.
|
122
|
+
#
|
123
|
+
# Returns:
|
124
|
+
# - false if the value was changed by someone else.
|
125
|
+
# - true if the value was successfully updated.
|
126
|
+
def cas!(key, ttl=nil, options=nil, &block)
|
127
|
+
cas_core(key, true, ttl, options, &block)
|
99
128
|
end
|
100
129
|
|
101
130
|
def set(key, value, ttl=nil, options=nil)
|
102
|
-
ttl
|
103
|
-
perform(:set, key, value, ttl, 0, options)
|
131
|
+
perform(:set, key, value, ttl_or_default(ttl), 0, options)
|
104
132
|
end
|
105
133
|
|
106
134
|
##
|
107
135
|
# Conditionally add a key/value pair, if the key does not already exist
|
108
|
-
# on the server. Returns
|
136
|
+
# on the server. Returns truthy if the operation succeeded.
|
109
137
|
def add(key, value, ttl=nil, options=nil)
|
110
|
-
ttl
|
111
|
-
perform(:add, key, value, ttl, options)
|
138
|
+
perform(:add, key, value, ttl_or_default(ttl), options)
|
112
139
|
end
|
113
140
|
|
114
141
|
##
|
115
142
|
# Conditionally add a key/value pair, only if the key already exists
|
116
|
-
# on the server. Returns
|
143
|
+
# on the server. Returns truthy if the operation succeeded.
|
117
144
|
def replace(key, value, ttl=nil, options=nil)
|
118
|
-
ttl
|
119
|
-
perform(:replace, key, value, ttl, 0, options)
|
145
|
+
perform(:replace, key, value, ttl_or_default(ttl), 0, options)
|
120
146
|
end
|
121
147
|
|
122
148
|
def delete(key)
|
@@ -157,8 +183,7 @@ module Dalli
|
|
157
183
|
# #cas.
|
158
184
|
def incr(key, amt=1, ttl=nil, default=nil)
|
159
185
|
raise ArgumentError, "Positive values only: #{amt}" if amt < 0
|
160
|
-
|
161
|
-
perform(:incr, key, amt.to_i, ttl, default)
|
186
|
+
perform(:incr, key, amt.to_i, ttl_or_default(ttl), default)
|
162
187
|
end
|
163
188
|
|
164
189
|
##
|
@@ -177,8 +202,7 @@ module Dalli
|
|
177
202
|
# #cas.
|
178
203
|
def decr(key, amt=1, ttl=nil, default=nil)
|
179
204
|
raise ArgumentError, "Positive values only: #{amt}" if amt < 0
|
180
|
-
|
181
|
-
perform(:decr, key, amt.to_i, ttl, default)
|
205
|
+
perform(:decr, key, amt.to_i, ttl_or_default(ttl), default)
|
182
206
|
end
|
183
207
|
|
184
208
|
##
|
@@ -186,20 +210,19 @@ module Dalli
|
|
186
210
|
#
|
187
211
|
# Returns true if key exists, otherwise nil.
|
188
212
|
def touch(key, ttl=nil)
|
189
|
-
|
190
|
-
resp = perform(:touch, key, ttl)
|
213
|
+
resp = perform(:touch, key, ttl_or_default(ttl))
|
191
214
|
resp.nil? ? nil : true
|
192
215
|
end
|
193
216
|
|
194
217
|
##
|
195
218
|
# Collect the stats for each server.
|
196
|
-
# You can optionally pass a type including :items or :
|
219
|
+
# You can optionally pass a type including :items, :slabs or :settings to get specific stats
|
197
220
|
# Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
|
198
221
|
def stats(type=nil)
|
199
|
-
type = nil if ![nil, :items,:slabs].include? type
|
222
|
+
type = nil if ![nil, :items,:slabs,:settings].include? type
|
200
223
|
values = {}
|
201
224
|
ring.servers.each do |server|
|
202
|
-
values["#{server.
|
225
|
+
values["#{server.name}"] = server.alive? ? server.request(:stats,type.to_s) : nil
|
203
226
|
end
|
204
227
|
values
|
205
228
|
end
|
@@ -212,12 +235,18 @@ module Dalli
|
|
212
235
|
end
|
213
236
|
end
|
214
237
|
|
238
|
+
##
|
239
|
+
## Make sure memcache servers are alive, or raise an Dalli::RingError
|
240
|
+
def alive!
|
241
|
+
ring.server_for_key("")
|
242
|
+
end
|
243
|
+
|
215
244
|
##
|
216
245
|
## Version of the memcache servers.
|
217
246
|
def version
|
218
247
|
values = {}
|
219
248
|
ring.servers.each do |server|
|
220
|
-
values["#{server.
|
249
|
+
values["#{server.name}"] = server.alive? ? server.request(:version) : nil
|
221
250
|
end
|
222
251
|
values
|
223
252
|
end
|
@@ -240,6 +269,20 @@ module Dalli
|
|
240
269
|
|
241
270
|
private
|
242
271
|
|
272
|
+
def cas_core(key, always_set, ttl=nil, options=nil)
|
273
|
+
(value, cas) = perform(:cas, key)
|
274
|
+
value = (!value || value == 'Not found') ? nil : value
|
275
|
+
return if value.nil? && !always_set
|
276
|
+
newvalue = yield(value)
|
277
|
+
perform(:set, key, newvalue, ttl_or_default(ttl), cas, options)
|
278
|
+
end
|
279
|
+
|
280
|
+
def ttl_or_default(ttl)
|
281
|
+
(ttl || @options[:expires_in]).to_i
|
282
|
+
rescue NoMethodError
|
283
|
+
raise ArgumentError, "Cannot convert ttl (#{ttl}) to an integer"
|
284
|
+
end
|
285
|
+
|
243
286
|
def groups_for_keys(*keys)
|
244
287
|
groups = mapped_keys(keys).flatten.group_by do |key|
|
245
288
|
begin
|
@@ -253,7 +296,9 @@ module Dalli
|
|
253
296
|
end
|
254
297
|
|
255
298
|
def mapped_keys(keys)
|
256
|
-
keys.flatten
|
299
|
+
keys_array = keys.flatten
|
300
|
+
keys_array.map! { |a| validate_key(a.to_s) }
|
301
|
+
keys_array
|
257
302
|
end
|
258
303
|
|
259
304
|
def make_multi_get_requests(groups)
|
@@ -266,7 +311,7 @@ module Dalli
|
|
266
311
|
server.request(:send_multiget, keys_for_server)
|
267
312
|
rescue DalliError, NetworkError => e
|
268
313
|
Dalli.logger.debug { e.inspect }
|
269
|
-
Dalli.logger.debug { "unable to get keys for server #{server.
|
314
|
+
Dalli.logger.debug { "unable to get keys for server #{server.name}" }
|
270
315
|
end
|
271
316
|
end
|
272
317
|
end
|
@@ -286,13 +331,16 @@ module Dalli
|
|
286
331
|
end
|
287
332
|
|
288
333
|
##
|
289
|
-
# Normalizes the argument into an array of servers.
|
290
|
-
# the
|
334
|
+
# Normalizes the argument into an array of servers.
|
335
|
+
# If the argument is a string, or an array containing strings, it's expected that the URIs are comma separated e.g.
|
336
|
+
# "memcache1.example.com:11211,memcache2.example.com:11211,memcache3.example.com:11211"
|
291
337
|
def normalize_servers(servers)
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
338
|
+
Array(servers).flat_map do |server|
|
339
|
+
if server.is_a? String
|
340
|
+
server.split(",")
|
341
|
+
else
|
342
|
+
server
|
343
|
+
end
|
296
344
|
end
|
297
345
|
end
|
298
346
|
|
@@ -312,8 +360,8 @@ module Dalli
|
|
312
360
|
end
|
313
361
|
|
314
362
|
# Chokepoint method for instrumentation
|
315
|
-
def perform(*all_args
|
316
|
-
return
|
363
|
+
def perform(*all_args)
|
364
|
+
return yield if block_given?
|
317
365
|
op, key, *args = *all_args
|
318
366
|
|
319
367
|
key = key.to_s
|
@@ -333,8 +381,9 @@ module Dalli
|
|
333
381
|
raise ArgumentError, "key cannot be blank" if !key || key.length == 0
|
334
382
|
key = key_with_namespace(key)
|
335
383
|
if key.length > 250
|
384
|
+
digest_class = @options[:digest_class] || ::Digest::MD5
|
336
385
|
max_length_before_namespace = 212 - (namespace || '').size
|
337
|
-
key = "#{key[0, max_length_before_namespace]}:md5:#{
|
386
|
+
key = "#{key[0, max_length_before_namespace]}:md5:#{digest_class.hexdigest(key)}"
|
338
387
|
end
|
339
388
|
return key
|
340
389
|
end
|
@@ -344,11 +393,12 @@ module Dalli
|
|
344
393
|
end
|
345
394
|
|
346
395
|
def key_without_namespace(key)
|
347
|
-
(ns = namespace) ? key.sub(%r(\A#{ns}:), '') : key
|
396
|
+
(ns = namespace) ? key.sub(%r(\A#{Regexp.escape ns}:), '') : key
|
348
397
|
end
|
349
398
|
|
350
399
|
def namespace
|
351
|
-
|
400
|
+
return nil unless @options[:namespace]
|
401
|
+
@options[:namespace].is_a?(Proc) ? @options[:namespace].call.to_s : @options[:namespace].to_s
|
352
402
|
end
|
353
403
|
|
354
404
|
def normalize_options(opts)
|
@@ -361,6 +411,9 @@ module Dalli
|
|
361
411
|
rescue NoMethodError
|
362
412
|
raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
|
363
413
|
end
|
414
|
+
if opts[:digest_class] && !opts[:digest_class].respond_to?(:hexdigest)
|
415
|
+
raise ArgumentError, "The digest_class object must respond to the hexdigest method"
|
416
|
+
end
|
364
417
|
opts
|
365
418
|
end
|
366
419
|
|
@@ -382,7 +435,7 @@ module Dalli
|
|
382
435
|
servers = perform_multi_response_start(servers)
|
383
436
|
|
384
437
|
start = Time.now
|
385
|
-
|
438
|
+
while true
|
386
439
|
# remove any dead servers
|
387
440
|
servers.delete_if { |s| s.sock.nil? }
|
388
441
|
break if servers.empty?
|
@@ -390,12 +443,10 @@ module Dalli
|
|
390
443
|
# calculate remaining timeout
|
391
444
|
elapsed = Time.now - start
|
392
445
|
timeout = servers.first.options[:socket_timeout]
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
readable, _ = IO.select(sockets, nil, nil, timeout - elapsed)
|
398
|
-
end
|
446
|
+
time_left = (elapsed > timeout) ? 0 : timeout - elapsed
|
447
|
+
|
448
|
+
sockets = servers.map(&:sock)
|
449
|
+
readable, _ = IO.select(sockets, nil, nil, time_left)
|
399
450
|
|
400
451
|
if readable.nil?
|
401
452
|
# no response within timeout; abort pending connections
|
data/lib/dalli/compressor.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'zlib'
|
2
3
|
require 'stringio'
|
3
4
|
|
@@ -14,7 +15,7 @@ module Dalli
|
|
14
15
|
|
15
16
|
class GzipCompressor
|
16
17
|
def self.compress(data)
|
17
|
-
io = StringIO.new("w")
|
18
|
+
io = StringIO.new(String.new(""), "w")
|
18
19
|
gz = Zlib::GzipWriter.new(io)
|
19
20
|
gz.write(data)
|
20
21
|
gz.close
|
data/lib/dalli/options.rb
CHANGED
data/lib/dalli/railtie.rb
CHANGED
data/lib/dalli/ring.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'digest/sha1'
|
2
3
|
require 'zlib'
|
3
4
|
|
@@ -15,12 +16,12 @@ module Dalli
|
|
15
16
|
continuum = []
|
16
17
|
servers.each do |server|
|
17
18
|
entry_count_for(server, servers.size, total_weight).times do |idx|
|
18
|
-
hash = Digest::SHA1.hexdigest("#{server.
|
19
|
+
hash = Digest::SHA1.hexdigest("#{server.name}:#{idx}")
|
19
20
|
value = Integer("0x#{hash[0..7]}")
|
20
21
|
continuum << Dalli::Ring::Entry.new(value, server)
|
21
22
|
end
|
22
23
|
end
|
23
|
-
@continuum = continuum.
|
24
|
+
@continuum = continuum.sort_by(&:value)
|
24
25
|
end
|
25
26
|
|
26
27
|
threadsafe! unless options[:threadsafe] == false
|
@@ -46,11 +47,11 @@ module Dalli
|
|
46
47
|
end
|
47
48
|
|
48
49
|
def lock
|
49
|
-
@servers.each
|
50
|
+
@servers.each(&:lock!)
|
50
51
|
begin
|
51
52
|
return yield
|
52
53
|
ensure
|
53
|
-
@servers.each
|
54
|
+
@servers.each(&:unlock!)
|
54
55
|
end
|
55
56
|
end
|
56
57
|
|
@@ -110,7 +111,6 @@ module Dalli
|
|
110
111
|
def binary_search(ary, value)
|
111
112
|
upper = ary.size - 1
|
112
113
|
lower = 0
|
113
|
-
idx = 0
|
114
114
|
|
115
115
|
while (lower <= upper) do
|
116
116
|
idx = (lower + upper) / 2
|
@@ -124,7 +124,7 @@ module Dalli
|
|
124
124
|
lower = idx + 1
|
125
125
|
end
|
126
126
|
end
|
127
|
-
|
127
|
+
upper
|
128
128
|
end
|
129
129
|
end
|
130
130
|
|