dalli 2.0.1 → 3.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +671 -0
- data/Gemfile +15 -3
- data/LICENSE +1 -1
- data/README.md +33 -148
- data/lib/dalli/cas/client.rb +3 -0
- data/lib/dalli/client.rb +293 -131
- data/lib/dalli/compressor.rb +40 -0
- data/lib/dalli/key_manager.rb +121 -0
- data/lib/dalli/options.rb +22 -4
- data/lib/dalli/pid_cache.rb +40 -0
- data/lib/dalli/pipelined_getter.rb +177 -0
- data/lib/dalli/protocol/base.rb +250 -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 +255 -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 +19 -0
- data/lib/dalli/ring.rb +98 -50
- data/lib/dalli/server.rb +4 -524
- data/lib/dalli/servers_arg_normalizer.rb +54 -0
- data/lib/dalli/socket.rb +154 -53
- data/lib/dalli/version.rb +5 -1
- data/lib/dalli.rb +49 -13
- data/lib/rack/session/dalli.rb +169 -26
- metadata +53 -88
- data/History.md +0 -262
- data/Performance.md +0 -42
- data/Rakefile +0 -39
- data/dalli.gemspec +0 -28
- data/lib/action_dispatch/middleware/session/dalli_store.rb +0 -76
- data/lib/active_support/cache/dalli_store.rb +0 -203
- data/test/abstract_unit.rb +0 -281
- data/test/benchmark_test.rb +0 -187
- data/test/helper.rb +0 -41
- data/test/memcached_mock.rb +0 -113
- data/test/test_active_support.rb +0 -163
- data/test/test_dalli.rb +0 -461
- data/test/test_encoding.rb +0 -43
- data/test/test_failover.rb +0 -107
- data/test/test_network.rb +0 -54
- data/test/test_ring.rb +0 -85
- data/test/test_sasl.rb +0 -83
- data/test/test_session_store.rb +0 -224
data/lib/dalli/client.rb
CHANGED
@@ -1,30 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'digest/md5'
|
4
|
+
require 'set'
|
5
|
+
|
1
6
|
# encoding: ascii
|
2
7
|
module Dalli
|
8
|
+
##
|
9
|
+
# Dalli::Client is the main class which developers will use to interact with
|
10
|
+
# Memcached.
|
11
|
+
##
|
3
12
|
class Client
|
4
|
-
|
5
13
|
##
|
6
14
|
# Dalli::Client is the main class which developers will use to interact with
|
7
15
|
# the memcached server. Usage:
|
8
16
|
#
|
9
|
-
# Dalli::Client.new(['localhost:11211:10',
|
10
|
-
#
|
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)
|
11
22
|
#
|
12
23
|
# servers is an Array of "host:port:weight" where weight allows you to distribute cache unevenly.
|
13
|
-
# Both weight and port are optional. If you pass in nil, Dalli will
|
14
|
-
#
|
15
|
-
#
|
24
|
+
# Both weight and port are optional. If you pass in nil, Dalli will use the <tt>MEMCACHE_SERVERS</tt>
|
25
|
+
# environment variable or default to 'localhost:11211' if it is not present. Dalli also supports
|
26
|
+
# the ability to connect to Memcached on localhost through a UNIX socket. To use this functionality,
|
27
|
+
# use a full pathname (beginning with a slash character '/') in place of the "host:port" pair in
|
28
|
+
# the server configuration.
|
16
29
|
#
|
17
30
|
# Options:
|
18
31
|
# - :namespace - prepend each key with this value to provide simple namespacing.
|
19
32
|
# - :failover - if a server is down, look for and store values on another server in the ring. Default: true.
|
20
33
|
# - :threadsafe - ensure that only one thread is actively using a socket at a time. Default: true.
|
21
|
-
# - :expires_in - default TTL in seconds if you do not pass TTL as a parameter to an individual operation, defaults
|
22
|
-
#
|
23
|
-
#
|
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.
|
40
|
+
# - :serializer - defaults to Marshal
|
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.
|
24
48
|
#
|
25
|
-
def initialize(servers=nil, options={})
|
26
|
-
@
|
49
|
+
def initialize(servers = nil, options = {})
|
50
|
+
@normalized_servers = ::Dalli::ServersArgNormalizer.normalize_servers(servers)
|
27
51
|
@options = normalize_options(options)
|
52
|
+
@key_manager = ::Dalli::KeyManager.new(@options)
|
28
53
|
@ring = nil
|
29
54
|
end
|
30
55
|
|
@@ -33,63 +58,90 @@ module Dalli
|
|
33
58
|
#
|
34
59
|
|
35
60
|
##
|
36
|
-
#
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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)
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
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
|
45
82
|
end
|
46
83
|
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
50
92
|
end
|
51
93
|
|
52
94
|
##
|
53
95
|
# Fetch multiple keys efficiently.
|
54
|
-
#
|
96
|
+
# If a block is given, yields key/value pairs one at a time.
|
97
|
+
# Otherwise returns a hash of { 'key' => 'value', 'key2' => 'value1' }
|
55
98
|
def get_multi(*keys)
|
99
|
+
keys.flatten!
|
100
|
+
keys.compact!
|
101
|
+
|
56
102
|
return {} if keys.empty?
|
57
|
-
options = nil
|
58
|
-
options = keys.pop if keys.last.is_a?(Hash) || keys.last.nil?
|
59
|
-
ring.lock do
|
60
|
-
keys.flatten.each do |key|
|
61
|
-
begin
|
62
|
-
perform(:getkq, key)
|
63
|
-
rescue DalliError, NetworkError => e
|
64
|
-
Dalli.logger.debug { e.message }
|
65
|
-
Dalli.logger.debug { "unable to get key #{key}" }
|
66
|
-
end
|
67
|
-
end
|
68
103
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
values[key_without_namespace(key)] = value
|
75
|
-
end
|
76
|
-
rescue DalliError, NetworkError => e
|
77
|
-
Dalli.logger.debug { e.message }
|
78
|
-
Dalli.logger.debug { "results from this server will be missing" }
|
79
|
-
end
|
104
|
+
if block_given?
|
105
|
+
pipelined_getter.process(keys) { |k, data| yield k, data.first }
|
106
|
+
else
|
107
|
+
{}.tap do |hash|
|
108
|
+
pipelined_getter.process(keys) { |k, data| hash[k] = data.first }
|
80
109
|
end
|
81
|
-
values
|
82
110
|
end
|
83
111
|
end
|
84
112
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
91
126
|
end
|
92
|
-
|
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
|
93
145
|
end
|
94
146
|
|
95
147
|
##
|
@@ -103,39 +155,89 @@ module Dalli
|
|
103
155
|
# - nil if the key did not exist.
|
104
156
|
# - false if the value was changed by someone else.
|
105
157
|
# - true if the value was successfully updated.
|
106
|
-
def cas(key, ttl=nil,
|
107
|
-
ttl
|
108
|
-
(value, cas) = perform(:cas, key)
|
109
|
-
value = (!value || value == 'Not found') ? nil : value
|
110
|
-
if value
|
111
|
-
newvalue = block.call(value)
|
112
|
-
perform(:set, key, newvalue, ttl, cas, options)
|
113
|
-
end
|
158
|
+
def cas(key, ttl = nil, req_options = nil, &block)
|
159
|
+
cas_core(key, false, ttl, req_options, &block)
|
114
160
|
end
|
115
161
|
|
116
|
-
|
117
|
-
|
118
|
-
|
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)
|
119
209
|
end
|
120
210
|
|
121
211
|
##
|
122
212
|
# Conditionally add a key/value pair, if the key does not already exist
|
123
|
-
# on the server. Returns
|
124
|
-
def add(key, value, ttl=nil,
|
125
|
-
ttl
|
126
|
-
perform(:add, key, value, ttl, options)
|
213
|
+
# on the server. Returns truthy if the operation succeeded.
|
214
|
+
def add(key, value, ttl = nil, req_options = nil)
|
215
|
+
perform(:add, key, value, ttl_or_default(ttl), req_options)
|
127
216
|
end
|
128
217
|
|
129
218
|
##
|
130
219
|
# Conditionally add a key/value pair, only if the key already exists
|
131
|
-
# on the server. Returns
|
132
|
-
def replace(key, value, ttl=nil,
|
133
|
-
ttl
|
134
|
-
|
220
|
+
# on the server. Returns truthy if the operation succeeded.
|
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)
|
135
237
|
end
|
136
238
|
|
137
239
|
def delete(key)
|
138
|
-
|
240
|
+
delete_cas(key, 0)
|
139
241
|
end
|
140
242
|
|
141
243
|
##
|
@@ -152,13 +254,6 @@ module Dalli
|
|
152
254
|
perform(:prepend, key, value.to_s)
|
153
255
|
end
|
154
256
|
|
155
|
-
def flush(delay=0)
|
156
|
-
time = -delay
|
157
|
-
ring.servers.map { |s| s.request(:flush, time += delay) }
|
158
|
-
end
|
159
|
-
|
160
|
-
alias_method :flush_all, :flush
|
161
|
-
|
162
257
|
##
|
163
258
|
# Incr adds the given amount to the counter on the memcached server.
|
164
259
|
# Amt must be a positive integer value.
|
@@ -170,10 +265,12 @@ module Dalli
|
|
170
265
|
# Note that the ttl will only apply if the counter does not already
|
171
266
|
# exist. To increase an existing counter and update its TTL, use
|
172
267
|
# #cas.
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
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)
|
177
274
|
end
|
178
275
|
|
179
276
|
##
|
@@ -190,19 +287,34 @@ module Dalli
|
|
190
287
|
# Note that the ttl will only apply if the counter does not already
|
191
288
|
# exist. To decrease an existing counter and update its TTL, use
|
192
289
|
# #cas.
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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)
|
296
|
+
end
|
297
|
+
|
298
|
+
##
|
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) }
|
197
304
|
end
|
305
|
+
alias flush_all flush
|
306
|
+
|
307
|
+
ALLOWED_STAT_KEYS = %i[items slabs settings].freeze
|
198
308
|
|
199
309
|
##
|
200
310
|
# Collect the stats for each server.
|
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
|
313
|
+
def stats(type = nil)
|
314
|
+
type = nil unless ALLOWED_STAT_KEYS.include? type
|
203
315
|
values = {}
|
204
316
|
ring.servers.each do |server|
|
205
|
-
values[
|
317
|
+
values[server.name.to_s] = server.alive? ? server.request(:stats, type.to_s) : nil
|
206
318
|
end
|
207
319
|
values
|
208
320
|
end
|
@@ -215,68 +327,118 @@ module Dalli
|
|
215
327
|
end
|
216
328
|
end
|
217
329
|
|
330
|
+
##
|
331
|
+
## Version of the memcache servers.
|
332
|
+
def version
|
333
|
+
values = {}
|
334
|
+
ring.servers.each do |server|
|
335
|
+
values[server.name.to_s] = server.alive? ? server.request(:version) : nil
|
336
|
+
end
|
337
|
+
values
|
338
|
+
end
|
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
|
+
|
218
346
|
##
|
219
347
|
# Close our connection to each server.
|
220
348
|
# If you perform another operation after this, the connections will be re-established.
|
221
349
|
def close
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
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]
|
363
|
+
end
|
364
|
+
|
365
|
+
# Stub method so a bare Dalli client can pretend to be a connection pool.
|
366
|
+
def with
|
367
|
+
yield self
|
226
368
|
end
|
227
|
-
alias_method :reset, :close
|
228
369
|
|
229
370
|
private
|
230
371
|
|
231
|
-
def
|
232
|
-
|
233
|
-
Array(@servers).map do |s|
|
234
|
-
Dalli::Server.new(s, @options)
|
235
|
-
end, @options
|
236
|
-
)
|
372
|
+
def check_positive!(amt)
|
373
|
+
raise ArgumentError, "Positive values only: #{amt}" if amt.negative?
|
237
374
|
end
|
238
375
|
|
239
|
-
def
|
240
|
-
|
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
|
379
|
+
|
380
|
+
newvalue = yield(value)
|
381
|
+
perform(:set, key, newvalue, ttl_or_default(ttl), cas, req_options)
|
241
382
|
end
|
242
383
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
rescue NetworkError => e
|
252
|
-
Dalli.logger.debug { e.message }
|
253
|
-
Dalli.logger.debug { "retrying request with new server" }
|
254
|
-
retry
|
255
|
-
end
|
384
|
+
##
|
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"
|
256
392
|
end
|
257
393
|
|
258
|
-
def
|
259
|
-
|
260
|
-
raise ArgumentError, "illegal character in key #{key}" if key.respond_to?(:ascii_only?) && !key.ascii_only?
|
261
|
-
raise ArgumentError, "illegal character in key #{key}" if key =~ /[\x00-\x20\x7F-\xFF]/
|
262
|
-
raise ArgumentError, "key too long #{key.inspect}" if key.length > 250
|
394
|
+
def ring
|
395
|
+
@ring ||= Dalli::Ring.new(@normalized_servers, protocol_implementation, @options)
|
263
396
|
end
|
264
397
|
|
265
|
-
def
|
266
|
-
@
|
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
|
267
405
|
end
|
268
406
|
|
269
|
-
|
270
|
-
|
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?
|
419
|
+
|
420
|
+
op, key, *args = all_args
|
421
|
+
|
422
|
+
key = key.to_s
|
423
|
+
key = @key_manager.validate_key(key)
|
424
|
+
|
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
|
271
431
|
end
|
272
432
|
|
273
433
|
def normalize_options(opts)
|
274
|
-
if opts[:
|
275
|
-
Dalli.logger.warn "DEPRECATED: Dalli's :compression option is now just :compress => true. Please update your configuration."
|
276
|
-
opts[:compress] = opts.delete(:compression)
|
277
|
-
end
|
278
|
-
opts[:expires_in] ||= 0
|
434
|
+
opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in]
|
279
435
|
opts
|
436
|
+
rescue NoMethodError
|
437
|
+
raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
|
438
|
+
end
|
439
|
+
|
440
|
+
def pipelined_getter
|
441
|
+
PipelinedGetter.new(ring, @key_manager)
|
280
442
|
end
|
281
443
|
end
|
282
444
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zlib'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
module Dalli
|
7
|
+
##
|
8
|
+
# Default compressor used by Dalli, that uses
|
9
|
+
# Zlib DEFLATE to compress data.
|
10
|
+
##
|
11
|
+
class Compressor
|
12
|
+
def self.compress(data)
|
13
|
+
Zlib::Deflate.deflate(data)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.decompress(data)
|
17
|
+
Zlib::Inflate.inflate(data)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Alternate compressor for Dalli, that uses
|
23
|
+
# Gzip. Gzip adds a checksum to each compressed
|
24
|
+
# entry.
|
25
|
+
##
|
26
|
+
class GzipCompressor
|
27
|
+
def self.compress(data)
|
28
|
+
io = StringIO.new(+'', 'w')
|
29
|
+
gz = Zlib::GzipWriter.new(io)
|
30
|
+
gz.write(data)
|
31
|
+
gz.close
|
32
|
+
io.string
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.decompress(data)
|
36
|
+
io = StringIO.new(data, 'rb')
|
37
|
+
Zlib::GzipReader.new(io).read
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|