dalli 1.1.4 → 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 +7 -0
- data/Gemfile +8 -4
- data/History.md +267 -1
- data/LICENSE +1 -1
- data/README.md +129 -85
- data/lib/action_dispatch/middleware/session/dalli_store.rb +7 -1
- data/lib/active_support/cache/dalli_store.rb +361 -104
- data/lib/dalli.rb +9 -7
- data/lib/dalli/cas/client.rb +59 -0
- data/lib/dalli/client.rb +298 -103
- data/lib/dalli/compressor.rb +30 -0
- data/lib/dalli/options.rb +19 -0
- data/lib/dalli/railtie.rb +8 -0
- data/lib/dalli/ring.rb +59 -22
- data/lib/dalli/server.rb +346 -125
- data/lib/dalli/socket.rb +105 -105
- data/lib/dalli/version.rb +2 -1
- data/lib/rack/session/dalli.rb +156 -22
- metadata +29 -104
- data/Performance.md +0 -85
- data/Rakefile +0 -39
- data/Upgrade.md +0 -45
- data/dalli.gemspec +0 -31
- data/lib/active_support/cache/dalli_store23.rb +0 -172
- data/lib/dalli/compatibility.rb +0 -52
- data/lib/dalli/memcache-client.rb +0 -1
- data/test/abstract_unit.rb +0 -282
- data/test/benchmark_test.rb +0 -170
- data/test/helper.rb +0 -39
- data/test/memcached_mock.rb +0 -126
- data/test/test_active_support.rb +0 -201
- data/test/test_compatibility.rb +0 -33
- data/test/test_dalli.rb +0 -450
- data/test/test_encoding.rb +0 -43
- data/test/test_failover.rb +0 -107
- data/test/test_network.rb +0 -54
- data/test/test_ring.rb +0 -85
- data/test/test_sasl.rb +0 -83
- data/test/test_session_store.rb +0 -225
- data/test/test_synchrony.rb +0 -175
data/lib/dalli.rb
CHANGED
@@ -1,15 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'dalli/compressor'
|
1
3
|
require 'dalli/client'
|
2
4
|
require 'dalli/ring'
|
3
5
|
require 'dalli/server'
|
4
6
|
require 'dalli/socket'
|
5
7
|
require 'dalli/version'
|
6
8
|
require 'dalli/options'
|
7
|
-
|
8
|
-
unless ''.respond_to?(:bytesize)
|
9
|
-
class String
|
10
|
-
alias_method :bytesize, :size
|
11
|
-
end
|
12
|
-
end
|
9
|
+
require 'dalli/railtie' if defined?(::Rails::Railtie)
|
13
10
|
|
14
11
|
module Dalli
|
15
12
|
# generic error
|
@@ -18,8 +15,12 @@ module Dalli
|
|
18
15
|
class NetworkError < DalliError; end
|
19
16
|
# no server available/alive error
|
20
17
|
class RingError < DalliError; end
|
21
|
-
# application error in marshalling
|
18
|
+
# application error in marshalling serialization
|
22
19
|
class MarshalError < DalliError; end
|
20
|
+
# application error in marshalling deserialization or decompression
|
21
|
+
class UnmarshalError < DalliError; end
|
22
|
+
# payload too big for memcached
|
23
|
+
class ValueOverMaxSize < DalliError; end
|
23
24
|
|
24
25
|
def self.logger
|
25
26
|
@logger ||= (rails_logger || default_logger)
|
@@ -40,6 +41,7 @@ module Dalli
|
|
40
41
|
def self.logger=(logger)
|
41
42
|
@logger = logger
|
42
43
|
end
|
44
|
+
|
43
45
|
end
|
44
46
|
|
45
47
|
if defined?(RAILS_VERSION) && RAILS_VERSION < '3'
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'dalli/client'
|
3
|
+
|
4
|
+
module Dalli
|
5
|
+
class Client
|
6
|
+
##
|
7
|
+
# Get the value and CAS ID associated with the key. If a block is provided,
|
8
|
+
# value and CAS will be passed to the block.
|
9
|
+
def get_cas(key)
|
10
|
+
(value, cas) = perform(:cas, key)
|
11
|
+
value = (!value || value == 'Not found') ? nil : value
|
12
|
+
if block_given?
|
13
|
+
yield value, cas
|
14
|
+
else
|
15
|
+
[value, cas]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Fetch multiple keys efficiently, including available metadata such as CAS.
|
21
|
+
# If a block is given, yields key/data pairs one a time. Data is an array:
|
22
|
+
# [value, cas_id]
|
23
|
+
# If no block is given, returns a hash of
|
24
|
+
# { 'key' => [value, cas_id] }
|
25
|
+
def get_multi_cas(*keys)
|
26
|
+
if block_given?
|
27
|
+
get_multi_yielder(keys) {|*args| yield(*args)}
|
28
|
+
else
|
29
|
+
Hash.new.tap do |hash|
|
30
|
+
get_multi_yielder(keys) {|k, data| hash[k] = data}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Set the key-value pair, verifying existing CAS.
|
37
|
+
# Returns the resulting CAS value if succeeded, and falsy otherwise.
|
38
|
+
def set_cas(key, value, cas, ttl=nil, options=nil)
|
39
|
+
ttl ||= @options[:expires_in].to_i
|
40
|
+
perform(:set, key, value, ttl, cas, options)
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Conditionally add a key/value pair, verifying existing CAS, only if the
|
45
|
+
# key already exists on the server. Returns the new CAS value if the
|
46
|
+
# operation succeeded, or falsy otherwise.
|
47
|
+
def replace_cas(key, value, cas, ttl=nil, options=nil)
|
48
|
+
ttl ||= @options[:expires_in].to_i
|
49
|
+
perform(:replace, key, value, ttl, cas, options)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Delete a key/value pair, verifying existing CAS.
|
53
|
+
# Returns true if succeeded, and falsy otherwise.
|
54
|
+
def delete_cas(key, cas=0)
|
55
|
+
perform(:delete, key, cas)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
data/lib/dalli/client.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'digest/md5'
|
3
|
+
require 'set'
|
4
|
+
|
1
5
|
# encoding: ascii
|
2
6
|
module Dalli
|
3
7
|
class Client
|
@@ -6,48 +10,33 @@ module Dalli
|
|
6
10
|
# Dalli::Client is the main class which developers will use to interact with
|
7
11
|
# the memcached server. Usage:
|
8
12
|
#
|
9
|
-
# 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'],
|
10
14
|
# :threadsafe => true, :failover => true, :expires_in => 300)
|
11
15
|
#
|
12
16
|
# servers is an Array of "host:port:weight" where weight allows you to distribute cache unevenly.
|
13
|
-
# Both weight and port are optional. If you pass in nil, Dalli will
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
# Dalli::Client.new("/tmp/memcached.sock")
|
20
|
-
#
|
21
|
-
# Initial testing shows that Unix sockets are about twice as fast as TCP sockets
|
22
|
-
# but Unix sockets only work on localhost.
|
17
|
+
# Both weight and port are optional. If you pass in nil, Dalli will use the <tt>MEMCACHE_SERVERS</tt>
|
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.
|
23
22
|
#
|
24
23
|
# Options:
|
24
|
+
# - :namespace - prepend each key with this value to provide simple namespacing.
|
25
25
|
# - :failover - if a server is down, look for and store values on another server in the ring. Default: true.
|
26
26
|
# - :threadsafe - ensure that only one thread is actively using a socket at a time. Default: true.
|
27
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
|
-
# - :
|
29
|
-
#
|
30
|
-
# - :
|
28
|
+
# - :compress - defaults to false, if true Dalli will compress values larger than 1024 bytes before sending them to memcached.
|
29
|
+
# - :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.
|
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.
|
31
33
|
#
|
32
34
|
def initialize(servers=nil, options={})
|
33
|
-
@servers =
|
34
|
-
@options =
|
35
|
-
self.extend(Dalli::Client::MemcacheClientCompatibility) if Dalli::Client.compatibility_mode
|
35
|
+
@servers = normalize_servers(servers || ENV["MEMCACHE_SERVERS"] || '127.0.0.1:11211')
|
36
|
+
@options = normalize_options(options)
|
36
37
|
@ring = nil
|
37
38
|
end
|
38
39
|
|
39
|
-
##
|
40
|
-
# Turn on compatibility mode, which mixes in methods in memcache_client_compatibility.rb
|
41
|
-
# This value is set to true in memcache-client.rb.
|
42
|
-
def self.compatibility_mode
|
43
|
-
@compatibility_mode ||= false
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.compatibility_mode=(compatibility_mode)
|
47
|
-
require 'dalli/compatibility'
|
48
|
-
@compatibility_mode = compatibility_mode
|
49
|
-
end
|
50
|
-
|
51
40
|
#
|
52
41
|
# The standard memcached instruction set
|
53
42
|
#
|
@@ -64,50 +53,50 @@ module Dalli
|
|
64
53
|
Thread.current[:dalli_multi] = old
|
65
54
|
end
|
66
55
|
|
56
|
+
##
|
57
|
+
# Get the value associated with the key.
|
58
|
+
# If a value is not found, then +nil+ is returned.
|
67
59
|
def get(key, options=nil)
|
68
|
-
|
69
|
-
(!resp || resp == 'Not found') ? nil : resp
|
60
|
+
perform(:get, key, options)
|
70
61
|
end
|
71
62
|
|
72
63
|
##
|
73
64
|
# Fetch multiple keys efficiently.
|
74
|
-
#
|
65
|
+
# If a block is given, yields key/value pairs one at a time.
|
66
|
+
# Otherwise returns a hash of { 'key' => 'value', 'key2' => 'value1' }
|
75
67
|
def get_multi(*keys)
|
76
|
-
|
77
|
-
|
78
|
-
options = keys.pop if keys.last.is_a?(Hash) || keys.last.nil?
|
79
|
-
ring.lock do
|
80
|
-
keys.flatten.each do |key|
|
81
|
-
begin
|
82
|
-
perform(:getkq, key)
|
83
|
-
rescue DalliError, NetworkError => e
|
84
|
-
Dalli.logger.debug { e.message }
|
85
|
-
Dalli.logger.debug { "unable to get key #{key}" }
|
86
|
-
end
|
87
|
-
end
|
68
|
+
check_keys = keys.flatten
|
69
|
+
check_keys.compact!
|
88
70
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
end
|
96
|
-
rescue DalliError, NetworkError => e
|
97
|
-
Dalli.logger.debug { e.message }
|
98
|
-
Dalli.logger.debug { "results from this server will be missing" }
|
99
|
-
end
|
71
|
+
return {} if check_keys.empty?
|
72
|
+
if block_given?
|
73
|
+
get_multi_yielder(keys) {|k, data| yield k, data.first}
|
74
|
+
else
|
75
|
+
Hash.new.tap do |hash|
|
76
|
+
get_multi_yielder(keys) {|k, data| hash[k] = data.first}
|
100
77
|
end
|
101
|
-
values
|
102
78
|
end
|
103
79
|
end
|
104
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.
|
105
91
|
def fetch(key, ttl=nil, options=nil)
|
106
|
-
|
92
|
+
options = options.nil? ? CACHE_NILS : options.merge(CACHE_NILS) if @options[:cache_nils]
|
107
93
|
val = get(key, options)
|
108
|
-
|
94
|
+
not_found = @options[:cache_nils] ?
|
95
|
+
val == Dalli::Server::NOT_FOUND :
|
96
|
+
val.nil?
|
97
|
+
if not_found && block_given?
|
109
98
|
val = yield
|
110
|
-
add(key, val, ttl, options)
|
99
|
+
add(key, val, ttl_or_default(ttl), options)
|
111
100
|
end
|
112
101
|
val
|
113
102
|
end
|
@@ -124,39 +113,40 @@ module Dalli
|
|
124
113
|
# - false if the value was changed by someone else.
|
125
114
|
# - true if the value was successfully updated.
|
126
115
|
def cas(key, ttl=nil, options=nil, &block)
|
127
|
-
ttl
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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)
|
134
128
|
end
|
135
129
|
|
136
130
|
def set(key, value, ttl=nil, options=nil)
|
137
|
-
|
138
|
-
ttl ||= @options[:expires_in]
|
139
|
-
perform(:set, key, value, ttl, 0, options)
|
131
|
+
perform(:set, key, value, ttl_or_default(ttl), 0, options)
|
140
132
|
end
|
141
133
|
|
142
134
|
##
|
143
135
|
# Conditionally add a key/value pair, if the key does not already exist
|
144
|
-
# on the server. Returns
|
136
|
+
# on the server. Returns truthy if the operation succeeded.
|
145
137
|
def add(key, value, ttl=nil, options=nil)
|
146
|
-
ttl
|
147
|
-
perform(:add, key, value, ttl, options)
|
138
|
+
perform(:add, key, value, ttl_or_default(ttl), options)
|
148
139
|
end
|
149
140
|
|
150
141
|
##
|
151
142
|
# Conditionally add a key/value pair, only if the key already exists
|
152
|
-
# on the server. Returns
|
143
|
+
# on the server. Returns truthy if the operation succeeded.
|
153
144
|
def replace(key, value, ttl=nil, options=nil)
|
154
|
-
ttl
|
155
|
-
perform(:replace, key, value, ttl, options)
|
145
|
+
perform(:replace, key, value, ttl_or_default(ttl), 0, options)
|
156
146
|
end
|
157
147
|
|
158
148
|
def delete(key)
|
159
|
-
perform(:delete, key)
|
149
|
+
perform(:delete, key, 0)
|
160
150
|
end
|
161
151
|
|
162
152
|
##
|
@@ -178,12 +168,11 @@ module Dalli
|
|
178
168
|
ring.servers.map { |s| s.request(:flush, time += delay) }
|
179
169
|
end
|
180
170
|
|
181
|
-
# deprecated, please use #flush.
|
182
171
|
alias_method :flush_all, :flush
|
183
172
|
|
184
173
|
##
|
185
174
|
# Incr adds the given amount to the counter on the memcached server.
|
186
|
-
# Amt must be a positive value.
|
175
|
+
# Amt must be a positive integer value.
|
187
176
|
#
|
188
177
|
# If default is nil, the counter must already exist or the operation
|
189
178
|
# will fail and will return nil. Otherwise this method will return
|
@@ -194,13 +183,12 @@ module Dalli
|
|
194
183
|
# #cas.
|
195
184
|
def incr(key, amt=1, ttl=nil, default=nil)
|
196
185
|
raise ArgumentError, "Positive values only: #{amt}" if amt < 0
|
197
|
-
ttl
|
198
|
-
perform(:incr, key, amt, ttl, default)
|
186
|
+
perform(:incr, key, amt.to_i, ttl_or_default(ttl), default)
|
199
187
|
end
|
200
188
|
|
201
189
|
##
|
202
190
|
# Decr subtracts the given amount from the counter on the memcached server.
|
203
|
-
# Amt must be a positive value.
|
191
|
+
# Amt must be a positive integer value.
|
204
192
|
#
|
205
193
|
# memcached counters are unsigned and cannot hold negative values. Calling
|
206
194
|
# decr on a counter which is 0 will just return 0.
|
@@ -214,17 +202,51 @@ module Dalli
|
|
214
202
|
# #cas.
|
215
203
|
def decr(key, amt=1, ttl=nil, default=nil)
|
216
204
|
raise ArgumentError, "Positive values only: #{amt}" if amt < 0
|
217
|
-
ttl
|
218
|
-
|
205
|
+
perform(:decr, key, amt.to_i, ttl_or_default(ttl), default)
|
206
|
+
end
|
207
|
+
|
208
|
+
##
|
209
|
+
# Touch updates expiration time for a given key.
|
210
|
+
#
|
211
|
+
# Returns true if key exists, otherwise nil.
|
212
|
+
def touch(key, ttl=nil)
|
213
|
+
resp = perform(:touch, key, ttl_or_default(ttl))
|
214
|
+
resp.nil? ? nil : true
|
219
215
|
end
|
220
216
|
|
221
217
|
##
|
222
218
|
# Collect the stats for each server.
|
219
|
+
# You can optionally pass a type including :items, :slabs or :settings to get specific stats
|
223
220
|
# Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
|
224
|
-
def stats
|
221
|
+
def stats(type=nil)
|
222
|
+
type = nil if ![nil, :items,:slabs,:settings].include? type
|
225
223
|
values = {}
|
226
224
|
ring.servers.each do |server|
|
227
|
-
values["#{server.
|
225
|
+
values["#{server.name}"] = server.alive? ? server.request(:stats,type.to_s) : nil
|
226
|
+
end
|
227
|
+
values
|
228
|
+
end
|
229
|
+
|
230
|
+
##
|
231
|
+
# Reset stats for each server.
|
232
|
+
def reset_stats
|
233
|
+
ring.servers.map do |server|
|
234
|
+
server.alive? ? server.request(:reset_stats) : nil
|
235
|
+
end
|
236
|
+
end
|
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
|
+
|
244
|
+
##
|
245
|
+
## Version of the memcache servers.
|
246
|
+
def version
|
247
|
+
values = {}
|
248
|
+
ring.servers.each do |server|
|
249
|
+
values["#{server.name}"] = server.alive? ? server.request(:version) : nil
|
228
250
|
end
|
229
251
|
values
|
230
252
|
end
|
@@ -240,49 +262,222 @@ module Dalli
|
|
240
262
|
end
|
241
263
|
alias_method :reset, :close
|
242
264
|
|
265
|
+
# Stub method so a bare Dalli client can pretend to be a connection pool.
|
266
|
+
def with
|
267
|
+
yield self
|
268
|
+
end
|
269
|
+
|
243
270
|
private
|
244
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
|
+
|
286
|
+
def groups_for_keys(*keys)
|
287
|
+
groups = mapped_keys(keys).flatten.group_by do |key|
|
288
|
+
begin
|
289
|
+
ring.server_for_key(key)
|
290
|
+
rescue Dalli::RingError
|
291
|
+
Dalli.logger.debug { "unable to get key #{key}" }
|
292
|
+
nil
|
293
|
+
end
|
294
|
+
end
|
295
|
+
return groups
|
296
|
+
end
|
297
|
+
|
298
|
+
def mapped_keys(keys)
|
299
|
+
keys_array = keys.flatten
|
300
|
+
keys_array.map! { |a| validate_key(a.to_s) }
|
301
|
+
keys_array
|
302
|
+
end
|
303
|
+
|
304
|
+
def make_multi_get_requests(groups)
|
305
|
+
groups.each do |server, keys_for_server|
|
306
|
+
begin
|
307
|
+
# TODO: do this with the perform chokepoint?
|
308
|
+
# But given the fact that fetching the response doesn't take place
|
309
|
+
# in that slot it's misleading anyway. Need to move all of this method
|
310
|
+
# into perform to be meaningful
|
311
|
+
server.request(:send_multiget, keys_for_server)
|
312
|
+
rescue DalliError, NetworkError => e
|
313
|
+
Dalli.logger.debug { e.inspect }
|
314
|
+
Dalli.logger.debug { "unable to get keys for server #{server.name}" }
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def perform_multi_response_start(servers)
|
320
|
+
servers.each do |server|
|
321
|
+
next unless server.alive?
|
322
|
+
begin
|
323
|
+
server.multi_response_start
|
324
|
+
rescue DalliError, NetworkError => e
|
325
|
+
Dalli.logger.debug { e.inspect }
|
326
|
+
Dalli.logger.debug { "results from this server will be missing" }
|
327
|
+
servers.delete(server)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
servers
|
331
|
+
end
|
332
|
+
|
333
|
+
##
|
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"
|
337
|
+
def normalize_servers(servers)
|
338
|
+
Array(servers).flat_map do |server|
|
339
|
+
if server.is_a? String
|
340
|
+
server.split(",")
|
341
|
+
else
|
342
|
+
server
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
245
347
|
def ring
|
246
348
|
@ring ||= Dalli::Ring.new(
|
247
|
-
|
248
|
-
|
349
|
+
@servers.map do |s|
|
350
|
+
server_options = {}
|
351
|
+
if s =~ %r{\Amemcached://}
|
352
|
+
uri = URI.parse(s)
|
353
|
+
server_options[:username] = uri.user
|
354
|
+
server_options[:password] = uri.password
|
355
|
+
s = "#{uri.host}:#{uri.port}"
|
356
|
+
end
|
357
|
+
Dalli::Server.new(s, @options.merge(server_options))
|
249
358
|
end, @options
|
250
359
|
)
|
251
360
|
end
|
252
361
|
|
253
|
-
def env_servers
|
254
|
-
ENV['MEMCACHE_SERVERS'] ? ENV['MEMCACHE_SERVERS'].split(',') : nil
|
255
|
-
end
|
256
|
-
|
257
362
|
# Chokepoint method for instrumentation
|
258
|
-
def perform(
|
363
|
+
def perform(*all_args)
|
364
|
+
return yield if block_given?
|
365
|
+
op, key, *args = *all_args
|
366
|
+
|
259
367
|
key = key.to_s
|
260
|
-
validate_key(key)
|
261
|
-
key = key_with_namespace(key)
|
368
|
+
key = validate_key(key)
|
262
369
|
begin
|
263
370
|
server = ring.server_for_key(key)
|
264
|
-
server.request(op, key, *args)
|
371
|
+
ret = server.request(op, key, *args)
|
372
|
+
ret
|
265
373
|
rescue NetworkError => e
|
266
|
-
Dalli.logger.debug { e.
|
374
|
+
Dalli.logger.debug { e.inspect }
|
267
375
|
Dalli.logger.debug { "retrying request with new server" }
|
268
376
|
retry
|
269
377
|
end
|
270
378
|
end
|
271
379
|
|
272
380
|
def validate_key(key)
|
273
|
-
raise ArgumentError, "
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
381
|
+
raise ArgumentError, "key cannot be blank" if !key || key.length == 0
|
382
|
+
key = key_with_namespace(key)
|
383
|
+
if key.length > 250
|
384
|
+
digest_class = @options[:digest_class] || ::Digest::MD5
|
385
|
+
max_length_before_namespace = 212 - (namespace || '').size
|
386
|
+
key = "#{key[0, max_length_before_namespace]}:md5:#{digest_class.hexdigest(key)}"
|
387
|
+
end
|
388
|
+
return key
|
278
389
|
end
|
279
390
|
|
280
391
|
def key_with_namespace(key)
|
281
|
-
|
392
|
+
(ns = namespace) ? "#{ns}:#{key}" : key
|
282
393
|
end
|
283
394
|
|
284
395
|
def key_without_namespace(key)
|
285
|
-
|
396
|
+
(ns = namespace) ? key.sub(%r(\A#{Regexp.escape ns}:), '') : key
|
286
397
|
end
|
398
|
+
|
399
|
+
def namespace
|
400
|
+
return nil unless @options[:namespace]
|
401
|
+
@options[:namespace].is_a?(Proc) ? @options[:namespace].call.to_s : @options[:namespace].to_s
|
402
|
+
end
|
403
|
+
|
404
|
+
def normalize_options(opts)
|
405
|
+
if opts[:compression]
|
406
|
+
Dalli.logger.warn "DEPRECATED: Dalli's :compression option is now just :compress => true. Please update your configuration."
|
407
|
+
opts[:compress] = opts.delete(:compression)
|
408
|
+
end
|
409
|
+
begin
|
410
|
+
opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in]
|
411
|
+
rescue NoMethodError
|
412
|
+
raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
|
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
|
417
|
+
opts
|
418
|
+
end
|
419
|
+
|
420
|
+
##
|
421
|
+
# Yields, one at a time, keys and their values+attributes.
|
422
|
+
def get_multi_yielder(keys)
|
423
|
+
perform do
|
424
|
+
return {} if keys.empty?
|
425
|
+
ring.lock do
|
426
|
+
begin
|
427
|
+
groups = groups_for_keys(keys)
|
428
|
+
if unfound_keys = groups.delete(nil)
|
429
|
+
Dalli.logger.debug { "unable to get keys for #{unfound_keys.length} keys because no matching server was found" }
|
430
|
+
end
|
431
|
+
make_multi_get_requests(groups)
|
432
|
+
|
433
|
+
servers = groups.keys
|
434
|
+
return if servers.empty?
|
435
|
+
servers = perform_multi_response_start(servers)
|
436
|
+
|
437
|
+
start = Time.now
|
438
|
+
while true
|
439
|
+
# remove any dead servers
|
440
|
+
servers.delete_if { |s| s.sock.nil? }
|
441
|
+
break if servers.empty?
|
442
|
+
|
443
|
+
# calculate remaining timeout
|
444
|
+
elapsed = Time.now - start
|
445
|
+
timeout = servers.first.options[:socket_timeout]
|
446
|
+
time_left = (elapsed > timeout) ? 0 : timeout - elapsed
|
447
|
+
|
448
|
+
sockets = servers.map(&:sock)
|
449
|
+
readable, _ = IO.select(sockets, nil, nil, time_left)
|
450
|
+
|
451
|
+
if readable.nil?
|
452
|
+
# no response within timeout; abort pending connections
|
453
|
+
servers.each do |server|
|
454
|
+
Dalli.logger.debug { "memcached at #{server.name} did not response within timeout" }
|
455
|
+
server.multi_response_abort
|
456
|
+
end
|
457
|
+
break
|
458
|
+
|
459
|
+
else
|
460
|
+
readable.each do |sock|
|
461
|
+
server = sock.server
|
462
|
+
|
463
|
+
begin
|
464
|
+
server.multi_response_nonblock.each_pair do |key, value_list|
|
465
|
+
yield key_without_namespace(key), value_list
|
466
|
+
end
|
467
|
+
|
468
|
+
if server.multi_response_completed?
|
469
|
+
servers.delete(server)
|
470
|
+
end
|
471
|
+
rescue NetworkError
|
472
|
+
servers.delete(server)
|
473
|
+
end
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
end
|
478
|
+
end
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
287
482
|
end
|
288
483
|
end
|