dalli 3.0.6 → 3.1.3
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/History.md +29 -0
- data/lib/dalli/client.rb +163 -261
- data/lib/dalli/options.rb +3 -3
- data/lib/dalli/pipelined_getter.rb +177 -0
- data/lib/dalli/protocol/base.rb +238 -0
- data/lib/dalli/protocol/binary/request_formatter.rb +11 -3
- data/lib/dalli/protocol/binary/response_header.rb +36 -0
- data/lib/dalli/protocol/binary/response_processor.rb +101 -42
- data/lib/dalli/protocol/binary/sasl_authentication.rb +1 -1
- data/lib/dalli/protocol/binary.rb +81 -445
- 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 +7 -7
- data/lib/dalli/ring.rb +5 -1
- data/lib/dalli/socket.rb +8 -6
- data/lib/dalli/version.rb +1 -1
- data/lib/dalli.rb +9 -0
- data/lib/rack/session/dalli.rb +83 -74
- metadata +15 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0905b56adf194401de7755ab22ebef6b57f6e673e8332f7cb99b3ef9ed1dde63'
|
4
|
+
data.tar.gz: dac2014c748ef0c55fe171af0cec8dec807ae2468f758338c8968135acd47b92
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 85d8382dfd13c2353a61c74525e17bd81e000308e8d476851ed25fdb6ef81e9aff4276eb0b1484e30c83001301a3500140cb46a595081e1b21f0801ea32d7e28
|
7
|
+
data.tar.gz: 7f0adfc09e31d435bbb3f6cff2f65392a58f81697e7025ca71d9423f9a536d9c984ed7ba0742d0c0f5de0e865a7ebd2dc5548a0406c86d7f1fe0c8135c7af363
|
data/History.md
CHANGED
@@ -1,6 +1,35 @@
|
|
1
1
|
Dalli Changelog
|
2
2
|
=====================
|
3
3
|
|
4
|
+
Unreleased
|
5
|
+
==========
|
6
|
+
|
7
|
+
3.1.3
|
8
|
+
==========
|
9
|
+
|
10
|
+
- Restore falsey behavior on delete/delete_cas for nonexistent key (petergoldstein)
|
11
|
+
|
12
|
+
3.1.2
|
13
|
+
==========
|
14
|
+
|
15
|
+
- Make quiet? / multi? public on Dalli::Protocol::Binary (petergoldstein)
|
16
|
+
|
17
|
+
3.1.1
|
18
|
+
==========
|
19
|
+
|
20
|
+
- Add quiet support for incr, decr, append, depend, and flush (petergoldstein)
|
21
|
+
- Additional refactoring to allow reuse of connection behavior (petergoldstein)
|
22
|
+
- Fix issue in flush such that it wasn't passing the delay argument to memcached (petergoldstein)
|
23
|
+
|
24
|
+
3.1.0
|
25
|
+
==========
|
26
|
+
|
27
|
+
- BREAKING CHANGE: Update Rack::Session::Dalli to inherit from Abstract::PersistedSecure. This will invalidate existing sessions (petergoldstein)
|
28
|
+
- BREAKING CHANGE: Use of unsupported operations in a multi block now raise an error. (petergoldstein)
|
29
|
+
- Extract PipelinedGetter from Dalli::Client (petergoldstein)
|
30
|
+
- Fix SSL socket so that it works with pipelined gets (petergoldstein)
|
31
|
+
- Additional refactoring to split classes (petergoldstein)
|
32
|
+
|
4
33
|
3.0.6
|
5
34
|
==========
|
6
35
|
|
data/lib/dalli/client.rb
CHANGED
@@ -49,7 +49,7 @@ module Dalli
|
|
49
49
|
def initialize(servers = nil, options = {})
|
50
50
|
@servers = ::Dalli::ServersArgNormalizer.normalize_servers(servers)
|
51
51
|
@options = normalize_options(options)
|
52
|
-
@key_manager = ::Dalli::KeyManager.new(options)
|
52
|
+
@key_manager = ::Dalli::KeyManager.new(@options)
|
53
53
|
@ring = nil
|
54
54
|
end
|
55
55
|
|
@@ -58,24 +58,39 @@ module Dalli
|
|
58
58
|
#
|
59
59
|
|
60
60
|
##
|
61
|
-
#
|
62
|
-
#
|
63
|
-
|
64
|
-
|
65
|
-
def multi
|
66
|
-
old = Thread.current[:dalli_multi]
|
67
|
-
Thread.current[:dalli_multi] = true
|
68
|
-
yield
|
69
|
-
ensure
|
70
|
-
@ring&.flush_multi_responses
|
71
|
-
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)
|
72
65
|
end
|
73
66
|
|
74
67
|
##
|
75
|
-
#
|
68
|
+
# Gat (get and touch) fetch an item and simultaneously update its expiration time.
|
69
|
+
#
|
76
70
|
# If a value is not found, then +nil+ is returned.
|
77
|
-
def
|
78
|
-
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
|
79
94
|
end
|
80
95
|
|
81
96
|
##
|
@@ -89,15 +104,29 @@ module Dalli
|
|
89
104
|
return {} if keys.empty?
|
90
105
|
|
91
106
|
if block_given?
|
92
|
-
|
107
|
+
pipelined_getter.process(keys) { |k, data| yield k, data.first }
|
93
108
|
else
|
94
109
|
{}.tap do |hash|
|
95
|
-
|
110
|
+
pipelined_getter.process(keys) { |k, data| hash[k] = data.first }
|
96
111
|
end
|
97
112
|
end
|
98
113
|
end
|
99
114
|
|
100
|
-
|
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
|
101
130
|
|
102
131
|
# Fetch the value associated with the key.
|
103
132
|
# If a value is found, then it is returned.
|
@@ -110,19 +139,11 @@ module Dalli
|
|
110
139
|
def fetch(key, ttl = nil, req_options = nil)
|
111
140
|
req_options = req_options.nil? ? CACHE_NILS : req_options.merge(CACHE_NILS) if cache_nils
|
112
141
|
val = get(key, req_options)
|
113
|
-
|
114
|
-
val = yield
|
115
|
-
add(key, val, ttl_or_default(ttl), req_options)
|
116
|
-
end
|
117
|
-
val
|
118
|
-
end
|
142
|
+
return val unless block_given? && not_found?(val)
|
119
143
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
def cache_nils
|
125
|
-
@options[:cache_nils]
|
144
|
+
new_val = yield
|
145
|
+
add(key, new_val, ttl_or_default(ttl), req_options)
|
146
|
+
new_val
|
126
147
|
end
|
127
148
|
|
128
149
|
##
|
@@ -136,8 +157,8 @@ module Dalli
|
|
136
157
|
# - nil if the key did not exist.
|
137
158
|
# - false if the value was changed by someone else.
|
138
159
|
# - true if the value was successfully updated.
|
139
|
-
def cas(key, ttl = nil,
|
140
|
-
cas_core(key, false, ttl,
|
160
|
+
def cas(key, ttl = nil, req_options = nil, &block)
|
161
|
+
cas_core(key, false, ttl, req_options, &block)
|
141
162
|
end
|
142
163
|
|
143
164
|
##
|
@@ -147,30 +168,78 @@ module Dalli
|
|
147
168
|
# Returns:
|
148
169
|
# - false if the value was changed by someone else.
|
149
170
|
# - true if the value was successfully updated.
|
150
|
-
def cas!(key, ttl = nil,
|
151
|
-
cas_core(key, true, ttl,
|
171
|
+
def cas!(key, ttl = nil, req_options = nil, &block)
|
172
|
+
cas_core(key, true, ttl, req_options, &block)
|
173
|
+
end
|
174
|
+
|
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
|
152
199
|
end
|
200
|
+
alias multi quiet
|
153
201
|
|
154
|
-
def set(key, value, ttl = nil,
|
155
|
-
|
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)
|
156
211
|
end
|
157
212
|
|
158
213
|
##
|
159
214
|
# Conditionally add a key/value pair, if the key does not already exist
|
160
215
|
# on the server. Returns truthy if the operation succeeded.
|
161
|
-
def add(key, value, ttl = nil,
|
162
|
-
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)
|
163
218
|
end
|
164
219
|
|
165
220
|
##
|
166
221
|
# Conditionally add a key/value pair, only if the key already exists
|
167
222
|
# on the server. Returns truthy if the operation succeeded.
|
168
|
-
def replace(key, value, ttl = nil,
|
169
|
-
|
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)
|
170
239
|
end
|
171
240
|
|
172
241
|
def delete(key)
|
173
|
-
|
242
|
+
delete_cas(key, 0)
|
174
243
|
end
|
175
244
|
|
176
245
|
##
|
@@ -187,13 +256,6 @@ module Dalli
|
|
187
256
|
perform(:prepend, key, value.to_s)
|
188
257
|
end
|
189
258
|
|
190
|
-
def flush(delay = 0)
|
191
|
-
time = -delay
|
192
|
-
ring.servers.map { |s| s.request(:flush, time += delay) }
|
193
|
-
end
|
194
|
-
|
195
|
-
alias flush_all flush
|
196
|
-
|
197
259
|
##
|
198
260
|
# Incr adds the given amount to the counter on the memcached server.
|
199
261
|
# Amt must be a positive integer value.
|
@@ -205,8 +267,10 @@ module Dalli
|
|
205
267
|
# Note that the ttl will only apply if the counter does not already
|
206
268
|
# exist. To increase an existing counter and update its TTL, use
|
207
269
|
# #cas.
|
270
|
+
#
|
271
|
+
# If the value already exists, it must have been set with raw: true
|
208
272
|
def incr(key, amt = 1, ttl = nil, default = nil)
|
209
|
-
|
273
|
+
check_positive!(amt)
|
210
274
|
|
211
275
|
perform(:incr, key, amt.to_i, ttl_or_default(ttl), default)
|
212
276
|
end
|
@@ -225,35 +289,31 @@ module Dalli
|
|
225
289
|
# Note that the ttl will only apply if the counter does not already
|
226
290
|
# exist. To decrease an existing counter and update its TTL, use
|
227
291
|
# #cas.
|
292
|
+
#
|
293
|
+
# If the value already exists, it must have been set with raw: true
|
228
294
|
def decr(key, amt = 1, ttl = nil, default = nil)
|
229
|
-
|
295
|
+
check_positive!(amt)
|
230
296
|
|
231
297
|
perform(:decr, key, amt.to_i, ttl_or_default(ttl), default)
|
232
298
|
end
|
233
299
|
|
234
300
|
##
|
235
|
-
#
|
236
|
-
#
|
237
|
-
# Returns true if key exists, otherwise nil.
|
238
|
-
def touch(key, ttl = nil)
|
239
|
-
resp = perform(:touch, key, ttl_or_default(ttl))
|
240
|
-
resp.nil? ? nil : true
|
241
|
-
end
|
242
|
-
|
301
|
+
# Flush the memcached server, at 'delay' seconds in the future.
|
302
|
+
# Delay defaults to zero seconds, which means an immediate flush.
|
243
303
|
##
|
244
|
-
|
245
|
-
|
246
|
-
# If a value is not found, then +nil+ is returned.
|
247
|
-
def gat(key, ttl = nil)
|
248
|
-
perform(:gat, key, ttl_or_default(ttl))
|
304
|
+
def flush(delay = 0)
|
305
|
+
ring.servers.map { |s| s.request(:flush, delay) }
|
249
306
|
end
|
307
|
+
alias flush_all flush
|
308
|
+
|
309
|
+
ALLOWED_STAT_KEYS = %i[items slabs settings].freeze
|
250
310
|
|
251
311
|
##
|
252
312
|
# Collect the stats for each server.
|
253
313
|
# You can optionally pass a type including :items, :slabs or :settings to get specific stats
|
254
314
|
# Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
|
255
315
|
def stats(type = nil)
|
256
|
-
type = nil unless
|
316
|
+
type = nil unless ALLOWED_STAT_KEYS.include? type
|
257
317
|
values = {}
|
258
318
|
ring.servers.each do |server|
|
259
319
|
values[server.name.to_s] = server.alive? ? server.request(:stats, type.to_s) : nil
|
@@ -269,12 +329,6 @@ module Dalli
|
|
269
329
|
end
|
270
330
|
end
|
271
331
|
|
272
|
-
##
|
273
|
-
## Make sure memcache servers are alive, or raise an Dalli::RingError
|
274
|
-
def alive!
|
275
|
-
ring.server_for_key('')
|
276
|
-
end
|
277
|
-
|
278
332
|
##
|
279
333
|
## Version of the memcache servers.
|
280
334
|
def version
|
@@ -286,68 +340,30 @@ module Dalli
|
|
286
340
|
end
|
287
341
|
|
288
342
|
##
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
(value, cas) = perform(:cas, key)
|
293
|
-
value = nil if !value || value == 'Not found'
|
294
|
-
if block_given?
|
295
|
-
yield value, cas
|
296
|
-
else
|
297
|
-
[value, cas]
|
298
|
-
end
|
299
|
-
end
|
300
|
-
|
301
|
-
##
|
302
|
-
# Fetch multiple keys efficiently, including available metadata such as CAS.
|
303
|
-
# If a block is given, yields key/data pairs one a time. Data is an array:
|
304
|
-
# [value, cas_id]
|
305
|
-
# If no block is given, returns a hash of
|
306
|
-
# { 'key' => [value, cas_id] }
|
307
|
-
def get_multi_cas(*keys)
|
308
|
-
if block_given?
|
309
|
-
get_multi_yielder(keys) { |*args| yield(*args) }
|
310
|
-
else
|
311
|
-
{}.tap do |hash|
|
312
|
-
get_multi_yielder(keys) { |k, data| hash[k] = data }
|
313
|
-
end
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
##
|
318
|
-
# Set the key-value pair, verifying existing CAS.
|
319
|
-
# Returns the resulting CAS value if succeeded, and falsy otherwise.
|
320
|
-
def set_cas(key, value, cas, ttl = nil, options = nil)
|
321
|
-
ttl ||= @options[:expires_in].to_i
|
322
|
-
perform(:set, key, value, ttl, cas, options)
|
323
|
-
end
|
324
|
-
|
325
|
-
##
|
326
|
-
# Conditionally add a key/value pair, verifying existing CAS, only if the
|
327
|
-
# key already exists on the server. Returns the new CAS value if the
|
328
|
-
# operation succeeded, or falsy otherwise.
|
329
|
-
def replace_cas(key, value, cas, ttl = nil, options = nil)
|
330
|
-
ttl ||= @options[:expires_in].to_i
|
331
|
-
perform(:replace, key, value, ttl, cas, options)
|
332
|
-
end
|
333
|
-
|
334
|
-
# Delete a key/value pair, verifying existing CAS.
|
335
|
-
# Returns true if succeeded, and falsy otherwise.
|
336
|
-
def delete_cas(key, cas = 0)
|
337
|
-
perform(:delete, key, cas)
|
343
|
+
## Make sure memcache servers are alive, or raise an Dalli::RingError
|
344
|
+
def alive!
|
345
|
+
ring.server_for_key('')
|
338
346
|
end
|
339
347
|
|
340
348
|
##
|
341
349
|
# Close our connection to each server.
|
342
350
|
# If you perform another operation after this, the connections will be re-established.
|
343
351
|
def close
|
344
|
-
|
345
|
-
|
346
|
-
@ring.servers.each(&:close)
|
352
|
+
@ring&.close
|
347
353
|
@ring = nil
|
348
354
|
end
|
349
355
|
alias reset close
|
350
356
|
|
357
|
+
CACHE_NILS = { cache_nils: true }.freeze
|
358
|
+
|
359
|
+
def not_found?(val)
|
360
|
+
cache_nils ? val == ::Dalli::NOT_FOUND : val.nil?
|
361
|
+
end
|
362
|
+
|
363
|
+
def cache_nils
|
364
|
+
@options[:cache_nils]
|
365
|
+
end
|
366
|
+
|
351
367
|
# Stub method so a bare Dalli client can pretend to be a connection pool.
|
352
368
|
def with
|
353
369
|
yield self
|
@@ -355,15 +371,23 @@ module Dalli
|
|
355
371
|
|
356
372
|
private
|
357
373
|
|
358
|
-
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)
|
359
379
|
(value, cas) = perform(:cas, key)
|
360
380
|
value = nil if !value || value == 'Not found'
|
361
381
|
return if value.nil? && !always_set
|
362
382
|
|
363
383
|
newvalue = yield(value)
|
364
|
-
perform(:set, key, newvalue, ttl_or_default(ttl), cas,
|
384
|
+
perform(:set, key, newvalue, ttl_or_default(ttl), cas, req_options)
|
365
385
|
end
|
366
386
|
|
387
|
+
##
|
388
|
+
# Uses the argument TTL or the client-wide default. Ensures
|
389
|
+
# that the value is an integer
|
390
|
+
##
|
367
391
|
def ttl_or_default(ttl)
|
368
392
|
(ttl || @options[:expires_in]).to_i
|
369
393
|
rescue NoMethodError
|
@@ -384,7 +408,16 @@ module Dalli
|
|
384
408
|
@protocol_implementation ||= @options.fetch(:protocol_implementation, Dalli::Protocol::Binary)
|
385
409
|
end
|
386
410
|
|
387
|
-
|
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
|
+
##
|
388
421
|
def perform(*all_args)
|
389
422
|
return yield if block_given?
|
390
423
|
|
@@ -402,145 +435,14 @@ module Dalli
|
|
402
435
|
end
|
403
436
|
|
404
437
|
def normalize_options(opts)
|
405
|
-
|
406
|
-
opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in]
|
407
|
-
rescue NoMethodError
|
408
|
-
raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
|
409
|
-
end
|
438
|
+
opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in]
|
410
439
|
opts
|
440
|
+
rescue NoMethodError
|
441
|
+
raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
|
411
442
|
end
|
412
443
|
|
413
|
-
|
414
|
-
|
415
|
-
##
|
416
|
-
# Yields, one at a time, keys and their values+attributes.
|
417
|
-
#
|
418
|
-
def get_multi_yielder(keys, &block)
|
419
|
-
return {} if keys.empty?
|
420
|
-
|
421
|
-
ring.lock do
|
422
|
-
groups = groups_for_keys(keys)
|
423
|
-
if (unfound_keys = groups.delete(nil))
|
424
|
-
Dalli.logger.debug do
|
425
|
-
"unable to get keys for #{unfound_keys.length} keys "\
|
426
|
-
'because no matching server was found'
|
427
|
-
end
|
428
|
-
end
|
429
|
-
make_multi_get_requests(groups)
|
430
|
-
|
431
|
-
servers = groups.keys
|
432
|
-
return if servers.empty?
|
433
|
-
|
434
|
-
# TODO: How does this exit on a NetworkError
|
435
|
-
servers = perform_multi_response_start(servers)
|
436
|
-
|
437
|
-
timeout = servers.first.options[:socket_timeout]
|
438
|
-
start_time = Time.now
|
439
|
-
loop do
|
440
|
-
# remove any dead servers
|
441
|
-
# TODO: Is this well behaved in a multi-threaded environment?
|
442
|
-
# Accessing the server socket like this seems problematic
|
443
|
-
servers.delete_if { |s| s.sock.nil? }
|
444
|
-
break if servers.empty?
|
445
|
-
|
446
|
-
servers = multi_yielder_loop(servers, start_time, timeout, &block)
|
447
|
-
end
|
448
|
-
end
|
449
|
-
rescue NetworkError => e
|
450
|
-
Dalli.logger.debug { e.inspect }
|
451
|
-
Dalli.logger.debug { 'retrying multi yielder because of timeout' }
|
452
|
-
retry
|
453
|
-
end
|
454
|
-
|
455
|
-
def make_multi_get_requests(groups)
|
456
|
-
groups.each do |server, keys_for_server|
|
457
|
-
server.request(:send_multiget, keys_for_server)
|
458
|
-
rescue DalliError, NetworkError => e
|
459
|
-
Dalli.logger.debug { e.inspect }
|
460
|
-
Dalli.logger.debug { "unable to get keys for server #{server.name}" }
|
461
|
-
end
|
462
|
-
end
|
463
|
-
|
464
|
-
# raises Dalli::NetworkError
|
465
|
-
def perform_multi_response_start(servers)
|
466
|
-
deleted = []
|
467
|
-
|
468
|
-
servers.each do |server|
|
469
|
-
next unless server.alive?
|
470
|
-
|
471
|
-
begin
|
472
|
-
server.multi_response_start
|
473
|
-
rescue Dalli::NetworkError
|
474
|
-
abort_multi_response(servers)
|
475
|
-
raise
|
476
|
-
rescue Dalli::DalliError => e
|
477
|
-
Dalli.logger.debug { e.inspect }
|
478
|
-
Dalli.logger.debug { 'results from this server will be missing' }
|
479
|
-
deleted.append(server)
|
480
|
-
end
|
481
|
-
end
|
482
|
-
|
483
|
-
servers.delete_if { |server| deleted.include?(server) }
|
484
|
-
end
|
485
|
-
|
486
|
-
# Swallows Dalli::NetworkError
|
487
|
-
def abort_multi_response(servers)
|
488
|
-
servers.each(&:multi_response_abort)
|
489
|
-
end
|
490
|
-
|
491
|
-
def multi_yielder_loop(servers, start_time, timeout, &block)
|
492
|
-
time_left = remaining_time(start_time, timeout)
|
493
|
-
readable_servers = servers_with_data(servers, time_left)
|
494
|
-
if readable_servers.empty?
|
495
|
-
abort_multi_connections_w_timeout(servers)
|
496
|
-
return readable_servers
|
497
|
-
end
|
498
|
-
|
499
|
-
readable_servers.each do |server|
|
500
|
-
servers.delete(server) if respond_to_readable_server(server, &block)
|
501
|
-
end
|
502
|
-
servers
|
503
|
-
rescue NetworkError
|
504
|
-
abort_multi_response(servers)
|
505
|
-
raise
|
506
|
-
end
|
507
|
-
|
508
|
-
def remaining_time(start, timeout)
|
509
|
-
elapsed = Time.now - start
|
510
|
-
return 0 if elapsed > timeout
|
511
|
-
|
512
|
-
timeout - elapsed
|
513
|
-
end
|
514
|
-
|
515
|
-
# Swallows Dalli::NetworkError
|
516
|
-
def abort_multi_connections_w_timeout(servers)
|
517
|
-
abort_multi_response(servers)
|
518
|
-
servers.each do |server|
|
519
|
-
Dalli.logger.debug { "memcached at #{server.name} did not response within timeout" }
|
520
|
-
end
|
521
|
-
|
522
|
-
true # Required to simplify caller
|
523
|
-
end
|
524
|
-
|
525
|
-
def respond_to_readable_server(server)
|
526
|
-
server.multi_response_nonblock.each_pair do |key, value_list|
|
527
|
-
yield @key_manager.key_without_namespace(key), value_list
|
528
|
-
end
|
529
|
-
|
530
|
-
server.multi_response_completed?
|
531
|
-
end
|
532
|
-
|
533
|
-
def servers_with_data(servers, timeout)
|
534
|
-
readable, = IO.select(servers.map(&:sock), nil, nil, timeout)
|
535
|
-
return [] if readable.nil?
|
536
|
-
|
537
|
-
readable.map(&:server)
|
538
|
-
end
|
539
|
-
|
540
|
-
def groups_for_keys(*keys)
|
541
|
-
keys.flatten!
|
542
|
-
keys.map! { |a| @key_manager.validate_key(a.to_s) }
|
543
|
-
ring.keys_grouped_by_server(keys)
|
444
|
+
def pipelined_getter
|
445
|
+
PipelinedGetter.new(ring, @key_manager)
|
544
446
|
end
|
545
447
|
end
|
546
448
|
end
|
data/lib/dalli/options.rb
CHANGED
@@ -31,19 +31,19 @@ module Dalli
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
def
|
34
|
+
def pipeline_response_setup
|
35
35
|
@lock.synchronize do
|
36
36
|
super
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
def
|
40
|
+
def pipeline_next_responses
|
41
41
|
@lock.synchronize do
|
42
42
|
super
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
def
|
46
|
+
def pipeline_abort
|
47
47
|
@lock.synchronize do
|
48
48
|
super
|
49
49
|
end
|