dalli 2.7.8 → 3.2.2
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 +5 -1
- data/History.md +159 -0
- data/README.md +27 -223
- data/lib/dalli/cas/client.rb +1 -57
- data/lib/dalli/client.rb +227 -254
- data/lib/dalli/compressor.rb +12 -2
- data/lib/dalli/key_manager.rb +113 -0
- data/lib/dalli/options.rb +6 -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 +108 -0
- data/lib/dalli/protocol/meta/response_processor.rb +211 -0
- data/lib/dalli/protocol/meta.rb +177 -0
- data/lib/dalli/protocol/response_buffer.rb +54 -0
- data/lib/dalli/protocol/server_config_parser.rb +84 -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 +94 -83
- data/lib/dalli/server.rb +3 -746
- data/lib/dalli/servers_arg_normalizer.rb +54 -0
- data/lib/dalli/socket.rb +117 -137
- data/lib/dalli/version.rb +4 -1
- data/lib/dalli.rb +43 -15
- data/lib/rack/session/dalli.rb +103 -94
- metadata +64 -27
- data/lib/action_dispatch/middleware/session/dalli_store.rb +0 -82
- data/lib/active_support/cache/dalli_store.rb +0 -429
- data/lib/dalli/railtie.rb +0 -8
data/lib/dalli/client.rb
CHANGED
@@ -1,17 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'digest/md5'
|
3
4
|
require 'set'
|
4
5
|
|
5
6
|
# encoding: ascii
|
6
7
|
module Dalli
|
8
|
+
##
|
9
|
+
# Dalli::Client is the main class which developers will use to interact with
|
10
|
+
# Memcached.
|
11
|
+
##
|
7
12
|
class Client
|
8
|
-
|
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,15 +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
|
-
#
|
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.
|
29
40
|
# - :serializer - defaults to Marshal
|
30
|
-
# - :compressor - defaults to
|
31
|
-
# - :cache_nils - defaults to false, if true Dalli will not treat cached nil values as 'not found' for
|
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.
|
32
48
|
#
|
33
|
-
def initialize(servers=nil, options={})
|
34
|
-
@
|
49
|
+
def initialize(servers = nil, options = {})
|
50
|
+
@normalized_servers = ::Dalli::ServersArgNormalizer.normalize_servers(servers)
|
35
51
|
@options = normalize_options(options)
|
52
|
+
@key_manager = ::Dalli::KeyManager.new(@options)
|
36
53
|
@ring = nil
|
37
54
|
end
|
38
55
|
|
@@ -41,22 +58,37 @@ module Dalli
|
|
41
58
|
#
|
42
59
|
|
43
60
|
##
|
44
|
-
#
|
45
|
-
#
|
46
|
-
|
47
|
-
|
48
|
-
def multi
|
49
|
-
old, Thread.current[:dalli_multi] = Thread.current[:dalli_multi], true
|
50
|
-
yield
|
51
|
-
ensure
|
52
|
-
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)
|
53
65
|
end
|
54
66
|
|
55
67
|
##
|
56
|
-
#
|
68
|
+
# Gat (get and touch) fetch an item and simultaneously update its expiration time.
|
69
|
+
#
|
57
70
|
# If a value is not found, then +nil+ is returned.
|
58
|
-
def
|
59
|
-
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
|
+
return [value, cas] unless block_given?
|
90
|
+
|
91
|
+
yield value, cas
|
60
92
|
end
|
61
93
|
|
62
94
|
##
|
@@ -64,17 +96,35 @@ module Dalli
|
|
64
96
|
# If a block is given, yields key/value pairs one at a time.
|
65
97
|
# Otherwise returns a hash of { 'key' => 'value', 'key2' => 'value1' }
|
66
98
|
def get_multi(*keys)
|
67
|
-
|
99
|
+
keys.flatten!
|
100
|
+
keys.compact!
|
101
|
+
|
102
|
+
return {} if keys.empty?
|
103
|
+
|
68
104
|
if block_given?
|
69
|
-
|
105
|
+
pipelined_getter.process(keys) { |k, data| yield k, data.first }
|
70
106
|
else
|
71
|
-
|
72
|
-
|
107
|
+
{}.tap do |hash|
|
108
|
+
pipelined_getter.process(keys) { |k, data| hash[k] = data.first }
|
73
109
|
end
|
74
110
|
end
|
75
111
|
end
|
76
112
|
|
77
|
-
|
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
|
126
|
+
end
|
127
|
+
end
|
78
128
|
|
79
129
|
# Fetch the value associated with the key.
|
80
130
|
# If a value is found, then it is returned.
|
@@ -84,17 +134,14 @@ module Dalli
|
|
84
134
|
# If a value is not found (or if the found value is nil and :cache_nils is false)
|
85
135
|
# and a block is given, the block will be invoked and its return value
|
86
136
|
# written to the cache and returned.
|
87
|
-
def fetch(key, ttl=nil,
|
88
|
-
|
89
|
-
val = get(key,
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
add(key, val, ttl_or_default(ttl), options)
|
96
|
-
end
|
97
|
-
val
|
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
|
98
145
|
end
|
99
146
|
|
100
147
|
##
|
@@ -108,8 +155,8 @@ module Dalli
|
|
108
155
|
# - nil if the key did not exist.
|
109
156
|
# - false if the value was changed by someone else.
|
110
157
|
# - true if the value was successfully updated.
|
111
|
-
def cas(key, ttl=nil,
|
112
|
-
cas_core(key, false, ttl,
|
158
|
+
def cas(key, ttl = nil, req_options = nil, &block)
|
159
|
+
cas_core(key, false, ttl, req_options, &block)
|
113
160
|
end
|
114
161
|
|
115
162
|
##
|
@@ -119,30 +166,78 @@ module Dalli
|
|
119
166
|
# Returns:
|
120
167
|
# - false if the value was changed by someone else.
|
121
168
|
# - true if the value was successfully updated.
|
122
|
-
def cas!(key, ttl=nil,
|
123
|
-
cas_core(key, true, ttl,
|
169
|
+
def cas!(key, ttl = nil, req_options = nil, &block)
|
170
|
+
cas_core(key, true, ttl, req_options, &block)
|
124
171
|
end
|
125
172
|
|
126
|
-
|
127
|
-
|
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)
|
128
209
|
end
|
129
210
|
|
130
211
|
##
|
131
212
|
# Conditionally add a key/value pair, if the key does not already exist
|
132
213
|
# on the server. Returns truthy if the operation succeeded.
|
133
|
-
def add(key, value, ttl=nil,
|
134
|
-
perform(:add, key, value, ttl_or_default(ttl),
|
214
|
+
def add(key, value, ttl = nil, req_options = nil)
|
215
|
+
perform(:add, key, value, ttl_or_default(ttl), req_options)
|
135
216
|
end
|
136
217
|
|
137
218
|
##
|
138
219
|
# Conditionally add a key/value pair, only if the key already exists
|
139
220
|
# on the server. Returns truthy if the operation succeeded.
|
140
|
-
def replace(key, value, ttl=nil,
|
141
|
-
|
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)
|
142
237
|
end
|
143
238
|
|
144
239
|
def delete(key)
|
145
|
-
|
240
|
+
delete_cas(key, 0)
|
146
241
|
end
|
147
242
|
|
148
243
|
##
|
@@ -159,13 +254,6 @@ module Dalli
|
|
159
254
|
perform(:prepend, key, value.to_s)
|
160
255
|
end
|
161
256
|
|
162
|
-
def flush(delay=0)
|
163
|
-
time = -delay
|
164
|
-
ring.servers.map { |s| s.request(:flush, time += delay) }
|
165
|
-
end
|
166
|
-
|
167
|
-
alias_method :flush_all, :flush
|
168
|
-
|
169
257
|
##
|
170
258
|
# Incr adds the given amount to the counter on the memcached server.
|
171
259
|
# Amt must be a positive integer value.
|
@@ -177,8 +265,11 @@ module Dalli
|
|
177
265
|
# Note that the ttl will only apply if the counter does not already
|
178
266
|
# exist. To increase an existing counter and update its TTL, use
|
179
267
|
# #cas.
|
180
|
-
|
181
|
-
|
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
|
+
|
182
273
|
perform(:incr, key, amt.to_i, ttl_or_default(ttl), default)
|
183
274
|
end
|
184
275
|
|
@@ -196,29 +287,34 @@ module Dalli
|
|
196
287
|
# Note that the ttl will only apply if the counter does not already
|
197
288
|
# exist. To decrease an existing counter and update its TTL, use
|
198
289
|
# #cas.
|
199
|
-
|
200
|
-
|
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
|
+
|
201
295
|
perform(:decr, key, amt.to_i, ttl_or_default(ttl), default)
|
202
296
|
end
|
203
297
|
|
204
298
|
##
|
205
|
-
#
|
206
|
-
#
|
207
|
-
|
208
|
-
def
|
209
|
-
|
210
|
-
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) }
|
211
304
|
end
|
305
|
+
alias flush_all flush
|
306
|
+
|
307
|
+
ALLOWED_STAT_KEYS = %i[items slabs settings].freeze
|
212
308
|
|
213
309
|
##
|
214
310
|
# Collect the stats for each server.
|
215
311
|
# You can optionally pass a type including :items, :slabs or :settings to get specific stats
|
216
312
|
# Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
|
217
|
-
def stats(type=nil)
|
218
|
-
type = nil
|
313
|
+
def stats(type = nil)
|
314
|
+
type = nil unless ALLOWED_STAT_KEYS.include? type
|
219
315
|
values = {}
|
220
316
|
ring.servers.each do |server|
|
221
|
-
values[
|
317
|
+
values[server.name.to_s] = server.alive? ? server.request(:stats, type.to_s) : nil
|
222
318
|
end
|
223
319
|
values
|
224
320
|
end
|
@@ -231,32 +327,40 @@ module Dalli
|
|
231
327
|
end
|
232
328
|
end
|
233
329
|
|
234
|
-
##
|
235
|
-
## Make sure memcache servers are alive, or raise an Dalli::RingError
|
236
|
-
def alive!
|
237
|
-
ring.server_for_key("")
|
238
|
-
end
|
239
|
-
|
240
330
|
##
|
241
331
|
## Version of the memcache servers.
|
242
332
|
def version
|
243
333
|
values = {}
|
244
334
|
ring.servers.each do |server|
|
245
|
-
values[
|
335
|
+
values[server.name.to_s] = server.alive? ? server.request(:version) : nil
|
246
336
|
end
|
247
337
|
values
|
248
338
|
end
|
249
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
|
+
|
250
346
|
##
|
251
347
|
# Close our connection to each server.
|
252
348
|
# If you perform another operation after this, the connections will be re-established.
|
253
349
|
def close
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
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]
|
258
363
|
end
|
259
|
-
alias_method :reset, :close
|
260
364
|
|
261
365
|
# Stub method so a bare Dalli client can pretend to be a connection pool.
|
262
366
|
def with
|
@@ -265,207 +369,76 @@ module Dalli
|
|
265
369
|
|
266
370
|
private
|
267
371
|
|
268
|
-
def
|
372
|
+
def check_positive!(amt)
|
373
|
+
raise ArgumentError, "Positive values only: #{amt}" if amt.negative?
|
374
|
+
end
|
375
|
+
|
376
|
+
def cas_core(key, always_set, ttl = nil, req_options = nil)
|
269
377
|
(value, cas) = perform(:cas, key)
|
270
|
-
value = (!value || value == 'Not found') ? nil : value
|
271
378
|
return if value.nil? && !always_set
|
379
|
+
|
272
380
|
newvalue = yield(value)
|
273
|
-
perform(:set, key, newvalue, ttl_or_default(ttl), cas,
|
381
|
+
perform(:set, key, newvalue, ttl_or_default(ttl), cas, req_options)
|
274
382
|
end
|
275
383
|
|
384
|
+
##
|
385
|
+
# Uses the argument TTL or the client-wide default. Ensures
|
386
|
+
# that the value is an integer
|
387
|
+
##
|
276
388
|
def ttl_or_default(ttl)
|
277
389
|
(ttl || @options[:expires_in]).to_i
|
278
390
|
rescue NoMethodError
|
279
391
|
raise ArgumentError, "Cannot convert ttl (#{ttl}) to an integer"
|
280
392
|
end
|
281
393
|
|
282
|
-
def
|
283
|
-
|
284
|
-
begin
|
285
|
-
ring.server_for_key(key)
|
286
|
-
rescue Dalli::RingError
|
287
|
-
Dalli.logger.debug { "unable to get key #{key}" }
|
288
|
-
nil
|
289
|
-
end
|
290
|
-
end
|
291
|
-
return groups
|
292
|
-
end
|
293
|
-
|
294
|
-
def mapped_keys(keys)
|
295
|
-
keys.flatten.map {|a| validate_key(a.to_s)}
|
296
|
-
end
|
297
|
-
|
298
|
-
def make_multi_get_requests(groups)
|
299
|
-
groups.each do |server, keys_for_server|
|
300
|
-
begin
|
301
|
-
# TODO: do this with the perform chokepoint?
|
302
|
-
# But given the fact that fetching the response doesn't take place
|
303
|
-
# in that slot it's misleading anyway. Need to move all of this method
|
304
|
-
# into perform to be meaningful
|
305
|
-
server.request(:send_multiget, keys_for_server)
|
306
|
-
rescue DalliError, NetworkError => e
|
307
|
-
Dalli.logger.debug { e.inspect }
|
308
|
-
Dalli.logger.debug { "unable to get keys for server #{server.name}" }
|
309
|
-
end
|
310
|
-
end
|
394
|
+
def ring
|
395
|
+
@ring ||= Dalli::Ring.new(@normalized_servers, protocol_implementation, @options)
|
311
396
|
end
|
312
397
|
|
313
|
-
def
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
Dalli.logger.debug { "results from this server will be missing" }
|
321
|
-
servers.delete(server)
|
322
|
-
end
|
323
|
-
end
|
324
|
-
servers
|
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
|
325
405
|
end
|
326
406
|
|
327
407
|
##
|
328
|
-
#
|
329
|
-
#
|
330
|
-
#
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
end
|
338
|
-
|
339
|
-
def ring
|
340
|
-
@ring ||= Dalli::Ring.new(
|
341
|
-
@servers.map do |s|
|
342
|
-
server_options = {}
|
343
|
-
if s =~ %r{\Amemcached://}
|
344
|
-
uri = URI.parse(s)
|
345
|
-
server_options[:username] = uri.user
|
346
|
-
server_options[:password] = uri.password
|
347
|
-
s = "#{uri.host}:#{uri.port}"
|
348
|
-
end
|
349
|
-
Dalli::Server.new(s, @options.merge(server_options))
|
350
|
-
end, @options
|
351
|
-
)
|
352
|
-
end
|
353
|
-
|
354
|
-
# Chokepoint method for instrumentation
|
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
|
+
##
|
355
417
|
def perform(*all_args)
|
356
418
|
return yield if block_given?
|
357
|
-
op, key, *args = *all_args
|
358
|
-
|
359
|
-
key = key.to_s
|
360
|
-
key = validate_key(key)
|
361
|
-
begin
|
362
|
-
server = ring.server_for_key(key)
|
363
|
-
ret = server.request(op, key, *args)
|
364
|
-
ret
|
365
|
-
rescue NetworkError => e
|
366
|
-
Dalli.logger.debug { e.inspect }
|
367
|
-
Dalli.logger.debug { "retrying request with new server" }
|
368
|
-
retry
|
369
|
-
end
|
370
|
-
end
|
371
|
-
|
372
|
-
def validate_key(key)
|
373
|
-
raise ArgumentError, "key cannot be blank" if !key || key.length == 0
|
374
|
-
key = key_with_namespace(key)
|
375
|
-
if key.length > 250
|
376
|
-
max_length_before_namespace = 212 - (namespace || '').size
|
377
|
-
key = "#{key[0, max_length_before_namespace]}:md5:#{Digest::MD5.hexdigest(key)}"
|
378
|
-
end
|
379
|
-
return key
|
380
|
-
end
|
381
419
|
|
382
|
-
|
383
|
-
(ns = namespace) ? "#{ns}:#{key}" : key
|
384
|
-
end
|
420
|
+
op, key, *args = all_args
|
385
421
|
|
386
|
-
|
387
|
-
|
388
|
-
end
|
422
|
+
key = key.to_s
|
423
|
+
key = @key_manager.validate_key(key)
|
389
424
|
|
390
|
-
|
391
|
-
|
392
|
-
|
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
|
393
431
|
end
|
394
432
|
|
395
433
|
def normalize_options(opts)
|
396
|
-
if opts[:
|
397
|
-
Dalli.logger.warn "DEPRECATED: Dalli's :compression option is now just :compress => true. Please update your configuration."
|
398
|
-
opts[:compress] = opts.delete(:compression)
|
399
|
-
end
|
400
|
-
begin
|
401
|
-
opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in]
|
402
|
-
rescue NoMethodError
|
403
|
-
raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
|
404
|
-
end
|
434
|
+
opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in]
|
405
435
|
opts
|
436
|
+
rescue NoMethodError
|
437
|
+
raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
|
406
438
|
end
|
407
439
|
|
408
|
-
|
409
|
-
|
410
|
-
def get_multi_yielder(keys)
|
411
|
-
perform do
|
412
|
-
return {} if keys.empty?
|
413
|
-
ring.lock do
|
414
|
-
begin
|
415
|
-
groups = groups_for_keys(keys)
|
416
|
-
if unfound_keys = groups.delete(nil)
|
417
|
-
Dalli.logger.debug { "unable to get keys for #{unfound_keys.length} keys because no matching server was found" }
|
418
|
-
end
|
419
|
-
make_multi_get_requests(groups)
|
420
|
-
|
421
|
-
servers = groups.keys
|
422
|
-
return if servers.empty?
|
423
|
-
servers = perform_multi_response_start(servers)
|
424
|
-
|
425
|
-
start = Time.now
|
426
|
-
while true
|
427
|
-
# remove any dead servers
|
428
|
-
servers.delete_if { |s| s.sock.nil? }
|
429
|
-
break if servers.empty?
|
430
|
-
|
431
|
-
# calculate remaining timeout
|
432
|
-
elapsed = Time.now - start
|
433
|
-
timeout = servers.first.options[:socket_timeout]
|
434
|
-
time_left = (elapsed > timeout) ? 0 : timeout - elapsed
|
435
|
-
|
436
|
-
sockets = servers.map(&:sock)
|
437
|
-
readable, _ = IO.select(sockets, nil, nil, time_left)
|
438
|
-
|
439
|
-
if readable.nil?
|
440
|
-
# no response within timeout; abort pending connections
|
441
|
-
servers.each do |server|
|
442
|
-
Dalli.logger.debug { "memcached at #{server.name} did not response within timeout" }
|
443
|
-
server.multi_response_abort
|
444
|
-
end
|
445
|
-
break
|
446
|
-
|
447
|
-
else
|
448
|
-
readable.each do |sock|
|
449
|
-
server = sock.server
|
450
|
-
|
451
|
-
begin
|
452
|
-
server.multi_response_nonblock.each_pair do |key, value_list|
|
453
|
-
yield key_without_namespace(key), value_list
|
454
|
-
end
|
455
|
-
|
456
|
-
if server.multi_response_completed?
|
457
|
-
servers.delete(server)
|
458
|
-
end
|
459
|
-
rescue NetworkError
|
460
|
-
servers.delete(server)
|
461
|
-
end
|
462
|
-
end
|
463
|
-
end
|
464
|
-
end
|
465
|
-
end
|
466
|
-
end
|
467
|
-
end
|
440
|
+
def pipelined_getter
|
441
|
+
PipelinedGetter.new(ring, @key_manager)
|
468
442
|
end
|
469
|
-
|
470
443
|
end
|
471
444
|
end
|
data/lib/dalli/compressor.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'zlib'
|
3
4
|
require 'stringio'
|
4
5
|
|
5
6
|
module Dalli
|
7
|
+
##
|
8
|
+
# Default compressor used by Dalli, that uses
|
9
|
+
# Zlib DEFLATE to compress data.
|
10
|
+
##
|
6
11
|
class Compressor
|
7
12
|
def self.compress(data)
|
8
13
|
Zlib::Deflate.deflate(data)
|
@@ -13,9 +18,14 @@ module Dalli
|
|
13
18
|
end
|
14
19
|
end
|
15
20
|
|
21
|
+
##
|
22
|
+
# Alternate compressor for Dalli, that uses
|
23
|
+
# Gzip. Gzip adds a checksum to each compressed
|
24
|
+
# entry.
|
25
|
+
##
|
16
26
|
class GzipCompressor
|
17
27
|
def self.compress(data)
|
18
|
-
io = StringIO.new(
|
28
|
+
io = StringIO.new(+'', 'w')
|
19
29
|
gz = Zlib::GzipWriter.new(io)
|
20
30
|
gz.write(data)
|
21
31
|
gz.close
|
@@ -23,7 +33,7 @@ module Dalli
|
|
23
33
|
end
|
24
34
|
|
25
35
|
def self.decompress(data)
|
26
|
-
io = StringIO.new(data,
|
36
|
+
io = StringIO.new(data, 'rb')
|
27
37
|
Zlib::GzipReader.new(io).read
|
28
38
|
end
|
29
39
|
end
|