dalli 2.7.3 → 3.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/{History.md → CHANGELOG.md} +211 -0
- data/Gemfile +3 -6
- data/LICENSE +1 -1
- data/README.md +30 -208
- data/lib/dalli/cas/client.rb +2 -57
- data/lib/dalli/client.rb +254 -253
- data/lib/dalli/compressor.rb +13 -2
- data/lib/dalli/key_manager.rb +121 -0
- data/lib/dalli/options.rb +7 -7
- data/lib/dalli/pipelined_getter.rb +177 -0
- data/lib/dalli/protocol/base.rb +241 -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 +239 -0
- data/lib/dalli/protocol/binary/sasl_authentication.rb +60 -0
- data/lib/dalli/protocol/binary.rb +173 -0
- data/lib/dalli/protocol/connection_manager.rb +252 -0
- data/lib/dalli/protocol/meta/key_regularizer.rb +31 -0
- data/lib/dalli/protocol/meta/request_formatter.rb +121 -0
- data/lib/dalli/protocol/meta/response_processor.rb +211 -0
- data/lib/dalli/protocol/meta.rb +178 -0
- data/lib/dalli/protocol/response_buffer.rb +54 -0
- data/lib/dalli/protocol/server_config_parser.rb +86 -0
- data/lib/dalli/protocol/ttl_sanitizer.rb +45 -0
- data/lib/dalli/protocol/value_compressor.rb +85 -0
- data/lib/dalli/protocol/value_marshaller.rb +59 -0
- data/lib/dalli/protocol/value_serializer.rb +91 -0
- data/lib/dalli/protocol.rb +8 -0
- data/lib/dalli/ring.rb +97 -86
- data/lib/dalli/server.rb +4 -719
- data/lib/dalli/servers_arg_normalizer.rb +54 -0
- data/lib/dalli/socket.rb +123 -115
- data/lib/dalli/version.rb +5 -1
- data/lib/dalli.rb +45 -14
- data/lib/rack/session/dalli.rb +162 -42
- metadata +136 -63
- data/Performance.md +0 -42
- data/Rakefile +0 -43
- data/dalli.gemspec +0 -29
- data/lib/action_dispatch/middleware/session/dalli_store.rb +0 -81
- data/lib/active_support/cache/dalli_store.rb +0 -372
- data/lib/dalli/railtie.rb +0 -7
- data/test/benchmark_test.rb +0 -243
- data/test/helper.rb +0 -56
- data/test/memcached_mock.rb +0 -201
- data/test/sasl/memcached.conf +0 -1
- data/test/sasl/sasldb +0 -1
- data/test/test_active_support.rb +0 -541
- data/test/test_cas_client.rb +0 -107
- data/test/test_compressor.rb +0 -52
- data/test/test_dalli.rb +0 -682
- data/test/test_encoding.rb +0 -32
- data/test/test_failover.rb +0 -137
- data/test/test_network.rb +0 -64
- data/test/test_rack_session.rb +0 -341
- data/test/test_ring.rb +0 -85
- data/test/test_sasl.rb +0 -105
- data/test/test_serializer.rb +0 -29
- data/test/test_server.rb +0 -110
data/lib/dalli/client.rb
CHANGED
@@ -1,16 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'digest/md5'
|
2
4
|
require 'set'
|
3
5
|
|
4
6
|
# encoding: ascii
|
5
7
|
module Dalli
|
8
|
+
##
|
9
|
+
# Dalli::Client is the main class which developers will use to interact with
|
10
|
+
# Memcached.
|
11
|
+
##
|
6
12
|
class Client
|
7
|
-
|
8
13
|
##
|
9
14
|
# Dalli::Client is the main class which developers will use to interact with
|
10
15
|
# the memcached server. Usage:
|
11
16
|
#
|
12
|
-
# Dalli::Client.new(['localhost:11211:10',
|
13
|
-
#
|
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)
|
14
22
|
#
|
15
23
|
# servers is an Array of "host:port:weight" where weight allows you to distribute cache unevenly.
|
16
24
|
# Both weight and port are optional. If you pass in nil, Dalli will use the <tt>MEMCACHE_SERVERS</tt>
|
@@ -23,14 +31,25 @@ module Dalli
|
|
23
31
|
# - :namespace - prepend each key with this value to provide simple namespacing.
|
24
32
|
# - :failover - if a server is down, look for and store values on another server in the ring. Default: true.
|
25
33
|
# - :threadsafe - ensure that only one thread is actively using a socket at a time. Default: true.
|
26
|
-
# - :expires_in - default TTL in seconds if you do not pass TTL as a parameter to an individual operation, defaults
|
27
|
-
#
|
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.
|
28
40
|
# - :serializer - defaults to Marshal
|
29
|
-
# - :compressor - defaults to
|
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 - one of either :binary or :meta, defaulting to :binary. This sets the protocol that Dalli uses
|
47
|
+
# to communicate with memcached.
|
30
48
|
#
|
31
|
-
def initialize(servers=nil, options={})
|
32
|
-
@
|
49
|
+
def initialize(servers = nil, options = {})
|
50
|
+
@normalized_servers = ::Dalli::ServersArgNormalizer.normalize_servers(servers)
|
33
51
|
@options = normalize_options(options)
|
52
|
+
@key_manager = ::Dalli::KeyManager.new(@options)
|
34
53
|
@ring = nil
|
35
54
|
end
|
36
55
|
|
@@ -39,21 +58,37 @@ module Dalli
|
|
39
58
|
#
|
40
59
|
|
41
60
|
##
|
42
|
-
#
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
def multi
|
47
|
-
old, Thread.current[:dalli_multi] = Thread.current[:dalli_multi], true
|
48
|
-
yield
|
49
|
-
ensure
|
50
|
-
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)
|
51
65
|
end
|
52
66
|
|
53
67
|
##
|
54
|
-
#
|
55
|
-
|
56
|
-
|
68
|
+
# Gat (get and touch) fetch an item and simultaneously update its expiration time.
|
69
|
+
#
|
70
|
+
# If a value is not found, then +nil+ is returned.
|
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
|
+
return [value, cas] unless block_given?
|
90
|
+
|
91
|
+
yield value, cas
|
57
92
|
end
|
58
93
|
|
59
94
|
##
|
@@ -61,24 +96,52 @@ module Dalli
|
|
61
96
|
# If a block is given, yields key/value pairs one at a time.
|
62
97
|
# Otherwise returns a hash of { 'key' => 'value', 'key2' => 'value1' }
|
63
98
|
def get_multi(*keys)
|
64
|
-
|
99
|
+
keys.flatten!
|
100
|
+
keys.compact!
|
101
|
+
|
102
|
+
return {} if keys.empty?
|
103
|
+
|
65
104
|
if block_given?
|
66
|
-
|
105
|
+
pipelined_getter.process(keys) { |k, data| yield k, data.first }
|
67
106
|
else
|
68
|
-
|
69
|
-
|
107
|
+
{}.tap do |hash|
|
108
|
+
pipelined_getter.process(keys) { |k, data| hash[k] = data.first }
|
70
109
|
end
|
71
110
|
end
|
72
111
|
end
|
73
112
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
113
|
+
##
|
114
|
+
# Fetch multiple keys efficiently, including available metadata such as CAS.
|
115
|
+
# If a block is given, yields key/data pairs one a time. Data is an array:
|
116
|
+
# [value, cas_id]
|
117
|
+
# If no block is given, returns a hash of
|
118
|
+
# { 'key' => [value, cas_id] }
|
119
|
+
def get_multi_cas(*keys)
|
120
|
+
if block_given?
|
121
|
+
pipelined_getter.process(keys) { |*args| yield(*args) }
|
122
|
+
else
|
123
|
+
{}.tap do |hash|
|
124
|
+
pipelined_getter.process(keys) { |k, data| hash[k] = data }
|
125
|
+
end
|
80
126
|
end
|
81
|
-
|
127
|
+
end
|
128
|
+
|
129
|
+
# Fetch the value associated with the key.
|
130
|
+
# If a value is found, then it is returned.
|
131
|
+
#
|
132
|
+
# If a value is not found and no block is given, then nil is returned.
|
133
|
+
#
|
134
|
+
# If a value is not found (or if the found value is nil and :cache_nils is false)
|
135
|
+
# and a block is given, the block will be invoked and its return value
|
136
|
+
# written to the cache and returned.
|
137
|
+
def fetch(key, ttl = nil, req_options = nil)
|
138
|
+
req_options = req_options.nil? ? CACHE_NILS : req_options.merge(CACHE_NILS) if cache_nils
|
139
|
+
val = get(key, req_options)
|
140
|
+
return val unless block_given? && not_found?(val)
|
141
|
+
|
142
|
+
new_val = yield
|
143
|
+
add(key, new_val, ttl_or_default(ttl), req_options)
|
144
|
+
new_val
|
82
145
|
end
|
83
146
|
|
84
147
|
##
|
@@ -92,39 +155,89 @@ module Dalli
|
|
92
155
|
# - nil if the key did not exist.
|
93
156
|
# - false if the value was changed by someone else.
|
94
157
|
# - true if the value was successfully updated.
|
95
|
-
def cas(key, ttl=nil,
|
96
|
-
ttl
|
97
|
-
(value, cas) = perform(:cas, key)
|
98
|
-
value = (!value || value == 'Not found') ? nil : value
|
99
|
-
if value
|
100
|
-
newvalue = yield(value)
|
101
|
-
perform(:set, key, newvalue, ttl, cas, options)
|
102
|
-
end
|
158
|
+
def cas(key, ttl = nil, req_options = nil, &block)
|
159
|
+
cas_core(key, false, ttl, req_options, &block)
|
103
160
|
end
|
104
161
|
|
105
|
-
|
106
|
-
|
107
|
-
|
162
|
+
##
|
163
|
+
# like #cas, but will yield to the block whether or not the value
|
164
|
+
# already exists.
|
165
|
+
#
|
166
|
+
# Returns:
|
167
|
+
# - false if the value was changed by someone else.
|
168
|
+
# - true if the value was successfully updated.
|
169
|
+
def cas!(key, ttl = nil, req_options = nil, &block)
|
170
|
+
cas_core(key, true, ttl, req_options, &block)
|
171
|
+
end
|
172
|
+
|
173
|
+
##
|
174
|
+
# Turn on quiet aka noreply support for a number of
|
175
|
+
# memcached operations.
|
176
|
+
#
|
177
|
+
# All relevant operations within this block will be effectively
|
178
|
+
# pipelined as Dalli will use 'quiet' versions. The invoked methods
|
179
|
+
# will all return nil, rather than their usual response. Method
|
180
|
+
# latency will be substantially lower, as the caller will not be
|
181
|
+
# blocking on responses.
|
182
|
+
#
|
183
|
+
# Currently supports storage (set, add, replace, append, prepend),
|
184
|
+
# arithmetic (incr, decr), flush and delete operations. Use of
|
185
|
+
# unsupported operations inside a block will raise an error.
|
186
|
+
#
|
187
|
+
# Any error replies will be discarded at the end of the block, and
|
188
|
+
# Dalli client methods invoked inside the block will not
|
189
|
+
# have return values
|
190
|
+
def quiet
|
191
|
+
old = Thread.current[::Dalli::QUIET]
|
192
|
+
Thread.current[::Dalli::QUIET] = true
|
193
|
+
yield
|
194
|
+
ensure
|
195
|
+
@ring&.pipeline_consume_and_ignore_responses
|
196
|
+
Thread.current[::Dalli::QUIET] = old
|
197
|
+
end
|
198
|
+
alias multi quiet
|
199
|
+
|
200
|
+
def set(key, value, ttl = nil, req_options = nil)
|
201
|
+
set_cas(key, value, 0, ttl, req_options)
|
202
|
+
end
|
203
|
+
|
204
|
+
##
|
205
|
+
# Set the key-value pair, verifying existing CAS.
|
206
|
+
# Returns the resulting CAS value if succeeded, and falsy otherwise.
|
207
|
+
def set_cas(key, value, cas, ttl = nil, req_options = nil)
|
208
|
+
perform(:set, key, value, ttl_or_default(ttl), cas, req_options)
|
108
209
|
end
|
109
210
|
|
110
211
|
##
|
111
212
|
# Conditionally add a key/value pair, if the key does not already exist
|
112
213
|
# on the server. Returns truthy if the operation succeeded.
|
113
|
-
def add(key, value, ttl=nil,
|
114
|
-
ttl
|
115
|
-
perform(:add, key, value, ttl, options)
|
214
|
+
def add(key, value, ttl = nil, req_options = nil)
|
215
|
+
perform(:add, key, value, ttl_or_default(ttl), req_options)
|
116
216
|
end
|
117
217
|
|
118
218
|
##
|
119
219
|
# Conditionally add a key/value pair, only if the key already exists
|
120
220
|
# on the server. Returns truthy if the operation succeeded.
|
121
|
-
def replace(key, value, ttl=nil,
|
122
|
-
ttl
|
123
|
-
|
221
|
+
def replace(key, value, ttl = nil, req_options = nil)
|
222
|
+
replace_cas(key, value, 0, ttl, req_options)
|
223
|
+
end
|
224
|
+
|
225
|
+
##
|
226
|
+
# Conditionally add a key/value pair, verifying existing CAS, only if the
|
227
|
+
# key already exists on the server. Returns the new CAS value if the
|
228
|
+
# operation succeeded, or falsy otherwise.
|
229
|
+
def replace_cas(key, value, cas, ttl = nil, req_options = nil)
|
230
|
+
perform(:replace, key, value, ttl_or_default(ttl), cas, req_options)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Delete a key/value pair, verifying existing CAS.
|
234
|
+
# Returns true if succeeded, and falsy otherwise.
|
235
|
+
def delete_cas(key, cas = 0)
|
236
|
+
perform(:delete, key, cas)
|
124
237
|
end
|
125
238
|
|
126
239
|
def delete(key)
|
127
|
-
|
240
|
+
delete_cas(key, 0)
|
128
241
|
end
|
129
242
|
|
130
243
|
##
|
@@ -141,13 +254,6 @@ module Dalli
|
|
141
254
|
perform(:prepend, key, value.to_s)
|
142
255
|
end
|
143
256
|
|
144
|
-
def flush(delay=0)
|
145
|
-
time = -delay
|
146
|
-
ring.servers.map { |s| s.request(:flush, time += delay) }
|
147
|
-
end
|
148
|
-
|
149
|
-
alias_method :flush_all, :flush
|
150
|
-
|
151
257
|
##
|
152
258
|
# Incr adds the given amount to the counter on the memcached server.
|
153
259
|
# Amt must be a positive integer value.
|
@@ -159,10 +265,12 @@ module Dalli
|
|
159
265
|
# Note that the ttl will only apply if the counter does not already
|
160
266
|
# exist. To increase an existing counter and update its TTL, use
|
161
267
|
# #cas.
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
268
|
+
#
|
269
|
+
# If the value already exists, it must have been set with raw: true
|
270
|
+
def incr(key, amt = 1, ttl = nil, default = nil)
|
271
|
+
check_positive!(amt)
|
272
|
+
|
273
|
+
perform(:incr, key, amt.to_i, ttl_or_default(ttl), default)
|
166
274
|
end
|
167
275
|
|
168
276
|
##
|
@@ -179,31 +287,34 @@ module Dalli
|
|
179
287
|
# Note that the ttl will only apply if the counter does not already
|
180
288
|
# exist. To decrease an existing counter and update its TTL, use
|
181
289
|
# #cas.
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
290
|
+
#
|
291
|
+
# If the value already exists, it must have been set with raw: true
|
292
|
+
def decr(key, amt = 1, ttl = nil, default = nil)
|
293
|
+
check_positive!(amt)
|
294
|
+
|
295
|
+
perform(:decr, key, amt.to_i, ttl_or_default(ttl), default)
|
186
296
|
end
|
187
297
|
|
188
298
|
##
|
189
|
-
#
|
190
|
-
#
|
191
|
-
|
192
|
-
def
|
193
|
-
|
194
|
-
resp = perform(:touch, key, ttl)
|
195
|
-
resp.nil? ? nil : true
|
299
|
+
# Flush the memcached server, at 'delay' seconds in the future.
|
300
|
+
# Delay defaults to zero seconds, which means an immediate flush.
|
301
|
+
##
|
302
|
+
def flush(delay = 0)
|
303
|
+
ring.servers.map { |s| s.request(:flush, delay) }
|
196
304
|
end
|
305
|
+
alias flush_all flush
|
306
|
+
|
307
|
+
ALLOWED_STAT_KEYS = %i[items slabs settings].freeze
|
197
308
|
|
198
309
|
##
|
199
310
|
# Collect the stats for each server.
|
200
311
|
# You can optionally pass a type including :items, :slabs or :settings to get specific stats
|
201
312
|
# Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
|
202
|
-
def stats(type=nil)
|
203
|
-
type = nil
|
313
|
+
def stats(type = nil)
|
314
|
+
type = nil unless ALLOWED_STAT_KEYS.include? type
|
204
315
|
values = {}
|
205
316
|
ring.servers.each do |server|
|
206
|
-
values[
|
317
|
+
values[server.name.to_s] = server.alive? ? server.request(:stats, type.to_s) : nil
|
207
318
|
end
|
208
319
|
values
|
209
320
|
end
|
@@ -216,32 +327,40 @@ module Dalli
|
|
216
327
|
end
|
217
328
|
end
|
218
329
|
|
219
|
-
##
|
220
|
-
## Make sure memcache servers are alive, or raise an Dalli::RingError
|
221
|
-
def alive!
|
222
|
-
ring.server_for_key("")
|
223
|
-
end
|
224
|
-
|
225
330
|
##
|
226
331
|
## Version of the memcache servers.
|
227
332
|
def version
|
228
333
|
values = {}
|
229
334
|
ring.servers.each do |server|
|
230
|
-
values[
|
335
|
+
values[server.name.to_s] = server.alive? ? server.request(:version) : nil
|
231
336
|
end
|
232
337
|
values
|
233
338
|
end
|
234
339
|
|
340
|
+
##
|
341
|
+
## Make sure memcache servers are alive, or raise an Dalli::RingError
|
342
|
+
def alive!
|
343
|
+
ring.server_for_key('')
|
344
|
+
end
|
345
|
+
|
235
346
|
##
|
236
347
|
# Close our connection to each server.
|
237
348
|
# If you perform another operation after this, the connections will be re-established.
|
238
349
|
def close
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
350
|
+
@ring&.close
|
351
|
+
@ring = nil
|
352
|
+
end
|
353
|
+
alias reset close
|
354
|
+
|
355
|
+
CACHE_NILS = { cache_nils: true }.freeze
|
356
|
+
|
357
|
+
def not_found?(val)
|
358
|
+
cache_nils ? val == ::Dalli::NOT_FOUND : val.nil?
|
359
|
+
end
|
360
|
+
|
361
|
+
def cache_nils
|
362
|
+
@options[:cache_nils]
|
243
363
|
end
|
244
|
-
alias_method :reset, :close
|
245
364
|
|
246
365
|
# Stub method so a bare Dalli client can pretend to be a connection pool.
|
247
366
|
def with
|
@@ -250,194 +369,76 @@ module Dalli
|
|
250
369
|
|
251
370
|
private
|
252
371
|
|
253
|
-
def
|
254
|
-
|
255
|
-
begin
|
256
|
-
ring.server_for_key(key)
|
257
|
-
rescue Dalli::RingError
|
258
|
-
Dalli.logger.debug { "unable to get key #{key}" }
|
259
|
-
nil
|
260
|
-
end
|
261
|
-
end
|
262
|
-
return groups
|
263
|
-
end
|
264
|
-
|
265
|
-
def mapped_keys(keys)
|
266
|
-
keys.flatten.map {|a| validate_key(a.to_s)}
|
372
|
+
def check_positive!(amt)
|
373
|
+
raise ArgumentError, "Positive values only: #{amt}" if amt.negative?
|
267
374
|
end
|
268
375
|
|
269
|
-
def
|
270
|
-
|
271
|
-
|
272
|
-
# TODO: do this with the perform chokepoint?
|
273
|
-
# But given the fact that fetching the response doesn't take place
|
274
|
-
# in that slot it's misleading anyway. Need to move all of this method
|
275
|
-
# into perform to be meaningful
|
276
|
-
server.request(:send_multiget, keys_for_server)
|
277
|
-
rescue DalliError, NetworkError => e
|
278
|
-
Dalli.logger.debug { e.inspect }
|
279
|
-
Dalli.logger.debug { "unable to get keys for server #{server.name}" }
|
280
|
-
end
|
281
|
-
end
|
282
|
-
end
|
376
|
+
def cas_core(key, always_set, ttl = nil, req_options = nil)
|
377
|
+
(value, cas) = perform(:cas, key)
|
378
|
+
return if value.nil? && !always_set
|
283
379
|
|
284
|
-
|
285
|
-
|
286
|
-
next unless server.alive?
|
287
|
-
begin
|
288
|
-
server.multi_response_start
|
289
|
-
rescue DalliError, NetworkError => e
|
290
|
-
Dalli.logger.debug { e.inspect }
|
291
|
-
Dalli.logger.debug { "results from this server will be missing" }
|
292
|
-
servers.delete(server)
|
293
|
-
end
|
294
|
-
end
|
295
|
-
servers
|
380
|
+
newvalue = yield(value)
|
381
|
+
perform(:set, key, newvalue, ttl_or_default(ttl), cas, req_options)
|
296
382
|
end
|
297
383
|
|
298
384
|
##
|
299
|
-
#
|
300
|
-
# the
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
end
|
385
|
+
# Uses the argument TTL or the client-wide default. Ensures
|
386
|
+
# that the value is an integer
|
387
|
+
##
|
388
|
+
def ttl_or_default(ttl)
|
389
|
+
(ttl || @options[:expires_in]).to_i
|
390
|
+
rescue NoMethodError
|
391
|
+
raise ArgumentError, "Cannot convert ttl (#{ttl}) to an integer"
|
307
392
|
end
|
308
393
|
|
309
394
|
def ring
|
310
|
-
@ring ||= Dalli::Ring.new(
|
311
|
-
@servers.map do |s|
|
312
|
-
server_options = {}
|
313
|
-
if s =~ %r{\Amemcached://}
|
314
|
-
uri = URI.parse(s)
|
315
|
-
server_options[:username] = uri.user
|
316
|
-
server_options[:password] = uri.password
|
317
|
-
s = "#{uri.host}:#{uri.port}"
|
318
|
-
end
|
319
|
-
Dalli::Server.new(s, @options.merge(server_options))
|
320
|
-
end, @options
|
321
|
-
)
|
322
|
-
end
|
323
|
-
|
324
|
-
# Chokepoint method for instrumentation
|
325
|
-
def perform(*all_args, &blk)
|
326
|
-
return blk.call if blk
|
327
|
-
op, key, *args = *all_args
|
328
|
-
|
329
|
-
key = key.to_s
|
330
|
-
key = validate_key(key)
|
331
|
-
begin
|
332
|
-
server = ring.server_for_key(key)
|
333
|
-
ret = server.request(op, key, *args)
|
334
|
-
ret
|
335
|
-
rescue NetworkError => e
|
336
|
-
Dalli.logger.debug { e.inspect }
|
337
|
-
Dalli.logger.debug { "retrying request with new server" }
|
338
|
-
retry
|
339
|
-
end
|
395
|
+
@ring ||= Dalli::Ring.new(@normalized_servers, protocol_implementation, @options)
|
340
396
|
end
|
341
397
|
|
342
|
-
def
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
return key
|
398
|
+
def protocol_implementation
|
399
|
+
@protocol_implementation ||= case @options[:protocol]&.to_s
|
400
|
+
when 'meta'
|
401
|
+
Dalli::Protocol::Meta
|
402
|
+
else
|
403
|
+
Dalli::Protocol::Binary
|
404
|
+
end
|
350
405
|
end
|
351
406
|
|
352
|
-
|
353
|
-
|
354
|
-
|
407
|
+
##
|
408
|
+
# Chokepoint method for memcached methods with a key argument.
|
409
|
+
# Validates the key, resolves the key to the appropriate server
|
410
|
+
# instance, and invokes the memcached method on the appropriate
|
411
|
+
# server.
|
412
|
+
#
|
413
|
+
# This method also forces retries on network errors - when
|
414
|
+
# a particular memcached instance becomes unreachable, or the
|
415
|
+
# operational times out.
|
416
|
+
##
|
417
|
+
def perform(*all_args)
|
418
|
+
return yield if block_given?
|
355
419
|
|
356
|
-
|
357
|
-
|
358
|
-
|
420
|
+
op, key, *args = all_args
|
421
|
+
|
422
|
+
key = key.to_s
|
423
|
+
key = @key_manager.validate_key(key)
|
359
424
|
|
360
|
-
|
361
|
-
|
362
|
-
|
425
|
+
server = ring.server_for_key(key)
|
426
|
+
server.request(op, key, *args)
|
427
|
+
rescue NetworkError => e
|
428
|
+
Dalli.logger.debug { e.inspect }
|
429
|
+
Dalli.logger.debug { 'retrying request with new server' }
|
430
|
+
retry
|
363
431
|
end
|
364
432
|
|
365
433
|
def normalize_options(opts)
|
366
|
-
if opts[:
|
367
|
-
Dalli.logger.warn "DEPRECATED: Dalli's :compression option is now just :compress => true. Please update your configuration."
|
368
|
-
opts[:compress] = opts.delete(:compression)
|
369
|
-
end
|
370
|
-
begin
|
371
|
-
opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in]
|
372
|
-
rescue NoMethodError
|
373
|
-
raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
|
374
|
-
end
|
434
|
+
opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in]
|
375
435
|
opts
|
436
|
+
rescue NoMethodError
|
437
|
+
raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
|
376
438
|
end
|
377
439
|
|
378
|
-
|
379
|
-
|
380
|
-
def get_multi_yielder(keys)
|
381
|
-
perform do
|
382
|
-
return {} if keys.empty?
|
383
|
-
ring.lock do
|
384
|
-
begin
|
385
|
-
groups = groups_for_keys(keys)
|
386
|
-
if unfound_keys = groups.delete(nil)
|
387
|
-
Dalli.logger.debug { "unable to get keys for #{unfound_keys.length} keys because no matching server was found" }
|
388
|
-
end
|
389
|
-
make_multi_get_requests(groups)
|
390
|
-
|
391
|
-
servers = groups.keys
|
392
|
-
return if servers.empty?
|
393
|
-
servers = perform_multi_response_start(servers)
|
394
|
-
|
395
|
-
start = Time.now
|
396
|
-
loop do
|
397
|
-
# remove any dead servers
|
398
|
-
servers.delete_if { |s| s.sock.nil? }
|
399
|
-
break if servers.empty?
|
400
|
-
|
401
|
-
# calculate remaining timeout
|
402
|
-
elapsed = Time.now - start
|
403
|
-
timeout = servers.first.options[:socket_timeout]
|
404
|
-
if elapsed > timeout
|
405
|
-
readable = nil
|
406
|
-
else
|
407
|
-
sockets = servers.map(&:sock)
|
408
|
-
readable, _ = IO.select(sockets, nil, nil, timeout - elapsed)
|
409
|
-
end
|
410
|
-
|
411
|
-
if readable.nil?
|
412
|
-
# no response within timeout; abort pending connections
|
413
|
-
servers.each do |server|
|
414
|
-
Dalli.logger.debug { "memcached at #{server.name} did not response within timeout" }
|
415
|
-
server.multi_response_abort
|
416
|
-
end
|
417
|
-
break
|
418
|
-
|
419
|
-
else
|
420
|
-
readable.each do |sock|
|
421
|
-
server = sock.server
|
422
|
-
|
423
|
-
begin
|
424
|
-
server.multi_response_nonblock.each_pair do |key, value_list|
|
425
|
-
yield key_without_namespace(key), value_list
|
426
|
-
end
|
427
|
-
|
428
|
-
if server.multi_response_completed?
|
429
|
-
servers.delete(server)
|
430
|
-
end
|
431
|
-
rescue NetworkError
|
432
|
-
servers.delete(server)
|
433
|
-
end
|
434
|
-
end
|
435
|
-
end
|
436
|
-
end
|
437
|
-
end
|
438
|
-
end
|
439
|
-
end
|
440
|
+
def pipelined_getter
|
441
|
+
PipelinedGetter.new(ring, @key_manager)
|
440
442
|
end
|
441
|
-
|
442
443
|
end
|
443
444
|
end
|