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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +671 -0
  3. data/Gemfile +15 -3
  4. data/LICENSE +1 -1
  5. data/README.md +33 -148
  6. data/lib/dalli/cas/client.rb +3 -0
  7. data/lib/dalli/client.rb +293 -131
  8. data/lib/dalli/compressor.rb +40 -0
  9. data/lib/dalli/key_manager.rb +121 -0
  10. data/lib/dalli/options.rb +22 -4
  11. data/lib/dalli/pid_cache.rb +40 -0
  12. data/lib/dalli/pipelined_getter.rb +177 -0
  13. data/lib/dalli/protocol/base.rb +250 -0
  14. data/lib/dalli/protocol/binary/request_formatter.rb +117 -0
  15. data/lib/dalli/protocol/binary/response_header.rb +36 -0
  16. data/lib/dalli/protocol/binary/response_processor.rb +239 -0
  17. data/lib/dalli/protocol/binary/sasl_authentication.rb +60 -0
  18. data/lib/dalli/protocol/binary.rb +173 -0
  19. data/lib/dalli/protocol/connection_manager.rb +255 -0
  20. data/lib/dalli/protocol/meta/key_regularizer.rb +31 -0
  21. data/lib/dalli/protocol/meta/request_formatter.rb +121 -0
  22. data/lib/dalli/protocol/meta/response_processor.rb +211 -0
  23. data/lib/dalli/protocol/meta.rb +178 -0
  24. data/lib/dalli/protocol/response_buffer.rb +54 -0
  25. data/lib/dalli/protocol/server_config_parser.rb +86 -0
  26. data/lib/dalli/protocol/ttl_sanitizer.rb +45 -0
  27. data/lib/dalli/protocol/value_compressor.rb +85 -0
  28. data/lib/dalli/protocol/value_marshaller.rb +59 -0
  29. data/lib/dalli/protocol/value_serializer.rb +91 -0
  30. data/lib/dalli/protocol.rb +19 -0
  31. data/lib/dalli/ring.rb +98 -50
  32. data/lib/dalli/server.rb +4 -524
  33. data/lib/dalli/servers_arg_normalizer.rb +54 -0
  34. data/lib/dalli/socket.rb +154 -53
  35. data/lib/dalli/version.rb +5 -1
  36. data/lib/dalli.rb +49 -13
  37. data/lib/rack/session/dalli.rb +169 -26
  38. metadata +53 -88
  39. data/History.md +0 -262
  40. data/Performance.md +0 -42
  41. data/Rakefile +0 -39
  42. data/dalli.gemspec +0 -28
  43. data/lib/action_dispatch/middleware/session/dalli_store.rb +0 -76
  44. data/lib/active_support/cache/dalli_store.rb +0 -203
  45. data/test/abstract_unit.rb +0 -281
  46. data/test/benchmark_test.rb +0 -187
  47. data/test/helper.rb +0 -41
  48. data/test/memcached_mock.rb +0 -113
  49. data/test/test_active_support.rb +0 -163
  50. data/test/test_dalli.rb +0 -461
  51. data/test/test_encoding.rb +0 -43
  52. data/test/test_failover.rb +0 -107
  53. data/test/test_network.rb +0 -54
  54. data/test/test_ring.rb +0 -85
  55. data/test/test_sasl.rb +0 -83
  56. 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', 'cache-2.example.com:11211:5', '192.168.0.1:22122:5'],
10
- # :threadsafe => true, :failover => true, :expires_in => 300)
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 default to 'localhost:11211'.
14
- # Note that the <tt>MEMCACHE_SERVERS</tt> environment variable will override the servers parameter for use
15
- # in managed environments like Heroku.
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 to 0 or forever
22
- # - :compress - defaults to false, if true Dalli will compress values larger than 100 bytes before
23
- # sending them to memcached.
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
- @servers = env_servers || servers || '127.0.0.1:11211'
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
- # Turn on quiet aka noreply support.
37
- # All relevant operations within this block will be effectively
38
- # pipelined as Dalli will use 'quiet' operations where possible.
39
- # Currently supports the set, add, replace and delete operations.
40
- def multi
41
- old, Thread.current[:dalli_multi] = Thread.current[:dalli_multi], true
42
- yield
43
- ensure
44
- 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)
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
- def get(key, options=nil)
48
- resp = perform(:get, key)
49
- (!resp || resp == 'Not found') ? nil : resp
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
- # Returns a hash of { 'key' => 'value', 'key2' => 'value1' }
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
- values = {}
70
- ring.servers.each do |server|
71
- next unless server.alive?
72
- begin
73
- server.request(:noop).each_pair do |key, value|
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
- def fetch(key, ttl=nil, options=nil)
86
- ttl ||= @options[:expires_in]
87
- val = get(key, options)
88
- if val.nil? && block_given?
89
- val = yield
90
- add(key, val, ttl, options)
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
- val
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, options=nil, &block)
107
- ttl ||= @options[:expires_in]
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
- def set(key, value, ttl=nil, options=nil)
117
- ttl ||= @options[:expires_in]
118
- perform(:set, key, value, ttl, 0, options)
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 true if the operation succeeded.
124
- def add(key, value, ttl=nil, options=nil)
125
- ttl ||= @options[:expires_in]
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 true if the operation succeeded.
132
- def replace(key, value, ttl=nil, options=nil)
133
- ttl ||= @options[:expires_in]
134
- perform(:replace, key, value, ttl, options)
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
- perform(:delete, key)
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
- def incr(key, amt=1, ttl=nil, default=nil)
174
- raise ArgumentError, "Positive values only: #{amt}" if amt < 0
175
- ttl ||= @options[:expires_in]
176
- perform(:incr, key, amt.to_i, ttl, default)
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
- def decr(key, amt=1, ttl=nil, default=nil)
194
- raise ArgumentError, "Positive values only: #{amt}" if amt < 0
195
- ttl ||= @options[:expires_in]
196
- perform(:decr, key, amt.to_i, ttl, default)
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["#{server.hostname}:#{server.port}"] = server.alive? ? server.request(:stats) : nil
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
- if @ring
223
- @ring.servers.each { |s| s.close }
224
- @ring = nil
225
- end
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 ring
232
- @ring ||= Dalli::Ring.new(
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 env_servers
240
- ENV['MEMCACHE_SERVERS'] ? ENV['MEMCACHE_SERVERS'].split(',') : nil
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
- # Chokepoint method for instrumentation
244
- def perform(op, key, *args)
245
- key = key.to_s
246
- validate_key(key)
247
- key = key_with_namespace(key)
248
- begin
249
- server = ring.server_for_key(key)
250
- server.request(op, key, *args)
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 validate_key(key)
259
- raise ArgumentError, "key cannot be blank" if !key || key.length == 0
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 key_with_namespace(key)
266
- @options[:namespace] ? "#{@options[:namespace]}:#{key}" : 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
267
405
  end
268
406
 
269
- def key_without_namespace(key)
270
- @options[:namespace] ? key.sub(%r(\A#{@options[:namespace]}:), '') : key
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[:compression]
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