dalli 3.0.4 → 3.1.1
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 +4 -4
- data/Gemfile +11 -5
- data/History.md +31 -0
- data/README.md +25 -134
- data/lib/dalli/cas/client.rb +2 -0
- data/lib/dalli/client.rb +215 -323
- data/lib/dalli/compressor.rb +13 -4
- data/lib/dalli/key_manager.rb +113 -0
- data/lib/dalli/options.rb +5 -5
- data/lib/dalli/pipelined_getter.rb +177 -0
- data/lib/dalli/protocol/binary/request_formatter.rb +117 -0
- data/lib/dalli/protocol/binary/response_header.rb +36 -0
- data/lib/dalli/protocol/binary/response_processor.rb +200 -0
- data/lib/dalli/protocol/binary/sasl_authentication.rb +60 -0
- data/lib/dalli/protocol/binary.rb +251 -561
- data/lib/dalli/protocol/connection_manager.rb +242 -0
- data/lib/dalli/protocol/response_buffer.rb +53 -0
- data/lib/dalli/protocol/server_config_parser.rb +22 -5
- data/lib/dalli/protocol/value_marshaller.rb +59 -0
- data/lib/dalli/protocol/value_serializer.rb +91 -0
- data/lib/dalli/protocol.rb +2 -3
- data/lib/dalli/ring.rb +95 -35
- data/lib/dalli/server.rb +2 -2
- data/lib/dalli/servers_arg_normalizer.rb +54 -0
- data/lib/dalli/socket.rb +101 -55
- data/lib/dalli/version.rb +3 -1
- data/lib/dalli.rb +39 -14
- data/lib/rack/session/dalli.rb +95 -76
- metadata +80 -6
data/lib/dalli/client.rb
CHANGED
@@ -1,17 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'digest/md5'
|
4
|
+
require 'set'
|
5
5
|
|
6
6
|
# encoding: ascii
|
7
7
|
module Dalli
|
8
|
+
##
|
9
|
+
# Dalli::Client is the main class which developers will use to interact with
|
10
|
+
# Memcached.
|
11
|
+
##
|
8
12
|
class Client
|
9
13
|
##
|
10
14
|
# Dalli::Client is the main class which developers will use to interact with
|
11
15
|
# the memcached server. Usage:
|
12
16
|
#
|
13
|
-
# Dalli::Client.new(['localhost:11211:10',
|
14
|
-
#
|
17
|
+
# Dalli::Client.new(['localhost:11211:10',
|
18
|
+
# 'cache-2.example.com:11211:5',
|
19
|
+
# '192.168.0.1:22122:5',
|
20
|
+
# '/var/run/memcached/socket'],
|
21
|
+
# failover: true, expires_in: 300)
|
15
22
|
#
|
16
23
|
# servers is an Array of "host:port:weight" where weight allows you to distribute cache unevenly.
|
17
24
|
# Both weight and port are optional. If you pass in nil, Dalli will use the <tt>MEMCACHE_SERVERS</tt>
|
@@ -24,19 +31,25 @@ module Dalli
|
|
24
31
|
# - :namespace - prepend each key with this value to provide simple namespacing.
|
25
32
|
# - :failover - if a server is down, look for and store values on another server in the ring. Default: true.
|
26
33
|
# - :threadsafe - ensure that only one thread is actively using a socket at a time. Default: true.
|
27
|
-
# - :expires_in - default TTL in seconds if you do not pass TTL as a parameter to an individual operation, defaults
|
28
|
-
#
|
29
|
-
# - :
|
34
|
+
# - :expires_in - default TTL in seconds if you do not pass TTL as a parameter to an individual operation, defaults
|
35
|
+
# to 0 or forever.
|
36
|
+
# - :compress - if true Dalli will compress values larger than compression_min_size bytes before sending them
|
37
|
+
# to memcached. Default: true.
|
38
|
+
# - :compression_min_size - the minimum size (in bytes) for which Dalli will compress values sent to Memcached.
|
39
|
+
# Defaults to 4K.
|
30
40
|
# - :serializer - defaults to Marshal
|
31
|
-
# - :compressor - defaults to
|
32
|
-
# - :cache_nils - defaults to false, if true Dalli will not treat cached nil values as 'not found' for
|
33
|
-
#
|
34
|
-
# - :
|
41
|
+
# - :compressor - defaults to Dalli::Compressor, a Zlib-based implementation
|
42
|
+
# - :cache_nils - defaults to false, if true Dalli will not treat cached nil values as 'not found' for
|
43
|
+
# #fetch operations.
|
44
|
+
# - :digest_class - defaults to Digest::MD5, allows you to pass in an object that responds to the hexdigest method,
|
45
|
+
# useful for injecting a FIPS compliant hash object.
|
46
|
+
# - :protocol_implementation - defaults to Dalli::Protocol::Binary which uses the binary protocol. Allows you to
|
47
|
+
# pass an alternative implementation using another protocol.
|
35
48
|
#
|
36
49
|
def initialize(servers = nil, options = {})
|
37
|
-
|
38
|
-
@servers = normalize_servers(servers || ENV["MEMCACHE_SERVERS"] || "127.0.0.1:11211")
|
50
|
+
@servers = ::Dalli::ServersArgNormalizer.normalize_servers(servers)
|
39
51
|
@options = normalize_options(options)
|
52
|
+
@key_manager = ::Dalli::KeyManager.new(@options)
|
40
53
|
@ring = nil
|
41
54
|
end
|
42
55
|
|
@@ -45,22 +58,39 @@ module Dalli
|
|
45
58
|
#
|
46
59
|
|
47
60
|
##
|
48
|
-
#
|
49
|
-
#
|
50
|
-
|
51
|
-
|
52
|
-
def multi
|
53
|
-
old, Thread.current[:dalli_multi] = Thread.current[:dalli_multi], true
|
54
|
-
yield
|
55
|
-
ensure
|
56
|
-
Thread.current[:dalli_multi] = old
|
61
|
+
# Get the value associated with the key.
|
62
|
+
# If a value is not found, then +nil+ is returned.
|
63
|
+
def get(key, req_options = nil)
|
64
|
+
perform(:get, key, req_options)
|
57
65
|
end
|
58
66
|
|
59
67
|
##
|
60
|
-
#
|
68
|
+
# Gat (get and touch) fetch an item and simultaneously update its expiration time.
|
69
|
+
#
|
61
70
|
# If a value is not found, then +nil+ is returned.
|
62
|
-
def
|
63
|
-
perform(:
|
71
|
+
def gat(key, ttl = nil)
|
72
|
+
perform(:gat, key, ttl_or_default(ttl))
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Touch updates expiration time for a given key.
|
77
|
+
#
|
78
|
+
# Returns true if key exists, otherwise nil.
|
79
|
+
def touch(key, ttl = nil)
|
80
|
+
resp = perform(:touch, key, ttl_or_default(ttl))
|
81
|
+
resp.nil? ? nil : true
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# Get the value and CAS ID associated with the key. If a block is provided,
|
86
|
+
# value and CAS will be passed to the block.
|
87
|
+
def get_cas(key)
|
88
|
+
(value, cas) = perform(:cas, key)
|
89
|
+
# TODO: This is odd. Confirm this is working as expected.
|
90
|
+
value = nil if !value || value == 'Not found'
|
91
|
+
return [value, cas] unless block_given?
|
92
|
+
|
93
|
+
yield value, cas
|
64
94
|
end
|
65
95
|
|
66
96
|
##
|
@@ -72,16 +102,31 @@ module Dalli
|
|
72
102
|
keys.compact!
|
73
103
|
|
74
104
|
return {} if keys.empty?
|
105
|
+
|
75
106
|
if block_given?
|
76
|
-
|
107
|
+
pipelined_getter.process(keys) { |k, data| yield k, data.first }
|
77
108
|
else
|
78
109
|
{}.tap do |hash|
|
79
|
-
|
110
|
+
pipelined_getter.process(keys) { |k, data| hash[k] = data.first }
|
80
111
|
end
|
81
112
|
end
|
82
113
|
end
|
83
114
|
|
84
|
-
|
115
|
+
##
|
116
|
+
# Fetch multiple keys efficiently, including available metadata such as CAS.
|
117
|
+
# If a block is given, yields key/data pairs one a time. Data is an array:
|
118
|
+
# [value, cas_id]
|
119
|
+
# If no block is given, returns a hash of
|
120
|
+
# { 'key' => [value, cas_id] }
|
121
|
+
def get_multi_cas(*keys)
|
122
|
+
if block_given?
|
123
|
+
pipelined_getter.process(keys) { |*args| yield(*args) }
|
124
|
+
else
|
125
|
+
{}.tap do |hash|
|
126
|
+
pipelined_getter.process(keys) { |k, data| hash[k] = data }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
85
130
|
|
86
131
|
# Fetch the value associated with the key.
|
87
132
|
# If a value is found, then it is returned.
|
@@ -91,17 +136,14 @@ module Dalli
|
|
91
136
|
# If a value is not found (or if the found value is nil and :cache_nils is false)
|
92
137
|
# and a block is given, the block will be invoked and its return value
|
93
138
|
# written to the cache and returned.
|
94
|
-
def fetch(key, ttl = nil,
|
95
|
-
|
96
|
-
val = get(key,
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
add(key, val, ttl_or_default(ttl), options)
|
103
|
-
end
|
104
|
-
val
|
139
|
+
def fetch(key, ttl = nil, req_options = nil)
|
140
|
+
req_options = req_options.nil? ? CACHE_NILS : req_options.merge(CACHE_NILS) if cache_nils
|
141
|
+
val = get(key, req_options)
|
142
|
+
return val unless block_given? && not_found?(val)
|
143
|
+
|
144
|
+
new_val = yield
|
145
|
+
add(key, new_val, ttl_or_default(ttl), req_options)
|
146
|
+
new_val
|
105
147
|
end
|
106
148
|
|
107
149
|
##
|
@@ -115,8 +157,8 @@ module Dalli
|
|
115
157
|
# - nil if the key did not exist.
|
116
158
|
# - false if the value was changed by someone else.
|
117
159
|
# - true if the value was successfully updated.
|
118
|
-
def cas(key, ttl = nil,
|
119
|
-
cas_core(key, false, ttl,
|
160
|
+
def cas(key, ttl = nil, req_options = nil, &block)
|
161
|
+
cas_core(key, false, ttl, req_options, &block)
|
120
162
|
end
|
121
163
|
|
122
164
|
##
|
@@ -126,30 +168,78 @@ module Dalli
|
|
126
168
|
# Returns:
|
127
169
|
# - false if the value was changed by someone else.
|
128
170
|
# - true if the value was successfully updated.
|
129
|
-
def cas!(key, ttl = nil,
|
130
|
-
cas_core(key, true, ttl,
|
171
|
+
def cas!(key, ttl = nil, req_options = nil, &block)
|
172
|
+
cas_core(key, true, ttl, req_options, &block)
|
131
173
|
end
|
132
174
|
|
133
|
-
|
134
|
-
|
175
|
+
##
|
176
|
+
# Turn on quiet aka noreply support for a number of
|
177
|
+
# memcached operations.
|
178
|
+
#
|
179
|
+
# All relevant operations within this block will be effectively
|
180
|
+
# pipelined as Dalli will use 'quiet' versions. The invoked methods
|
181
|
+
# will all return nil, rather than their usual response. Method
|
182
|
+
# latency will be substantially lower, as the caller will not be
|
183
|
+
# blocking on responses.
|
184
|
+
#
|
185
|
+
# Currently supports storage (set, add, replace, append, prepend),
|
186
|
+
# arithmetic (incr, decr), flush and delete operations. Use of
|
187
|
+
# unsupported operations inside a block will raise an error.
|
188
|
+
#
|
189
|
+
# Any error replies will be discarded at the end of the block, and
|
190
|
+
# Dalli client methods invoked inside the block will not
|
191
|
+
# have return values
|
192
|
+
def quiet
|
193
|
+
old = Thread.current[::Dalli::QUIET]
|
194
|
+
Thread.current[::Dalli::QUIET] = true
|
195
|
+
yield
|
196
|
+
ensure
|
197
|
+
@ring&.pipeline_consume_and_ignore_responses
|
198
|
+
Thread.current[::Dalli::QUIET] = old
|
199
|
+
end
|
200
|
+
alias multi quiet
|
201
|
+
|
202
|
+
def set(key, value, ttl = nil, req_options = nil)
|
203
|
+
set_cas(key, value, 0, ttl, req_options)
|
204
|
+
end
|
205
|
+
|
206
|
+
##
|
207
|
+
# Set the key-value pair, verifying existing CAS.
|
208
|
+
# Returns the resulting CAS value if succeeded, and falsy otherwise.
|
209
|
+
def set_cas(key, value, cas, ttl = nil, req_options = nil)
|
210
|
+
perform(:set, key, value, ttl_or_default(ttl), cas, req_options)
|
135
211
|
end
|
136
212
|
|
137
213
|
##
|
138
214
|
# Conditionally add a key/value pair, if the key does not already exist
|
139
215
|
# on the server. Returns truthy if the operation succeeded.
|
140
|
-
def add(key, value, ttl = nil,
|
141
|
-
perform(:add, key, value, ttl_or_default(ttl),
|
216
|
+
def add(key, value, ttl = nil, req_options = nil)
|
217
|
+
perform(:add, key, value, ttl_or_default(ttl), req_options)
|
142
218
|
end
|
143
219
|
|
144
220
|
##
|
145
221
|
# Conditionally add a key/value pair, only if the key already exists
|
146
222
|
# on the server. Returns truthy if the operation succeeded.
|
147
|
-
def replace(key, value, ttl = nil,
|
148
|
-
|
223
|
+
def replace(key, value, ttl = nil, req_options = nil)
|
224
|
+
replace_cas(key, value, 0, ttl, req_options)
|
225
|
+
end
|
226
|
+
|
227
|
+
##
|
228
|
+
# Conditionally add a key/value pair, verifying existing CAS, only if the
|
229
|
+
# key already exists on the server. Returns the new CAS value if the
|
230
|
+
# operation succeeded, or falsy otherwise.
|
231
|
+
def replace_cas(key, value, cas, ttl = nil, req_options = nil)
|
232
|
+
perform(:replace, key, value, ttl_or_default(ttl), cas, req_options)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Delete a key/value pair, verifying existing CAS.
|
236
|
+
# Returns true if succeeded, and falsy otherwise.
|
237
|
+
def delete_cas(key, cas = 0)
|
238
|
+
perform(:delete, key, cas)
|
149
239
|
end
|
150
240
|
|
151
241
|
def delete(key)
|
152
|
-
|
242
|
+
delete_cas(key, 0)
|
153
243
|
end
|
154
244
|
|
155
245
|
##
|
@@ -166,13 +256,6 @@ module Dalli
|
|
166
256
|
perform(:prepend, key, value.to_s)
|
167
257
|
end
|
168
258
|
|
169
|
-
def flush(delay = 0)
|
170
|
-
time = -delay
|
171
|
-
ring.servers.map { |s| s.request(:flush, time += delay) }
|
172
|
-
end
|
173
|
-
|
174
|
-
alias_method :flush_all, :flush
|
175
|
-
|
176
259
|
##
|
177
260
|
# Incr adds the given amount to the counter on the memcached server.
|
178
261
|
# Amt must be a positive integer value.
|
@@ -184,8 +267,11 @@ module Dalli
|
|
184
267
|
# Note that the ttl will only apply if the counter does not already
|
185
268
|
# exist. To increase an existing counter and update its TTL, use
|
186
269
|
# #cas.
|
270
|
+
#
|
271
|
+
# If the value already exists, it must have been set with raw: true
|
187
272
|
def incr(key, amt = 1, ttl = nil, default = nil)
|
188
|
-
|
273
|
+
check_positive!(amt)
|
274
|
+
|
189
275
|
perform(:incr, key, amt.to_i, ttl_or_default(ttl), default)
|
190
276
|
end
|
191
277
|
|
@@ -203,34 +289,31 @@ module Dalli
|
|
203
289
|
# Note that the ttl will only apply if the counter does not already
|
204
290
|
# exist. To decrease an existing counter and update its TTL, use
|
205
291
|
# #cas.
|
292
|
+
#
|
293
|
+
# If the value already exists, it must have been set with raw: true
|
206
294
|
def decr(key, amt = 1, ttl = nil, default = nil)
|
207
|
-
|
295
|
+
check_positive!(amt)
|
296
|
+
|
208
297
|
perform(:decr, key, amt.to_i, ttl_or_default(ttl), default)
|
209
298
|
end
|
210
299
|
|
211
300
|
##
|
212
|
-
#
|
213
|
-
#
|
214
|
-
# Returns true if key exists, otherwise nil.
|
215
|
-
def touch(key, ttl = nil)
|
216
|
-
resp = perform(:touch, key, ttl_or_default(ttl))
|
217
|
-
resp.nil? ? nil : true
|
218
|
-
end
|
219
|
-
|
301
|
+
# Flush the memcached server, at 'delay' seconds in the future.
|
302
|
+
# Delay defaults to zero seconds, which means an immediate flush.
|
220
303
|
##
|
221
|
-
|
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))
|
304
|
+
def flush(delay = 0)
|
305
|
+
ring.servers.map { |s| s.request(:flush, delay) }
|
226
306
|
end
|
307
|
+
alias flush_all flush
|
308
|
+
|
309
|
+
ALLOWED_STAT_KEYS = %i[items slabs settings].freeze
|
227
310
|
|
228
311
|
##
|
229
312
|
# Collect the stats for each server.
|
230
313
|
# You can optionally pass a type including :items, :slabs or :settings to get specific stats
|
231
314
|
# Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
|
232
315
|
def stats(type = nil)
|
233
|
-
type = nil unless
|
316
|
+
type = nil unless ALLOWED_STAT_KEYS.include? type
|
234
317
|
values = {}
|
235
318
|
ring.servers.each do |server|
|
236
319
|
values[server.name.to_s] = server.alive? ? server.request(:stats, type.to_s) : nil
|
@@ -246,12 +329,6 @@ module Dalli
|
|
246
329
|
end
|
247
330
|
end
|
248
331
|
|
249
|
-
##
|
250
|
-
## Make sure memcache servers are alive, or raise an Dalli::RingError
|
251
|
-
def alive!
|
252
|
-
ring.server_for_key("")
|
253
|
-
end
|
254
|
-
|
255
332
|
##
|
256
333
|
## Version of the memcache servers.
|
257
334
|
def version
|
@@ -263,67 +340,29 @@ module Dalli
|
|
263
340
|
end
|
264
341
|
|
265
342
|
##
|
266
|
-
|
267
|
-
|
268
|
-
|
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
|
343
|
+
## Make sure memcache servers are alive, or raise an Dalli::RingError
|
344
|
+
def alive!
|
345
|
+
ring.server_for_key('')
|
292
346
|
end
|
293
347
|
|
294
348
|
##
|
295
|
-
#
|
296
|
-
#
|
297
|
-
def
|
298
|
-
|
299
|
-
|
349
|
+
# Close our connection to each server.
|
350
|
+
# If you perform another operation after this, the connections will be re-established.
|
351
|
+
def close
|
352
|
+
@ring&.close
|
353
|
+
@ring = nil
|
300
354
|
end
|
355
|
+
alias reset close
|
301
356
|
|
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
|
357
|
+
CACHE_NILS = { cache_nils: true }.freeze
|
310
358
|
|
311
|
-
|
312
|
-
|
313
|
-
def delete_cas(key, cas = 0)
|
314
|
-
perform(:delete, key, cas)
|
359
|
+
def not_found?(val)
|
360
|
+
cache_nils ? val == ::Dalli::NOT_FOUND : val.nil?
|
315
361
|
end
|
316
362
|
|
317
|
-
|
318
|
-
|
319
|
-
# If you perform another operation after this, the connections will be re-established.
|
320
|
-
def close
|
321
|
-
if @ring
|
322
|
-
@ring.servers.each { |s| s.close }
|
323
|
-
@ring = nil
|
324
|
-
end
|
363
|
+
def cache_nils
|
364
|
+
@options[:cache_nils]
|
325
365
|
end
|
326
|
-
alias_method :reset, :close
|
327
366
|
|
328
367
|
# Stub method so a bare Dalli client can pretend to be a connection pool.
|
329
368
|
def with
|
@@ -332,225 +371,78 @@ module Dalli
|
|
332
371
|
|
333
372
|
private
|
334
373
|
|
335
|
-
def
|
374
|
+
def check_positive!(amt)
|
375
|
+
raise ArgumentError, "Positive values only: #{amt}" if amt.negative?
|
376
|
+
end
|
377
|
+
|
378
|
+
def cas_core(key, always_set, ttl = nil, req_options = nil)
|
336
379
|
(value, cas) = perform(:cas, key)
|
337
|
-
value = !value || value ==
|
380
|
+
value = nil if !value || value == 'Not found'
|
338
381
|
return if value.nil? && !always_set
|
382
|
+
|
339
383
|
newvalue = yield(value)
|
340
|
-
perform(:set, key, newvalue, ttl_or_default(ttl), cas,
|
384
|
+
perform(:set, key, newvalue, ttl_or_default(ttl), cas, req_options)
|
341
385
|
end
|
342
386
|
|
387
|
+
##
|
388
|
+
# Uses the argument TTL or the client-wide default. Ensures
|
389
|
+
# that the value is an integer
|
390
|
+
##
|
343
391
|
def ttl_or_default(ttl)
|
344
392
|
(ttl || @options[:expires_in]).to_i
|
345
393
|
rescue NoMethodError
|
346
394
|
raise ArgumentError, "Cannot convert ttl (#{ttl}) to an integer"
|
347
395
|
end
|
348
396
|
|
349
|
-
def groups_for_keys(*keys)
|
350
|
-
keys.flatten!
|
351
|
-
keys.map! { |a| validate_key(a.to_s) }
|
352
|
-
|
353
|
-
keys.group_by { |key|
|
354
|
-
begin
|
355
|
-
ring.server_for_key(key)
|
356
|
-
rescue Dalli::RingError
|
357
|
-
Dalli.logger.debug { "unable to get key #{key}" }
|
358
|
-
nil
|
359
|
-
end
|
360
|
-
}
|
361
|
-
end
|
362
|
-
|
363
|
-
def make_multi_get_requests(groups)
|
364
|
-
groups.each do |server, keys_for_server|
|
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}" }
|
373
|
-
end
|
374
|
-
end
|
375
|
-
|
376
|
-
def perform_multi_response_start(servers)
|
377
|
-
deleted = []
|
378
|
-
|
379
|
-
servers.each do |server|
|
380
|
-
next unless server.alive?
|
381
|
-
|
382
|
-
begin
|
383
|
-
server.multi_response_start
|
384
|
-
rescue Dalli::NetworkError
|
385
|
-
servers.each { |s| s.multi_response_abort unless s.sock.nil? }
|
386
|
-
raise
|
387
|
-
rescue Dalli::DalliError => e
|
388
|
-
Dalli.logger.debug { e.inspect }
|
389
|
-
Dalli.logger.debug { "results from this server will be missing" }
|
390
|
-
deleted.append(server)
|
391
|
-
end
|
392
|
-
end
|
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."
|
405
|
-
end
|
406
|
-
|
407
|
-
##
|
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"
|
411
|
-
def normalize_servers(servers)
|
412
|
-
Array(servers).flat_map do |server|
|
413
|
-
if server.is_a? String
|
414
|
-
server.split(",")
|
415
|
-
else
|
416
|
-
server
|
417
|
-
end
|
418
|
-
end
|
419
|
-
end
|
420
|
-
|
421
397
|
def ring
|
398
|
+
# TODO: This server initialization should probably be pushed down
|
399
|
+
# to the Ring
|
422
400
|
@ring ||= Dalli::Ring.new(
|
423
|
-
@servers.map
|
424
|
-
|
425
|
-
|
426
|
-
uri = URI.parse(s)
|
427
|
-
server_options[:username] = uri.user
|
428
|
-
server_options[:password] = uri.password
|
429
|
-
s = "#{uri.host}:#{uri.port}"
|
430
|
-
end
|
431
|
-
@options.fetch(:protocol_implementation, Dalli::Protocol::Binary).new(s, @options.merge(server_options))
|
432
|
-
}, @options
|
401
|
+
@servers.map do |s|
|
402
|
+
protocol_implementation.new(s, @options)
|
403
|
+
end, @options
|
433
404
|
)
|
434
405
|
end
|
435
406
|
|
436
|
-
|
437
|
-
|
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
|
-
|
445
|
-
server = ring.server_for_key(key)
|
446
|
-
server.request(op, key, *args)
|
447
|
-
rescue NetworkError => e
|
448
|
-
Dalli.logger.debug { e.inspect }
|
449
|
-
Dalli.logger.debug { "retrying request with new server" }
|
450
|
-
retry
|
451
|
-
end
|
407
|
+
def protocol_implementation
|
408
|
+
@protocol_implementation ||= @options.fetch(:protocol_implementation, Dalli::Protocol::Binary)
|
452
409
|
end
|
453
410
|
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
411
|
+
##
|
412
|
+
# Chokepoint method for memcached methods with a key argument.
|
413
|
+
# Validates the key, resolves the key to the appropriate server
|
414
|
+
# instance, and invokes the memcached method on the appropriate
|
415
|
+
# server.
|
416
|
+
#
|
417
|
+
# This method also forces retries on network errors - when
|
418
|
+
# a particular memcached instance becomes unreachable, or the
|
419
|
+
# operational times out.
|
420
|
+
##
|
421
|
+
def perform(*all_args)
|
422
|
+
return yield if block_given?
|
464
423
|
|
465
|
-
|
466
|
-
(ns = namespace) ? "#{ns}:#{key}" : key
|
467
|
-
end
|
424
|
+
op, key, *args = all_args
|
468
425
|
|
469
|
-
|
470
|
-
|
471
|
-
end
|
426
|
+
key = key.to_s
|
427
|
+
key = @key_manager.validate_key(key)
|
472
428
|
|
473
|
-
|
474
|
-
|
475
|
-
|
429
|
+
server = ring.server_for_key(key)
|
430
|
+
server.request(op, key, *args)
|
431
|
+
rescue NetworkError => e
|
432
|
+
Dalli.logger.debug { e.inspect }
|
433
|
+
Dalli.logger.debug { 'retrying request with new server' }
|
434
|
+
retry
|
476
435
|
end
|
477
436
|
|
478
437
|
def normalize_options(opts)
|
479
|
-
if opts[:
|
480
|
-
Dalli.logger.warn "DEPRECATED: Dalli's :compression option is now just :compress => true. Please update your configuration."
|
481
|
-
opts[:compress] = opts.delete(:compression)
|
482
|
-
end
|
483
|
-
begin
|
484
|
-
opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in]
|
485
|
-
rescue NoMethodError
|
486
|
-
raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
|
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
|
438
|
+
opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in]
|
491
439
|
opts
|
440
|
+
rescue NoMethodError
|
441
|
+
raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
|
492
442
|
end
|
493
443
|
|
494
|
-
|
495
|
-
|
496
|
-
def get_multi_yielder(keys)
|
497
|
-
perform do
|
498
|
-
return {} if keys.empty?
|
499
|
-
ring.lock do
|
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
|
529
|
-
end
|
530
|
-
break
|
531
|
-
|
532
|
-
else
|
533
|
-
readable.each do |sock|
|
534
|
-
server = sock.server
|
535
|
-
|
536
|
-
begin
|
537
|
-
server.multi_response_nonblock.each_pair do |key, value_list|
|
538
|
-
yield key_without_namespace(key), value_list
|
539
|
-
end
|
540
|
-
|
541
|
-
if server.multi_response_completed?
|
542
|
-
servers.delete(server)
|
543
|
-
end
|
544
|
-
rescue NetworkError
|
545
|
-
servers.each { |s| s.multi_response_abort unless s.sock.nil? }
|
546
|
-
raise
|
547
|
-
end
|
548
|
-
end
|
549
|
-
end
|
550
|
-
end
|
551
|
-
end
|
552
|
-
end
|
444
|
+
def pipelined_getter
|
445
|
+
PipelinedGetter.new(ring, @key_manager)
|
553
446
|
end
|
554
|
-
|
555
447
|
end
|
556
448
|
end
|