dalli 2.7.6 → 2.7.11

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: aa780eecd8063c6371f755a081c672bee694c596
4
- data.tar.gz: f044315c06b99f3ff10da4c4a37a4b77cce359da
2
+ SHA256:
3
+ metadata.gz: 4bc4fe873fe9319c9297219b7908d576470551d0e011d1299e54ebc162256a68
4
+ data.tar.gz: b60393783d05152ad67aa12298f6867127f973fbf07cd2d0dc90ab96b1d7971f
5
5
  SHA512:
6
- metadata.gz: 962196b3d7c2a75b928f32e896106ead0316b6d2614f1176041a3751c82816e5669a70d9d293ef1762df190bade229955b20c5673562600575774b3bfed6c978
7
- data.tar.gz: c890f77ce55953a3f0fe47629ddb1eda40ece7b58628c7168e45c3733c5120e72c7dba992f6b15267ea5025283bf8f5c2e213648c65e6a87131219bfe318f5a1
6
+ metadata.gz: 1348b528243f253c111ad74e17a0ba20d8d14eb29d4f41e15f60bb2c87505c6d09404217c868f1623bd68f1ad70a644efd09fd1acca0ba2a1d0b6395387967ae
7
+ data.tar.gz: 108c5477c6399ef838a8df14e4878509ec5f7c22c33d012c19a9deb635901e9251a52c392c080fd6eb65dfb54e8084ee70cf6668a6914fd1336120aabb19b4f9
data/Gemfile CHANGED
@@ -2,4 +2,14 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
+ gem 'appraisal'
5
6
  gem 'kgio', :platform => :mri
7
+ gem 'rails'
8
+
9
+ group :test do
10
+ gem 'minitest'
11
+ gem 'mocha'
12
+ gem 'rake'
13
+ gem 'connection_pool'
14
+ gem 'simplecov'
15
+ end
data/History.md CHANGED
@@ -1,6 +1,44 @@
1
1
  Dalli Changelog
2
2
  =====================
3
3
 
4
+ 2.7.11
5
+ ==========
6
+ - DEPRECATION: :dalli_store will be removed in Dalli 3.0.
7
+ Use Rails' official :mem_cache_store instead.
8
+ https://guides.rubyonrails.org/caching_with_rails.html
9
+ - Add new `digest_class` option to Dalli::Client [#724]
10
+ - Don't treat NameError as a network error [#728]
11
+ - Handle nested comma separated server strings (sambostock)
12
+
13
+ 2.7.10
14
+ ==========
15
+ - Revert frozen string change (schneems)
16
+ - Advertise supports_cached_versioning? in DalliStore (schneems)
17
+ - Better detection of fork support, to allow specs to run under Truffle Ruby (deepj)
18
+ - Update logging for over max size to log as error (aeroastro)
19
+
20
+ 2.7.9
21
+ ==========
22
+ - Fix behavior for Rails 5.2+ cache_versioning (GriwMF)
23
+ - Ensure fetch provides the key to the fallback block as an argument (0exp)
24
+ - Assorted performance improvements (schneems)
25
+
26
+ 2.7.8
27
+ ==========
28
+ - Rails 5.2 compatibility (pbougie)
29
+ - Fix Session Cache compatibility (pixeltrix)
30
+
31
+ 2.7.7
32
+ ==========
33
+ - Support large cache keys on fetch multi (sobrinho)
34
+ - Not found checks no longer trigger the result's equality method (dannyfallon)
35
+ - Use SVG build badges (olleolleolle)
36
+ - Travis updates (junaruga, tiarly, petergoldstein)
37
+ - Update default down_retry_delay (jaredhales)
38
+ - Close kgio socket after IO.select timeouts
39
+ - Documentation updates (tipair)
40
+ - Instrument DalliStore errors with instrument_errors configuration option. (btatnall)
41
+
4
42
  2.7.6
5
43
  ==========
6
44
  - Rails 5.0.0.beta2 compatibility (yui-knk, petergoldstein)
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- Dalli [![Build Status](https://secure.travis-ci.org/petergoldstein/dalli.png)](http://travis-ci.org/petergoldstein/dalli) [![Dependency Status](https://gemnasium.com/petergoldstein/dalli.png)](https://gemnasium.com/petergoldstein/dalli) [![Code Climate](https://codeclimate.com/github/petergoldstein/dalli.png)](https://codeclimate.com/github/petergoldstein/dalli)
1
+ Dalli [![Build Status](https://secure.travis-ci.org/petergoldstein/dalli.svg)](http://travis-ci.org/petergoldstein/dalli)
2
2
  =====
3
3
 
4
4
  Dalli is a high performance pure Ruby client for accessing memcached servers. It works with memcached 1.4+ only as it uses the newer binary protocol. It should be considered a replacement for the memcache-client gem.
@@ -9,26 +9,6 @@ The name is a variant of Salvador Dali for his famous painting [The Persistence
9
9
 
10
10
  Dalli's initial development was sponsored by [CouchBase](http://www.couchbase.com/). Many thanks to them!
11
11
 
12
-
13
- Design
14
- ------------
15
-
16
- Mike Perham decided to write Dalli after maintaining memcache-client for two years for a few specific reasons:
17
-
18
- 0. The code is mostly old and gross. The bulk of the code is a single 1000 line .rb file.
19
- 1. It has a lot of options that are infrequently used which complicate the codebase.
20
- 2. The implementation has no single point to attach monitoring hooks.
21
- 3. Uses the old text protocol, which hurts raw performance.
22
-
23
- So a few notes. Dalli:
24
-
25
- 0. uses the exact same algorithm to choose a server so existing memcached clusters with TBs of data will work identically to memcache-client.
26
- 1. is approximately 20% faster than memcache-client (which itself was heavily optimized) in Ruby 1.9.2.
27
- 2. contains explicit "chokepoint" methods which handle all requests; these can be hooked into by monitoring tools (NewRelic, Rack::Bug, etc) to track memcached usage.
28
- 3. supports SASL for use in managed environments, e.g. Heroku.
29
- 4. provides proper failover with recovery and adjustable timeouts
30
-
31
-
32
12
  Supported Ruby versions and implementations
33
13
  ------------------------------------------------
34
14
 
@@ -66,11 +46,9 @@ dc.set('abc', 123)
66
46
  value = dc.get('abc')
67
47
  ```
68
48
 
69
- The test suite requires memcached 1.4.3+ with SASL enabled (brew install memcached --enable-sasl ; mv /usr/bin/memcached /usr/bin/memcached.old). Currently only supports the PLAIN mechanism.
70
-
71
- Dalli has no runtime dependencies and never will. You can optionally install the 'kgio' gem to
72
- give Dalli a 20-30% performance boost.
49
+ The test suite requires memcached 1.4.3+ with SASL enabled (`brew install memcached --enable-sasl ; mv /usr/bin/memcached /usr/bin/memcached.old`). Currently only supports the PLAIN mechanism.
73
50
 
51
+ Dalli has no runtime dependencies.
74
52
 
75
53
  Usage with Rails 3.x and 4.x
76
54
  ---------------------------
@@ -90,10 +68,12 @@ config.cache_store = :dalli_store
90
68
  Here's a more comprehensive example that sets a reasonable default for maximum cache entry lifetime (one day), enables compression for large values and namespaces all entries for this rails app. Remove the namespace if you have multiple apps which share cached values.
91
69
 
92
70
  ```ruby
93
- config.cache_store = :dalli_store, 'cache-1.example.com', 'cache-2.example.com',
71
+ config.cache_store = :dalli_store, 'cache-1.example.com', 'cache-2.example.com:11211:2',
94
72
  { :namespace => NAME_OF_RAILS_APP, :expires_in => 1.day, :compress => true }
95
73
  ```
96
74
 
75
+ You can specify a port and a weight by appending to the server name. You may wish to increase the weight of a server with more memory configured. (e.g. to specify port 11211 and a weight of 2, append `:11211:2` )
76
+
97
77
  If your servers are specified in `ENV["MEMCACHE_SERVERS"]` (e.g. on Heroku when using a third-party hosted addon), simply provide `nil` for the servers:
98
78
 
99
79
  ```ruby
@@ -147,6 +127,8 @@ end
147
127
  Configuration
148
128
  ------------------------
149
129
 
130
+ **servers**: An Array of "host:port:weight" where weight allows you to distribute cache unevenly.
131
+
150
132
  Dalli::Client accepts the following options. All times are in seconds.
151
133
 
152
134
  **expires_in**: Global default for key TTL. Default is 0, which means no expiry.
@@ -155,7 +137,7 @@ Dalli::Client accepts the following options. All times are in seconds.
155
137
 
156
138
  **failover**: Boolean, if true Dalli will failover to another server if the main server for a key is down. Default is true.
157
139
 
158
- **threadsafe**: Boolean. If true Dalli ensures that only one thread is using a socket at a given time. Default is true. Set to false at your own peril.
140
+ **threadsafe**: Boolean. If true Dalli ensures that only one thread is using a socket at a given time. Default is true. You can set to false if you are using the Client within a thread-safe connection pool.
159
141
 
160
142
  **serializer**: The serializer to use for objects being stored (ex. JSON).
161
143
  Default is Marshal.
@@ -178,10 +160,12 @@ If serving compressed data using nginx's HttpMemcachedModule, set `memcached_gzi
178
160
 
179
161
  **socket_failure_delay**: Before retrying a socket operation, the process sleeps for this amount of time. Default is 0.01. Set to nil for no delay.
180
162
 
181
- **down_retry_delay**: When a server has been marked down due to many failures, the server will be checked again for being alive only after this amount of time. Don't set this value too low, otherwise each request which tries the failed server might hang for the maximum **socket_timeout**. Default is 1 second.
163
+ **down_retry_delay**: When a server has been marked down due to many failures, the server will be checked again for being alive only after this amount of time. Don't set this value too low, otherwise each request which tries the failed server might hang for the maximum **socket_timeout**. Default is 60 seconds.
182
164
 
183
165
  **value_max_bytes**: The maximum size of a value in memcached. Defaults to 1MB, this can be increased with memcached's -I parameter. You must also configure Dalli to allow the larger size here.
184
166
 
167
+ **error_when_over_max_size**: Boolean. If true, Dalli will throw a Dalli::ValueOverMaxSize exception when trying to store data larger than **value_max_bytes**. Defaults to false, meaning only a warning is logged.
168
+
185
169
  **username**: The username to use for authenticating this client instance against a SASL-enabled memcached server. Heroku users should not need to use this normally.
186
170
 
187
171
  **password**: The password to use for authenticating this client instance against a SASL-enabled memcached server. Heroku users should not need to use this normally.
@@ -192,10 +176,14 @@ If serving compressed data using nginx's HttpMemcachedModule, set `memcached_gzi
192
176
 
193
177
  **cache_nils**: Boolean. If true Dalli will not treat cached `nil` values as 'not found' for `#fetch` operations. Default is false.
194
178
 
179
+ **raise_errors**: Boolean. When true DalliStore will reraise Dalli:DalliError instead swallowing the error. Default is false.
180
+
181
+ **instrument_errors**: Boolean. When true DalliStore will send notification of Dalli::DalliError via a 'cache_error.active_support' event. Default is false.
182
+
195
183
  Features and Changes
196
184
  ------------------------
197
185
 
198
- By default, Dalli is thread-safe. Disable thread-safety at your own peril.
186
+ By default, Dalli is thread-safe. Disable thread-safety at your own peril.
199
187
 
200
188
  Dalli does not need anything special in Unicorn/Passenger since 2.0.4.
201
189
  It will detect sockets shared with child processes and gracefully reopen the
@@ -208,7 +196,7 @@ Note that Dalli does not require ActiveSupport or Rails. You can safely use it
208
196
  Helping Out
209
197
  -------------
210
198
 
211
- If you have a fix you wish to provide, please fork the code, fix in your local project and then send a pull request on github. Please ensure that you include a test which verifies your fix and update History.md with a one sentence description of your fix so you get credit as a contributor.
199
+ If you have a fix you wish to provide, please fork the code, fix in your local project and then send a pull request on github. Please ensure that you include a test which verifies your fix and update `History.md` with a one sentence description of your fix so you get credit as a contributor.
212
200
 
213
201
  We're not accepting new compressors. They are trivial to add in an initializer. See #385 (LZ4), #406 (Snappy)
214
202
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'active_support/cache'
2
3
  require 'action_dispatch/middleware/session/abstract_store'
3
4
  require 'dalli'
@@ -1,4 +1,5 @@
1
1
  # encoding: ascii
2
+ # frozen_string_literal: true
2
3
  require 'dalli'
3
4
 
4
5
  module ActiveSupport
@@ -8,6 +9,10 @@ module ActiveSupport
8
9
  attr_reader :silence, :options
9
10
  alias_method :silence?, :silence
10
11
 
12
+ def self.supports_cache_versioning?
13
+ true
14
+ end
15
+
11
16
  # Silence the logger.
12
17
  def silence!
13
18
  @silence = true
@@ -45,6 +50,11 @@ module ActiveSupport
45
50
  # wish to use pool support.
46
51
  #
47
52
  def initialize(*addresses)
53
+ puts <<-EOS
54
+ DEPRECATION: :dalli_store will be removed in Dalli 3.0.
55
+ Please use Rails' official :mem_cache_store instead.
56
+ https://guides.rubyonrails.org/caching_with_rails.html
57
+ EOS
48
58
  addresses = addresses.flatten
49
59
  options = addresses.extract_options!
50
60
  @options = options.dup
@@ -98,24 +108,24 @@ module ActiveSupport
98
108
  if block_given?
99
109
  entry = not_found
100
110
  unless options[:force]
101
- entry = instrument(:read, namespaced_name, options) do |payload|
111
+ entry = instrument_with_log(:read, namespaced_name, options) do |payload|
102
112
  read_entry(namespaced_name, options).tap do |result|
103
113
  if payload
104
114
  payload[:super_operation] = :fetch
105
- payload[:hit] = result != not_found
115
+ payload[:hit] = not_found != result
106
116
  end
107
117
  end
108
118
  end
109
119
  end
110
120
 
111
- if entry == not_found
112
- result = instrument(:generate, namespaced_name, options) do |payload|
113
- yield
121
+ if not_found == entry
122
+ result = instrument_with_log(:generate, namespaced_name, options) do |payload|
123
+ yield(name)
114
124
  end
115
125
  write(name, result, options)
116
126
  result
117
127
  else
118
- instrument(:fetch_hit, namespaced_name, options) { |payload| }
128
+ instrument_with_log(:fetch_hit, namespaced_name, options) { |payload| }
119
129
  entry
120
130
  end
121
131
  else
@@ -127,7 +137,7 @@ module ActiveSupport
127
137
  options ||= {}
128
138
  name = namespaced_key(name, options)
129
139
 
130
- instrument(:read, name, options) do |payload|
140
+ instrument_with_log(:read, name, options) do |payload|
131
141
  entry = read_entry(name, options)
132
142
  payload[:hit] = !entry.nil? if payload
133
143
  entry
@@ -138,7 +148,7 @@ module ActiveSupport
138
148
  options ||= {}
139
149
  name = namespaced_key(name, options)
140
150
 
141
- instrument(:write, name, options) do |payload|
151
+ instrument_with_log(:write, name, options) do |payload|
142
152
  with do |connection|
143
153
  options = options.merge(:connection => connection)
144
154
  write_entry(name, value, options)
@@ -158,7 +168,7 @@ module ActiveSupport
158
168
  options ||= {}
159
169
  name = namespaced_key(name, options)
160
170
 
161
- instrument(:delete, name, options) do |payload|
171
+ instrument_with_log(:delete, name, options) do |payload|
162
172
  delete_entry(name, options)
163
173
  end
164
174
  end
@@ -168,7 +178,7 @@ module ActiveSupport
168
178
  def read_multi(*names)
169
179
  options = names.extract_options!
170
180
  mapping = names.inject({}) { |memo, name| memo[namespaced_key(name, options)] = name; memo }
171
- instrument(:read_multi, mapping.keys) do
181
+ instrument_with_log(:read_multi, mapping.keys) do
172
182
  results = {}
173
183
  if local_cache
174
184
  mapping.each_key do |key|
@@ -199,7 +209,7 @@ module ActiveSupport
199
209
  options = names.extract_options!
200
210
  mapping = names.inject({}) { |memo, name| memo[namespaced_key(name, options)] = name; memo }
201
211
 
202
- instrument(:fetch_multi, mapping.keys) do
212
+ instrument_with_log(:fetch_multi, mapping.keys) do
203
213
  with do |connection|
204
214
  results = connection.get_multi(mapping.keys)
205
215
 
@@ -230,11 +240,12 @@ module ActiveSupport
230
240
  name = namespaced_key(name, options)
231
241
  initial = options.has_key?(:initial) ? options[:initial] : amount
232
242
  expires_in = options[:expires_in]
233
- instrument(:increment, name, :amount => amount) do
243
+ instrument_with_log(:increment, name, :amount => amount) do
234
244
  with { |c| c.incr(name, amount, expires_in, initial) }
235
245
  end
236
246
  rescue Dalli::DalliError => e
237
- logger.error("DalliError: #{e.message}") if logger
247
+ log_dalli_error(e)
248
+ instrument_error(e) if instrument_errors?
238
249
  raise if raise_errors?
239
250
  nil
240
251
  end
@@ -249,11 +260,12 @@ module ActiveSupport
249
260
  name = namespaced_key(name, options)
250
261
  initial = options.has_key?(:initial) ? options[:initial] : 0
251
262
  expires_in = options[:expires_in]
252
- instrument(:decrement, name, :amount => amount) do
263
+ instrument_with_log(:decrement, name, :amount => amount) do
253
264
  with { |c| c.decr(name, amount, expires_in, initial) }
254
265
  end
255
266
  rescue Dalli::DalliError => e
256
- logger.error("DalliError: #{e.message}") if logger
267
+ log_dalli_error(e)
268
+ instrument_error(e) if instrument_errors?
257
269
  raise if raise_errors?
258
270
  nil
259
271
  end
@@ -261,11 +273,12 @@ module ActiveSupport
261
273
  # Clear the entire cache on all memcached servers. This method should
262
274
  # be used with care when using a shared cache.
263
275
  def clear(options=nil)
264
- instrument(:clear, 'flushing all keys') do
276
+ instrument_with_log(:clear, 'flushing all keys') do
265
277
  with { |c| c.flush_all }
266
278
  end
267
279
  rescue Dalli::DalliError => e
268
- logger.error("DalliError: #{e.message}") if logger
280
+ log_dalli_error(e)
281
+ instrument_error(e) if instrument_errors?
269
282
  raise if raise_errors?
270
283
  nil
271
284
  end
@@ -299,7 +312,8 @@ module ActiveSupport
299
312
  # NB Backwards data compatibility, to be removed at some point
300
313
  entry.is_a?(ActiveSupport::Cache::Entry) ? entry.value : entry
301
314
  rescue Dalli::DalliError => e
302
- logger.error("DalliError: #{e.message}") if logger
315
+ log_dalli_error(e)
316
+ instrument_error(e) if instrument_errors?
303
317
  raise if raise_errors?
304
318
  nil
305
319
  end
@@ -313,7 +327,8 @@ module ActiveSupport
313
327
  connection = options.delete(:connection)
314
328
  connection.send(method, key, value, expires_in, options)
315
329
  rescue Dalli::DalliError => e
316
- logger.error("DalliError: #{e.message}") if logger
330
+ log_dalli_error(e)
331
+ instrument_error(e) if instrument_errors?
317
332
  raise if raise_errors?
318
333
  false
319
334
  end
@@ -322,7 +337,8 @@ module ActiveSupport
322
337
  def delete_entry(key, options) # :nodoc:
323
338
  with { |c| c.delete(key) }
324
339
  rescue Dalli::DalliError => e
325
- logger.error("DalliError: #{e.message}") if logger
340
+ log_dalli_error(e)
341
+ instrument_error(e) if instrument_errors?
326
342
  raise if raise_errors?
327
343
  false
328
344
  end
@@ -330,18 +346,22 @@ module ActiveSupport
330
346
  private
331
347
 
332
348
  def namespaced_key(key, options)
349
+ digest_class = @options[:digest_class] || ::Digest::MD5
333
350
  key = expanded_key(key)
334
351
  namespace = options[:namespace] if options
335
352
  prefix = namespace.is_a?(Proc) ? namespace.call : namespace
336
353
  key = "#{prefix}:#{key}" if prefix
354
+ key = "#{key[0, 213]}:md5:#{digest_class.hexdigest(key)}" if key && key.size > 250
337
355
  key
338
356
  end
339
357
  alias :normalize_key :namespaced_key
340
358
 
341
- # Expand key to be a consistent string value. Invoke +cache_key+ if
342
- # object responds to +cache_key+. Otherwise, to_param method will be
343
- # called. If the key is a Hash, then keys will be sorted alphabetically.
359
+ # Expand key to be a consistent string value. Invokes +cache_key_with_version+
360
+ # first to support Rails 5.2 cache versioning.
361
+ # Invoke +cache_key+ if object responds to +cache_key+. Otherwise, to_param method
362
+ # will be called. If the key is a Hash, then keys will be sorted alphabetically.
344
363
  def expanded_key(key) # :nodoc:
364
+ return key.cache_key_with_version.to_s if key.respond_to?(:cache_key_with_version)
345
365
  return key.cache_key.to_s if key.respond_to?(:cache_key)
346
366
 
347
367
  case key
@@ -363,12 +383,26 @@ module ActiveSupport
363
383
  key
364
384
  end
365
385
 
366
- def instrument(operation, key, options=nil)
386
+ def log_dalli_error(error)
387
+ logger.error("DalliError: #{error.message}") if logger
388
+ end
389
+
390
+ def instrument_with_log(operation, key, options=nil)
367
391
  log(operation, key, options)
368
392
 
369
393
  payload = { :key => key }
370
394
  payload.merge!(options) if options.is_a?(Hash)
371
- ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
395
+ instrument(operation, payload) { |p| yield(p) }
396
+ end
397
+
398
+ def instrument_error(error)
399
+ instrument(:error, { :key => 'DalliError', :message => error.message })
400
+ end
401
+
402
+ def instrument(operation, payload)
403
+ ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) do
404
+ yield(payload) if block_given?
405
+ end
372
406
  end
373
407
 
374
408
  def log(operation, key, options=nil)
@@ -380,12 +414,16 @@ module ActiveSupport
380
414
  !!@options[:raise_errors]
381
415
  end
382
416
 
417
+ def instrument_errors?
418
+ !!@options[:instrument_errors]
419
+ end
420
+
383
421
  # Make sure LocalCache is giving raw values, not `Entry`s, and
384
422
  # respect `raw` option.
385
423
  module LocalCacheEntryUnwrapAndRaw # :nodoc:
386
424
  protected
387
425
  def read_entry(key, options)
388
- retval = super
426
+ retval = super(key, **options)
389
427
  if retval.is_a? ActiveSupport::Cache::Entry
390
428
  # Must have come from LocalStore, unwrap it
391
429
  if options[:raw]
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'dalli/compressor'
2
3
  require 'dalli/client'
3
4
  require 'dalli/ring'
@@ -18,6 +19,8 @@ module Dalli
18
19
  class MarshalError < DalliError; end
19
20
  # application error in marshalling deserialization or decompression
20
21
  class UnmarshalError < DalliError; end
22
+ # payload too big for memcached
23
+ class ValueOverMaxSize < DalliError; end
21
24
 
22
25
  def self.logger
23
26
  @logger ||= (rails_logger || default_logger)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'dalli/client'
2
3
 
3
4
  module Dalli
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'digest/md5'
2
3
  require 'set'
3
4
 
@@ -28,6 +29,7 @@ module Dalli
28
29
  # - :serializer - defaults to Marshal
29
30
  # - :compressor - defaults to zlib
30
31
  # - :cache_nils - defaults to false, if true Dalli will not treat cached nil values as 'not found' for #fetch operations.
32
+ # - :digest_class - defaults to Digest::MD5, allows you to pass in an object that responds to the hexdigest method, useful for injecting a FIPS compliant hash object.
31
33
  #
32
34
  def initialize(servers=nil, options={})
33
35
  @servers = normalize_servers(servers || ENV["MEMCACHE_SERVERS"] || '127.0.0.1:11211')
@@ -63,7 +65,10 @@ module Dalli
63
65
  # If a block is given, yields key/value pairs one at a time.
64
66
  # Otherwise returns a hash of { 'key' => 'value', 'key2' => 'value1' }
65
67
  def get_multi(*keys)
66
- return {} if keys.flatten.compact.empty?
68
+ check_keys = keys.flatten
69
+ check_keys.compact!
70
+
71
+ return {} if check_keys.empty?
67
72
  if block_given?
68
73
  get_multi_yielder(keys) {|k, data| yield k, data.first}
69
74
  else
@@ -291,7 +296,9 @@ module Dalli
291
296
  end
292
297
 
293
298
  def mapped_keys(keys)
294
- keys.flatten.map {|a| validate_key(a.to_s)}
299
+ keys_array = keys.flatten
300
+ keys_array.map! { |a| validate_key(a.to_s) }
301
+ keys_array
295
302
  end
296
303
 
297
304
  def make_multi_get_requests(groups)
@@ -325,13 +332,15 @@ module Dalli
325
332
 
326
333
  ##
327
334
  # Normalizes the argument into an array of servers.
328
- # If the argument is a string, it's expected that the URIs are comma separated e.g.
335
+ # If the argument is a string, or an array containing strings, it's expected that the URIs are comma separated e.g.
329
336
  # "memcache1.example.com:11211,memcache2.example.com:11211,memcache3.example.com:11211"
330
337
  def normalize_servers(servers)
331
- if servers.is_a? String
332
- return servers.split(",")
333
- else
334
- return servers
338
+ Array(servers).flat_map do |server|
339
+ if server.is_a? String
340
+ server.split(",")
341
+ else
342
+ server
343
+ end
335
344
  end
336
345
  end
337
346
 
@@ -372,8 +381,9 @@ module Dalli
372
381
  raise ArgumentError, "key cannot be blank" if !key || key.length == 0
373
382
  key = key_with_namespace(key)
374
383
  if key.length > 250
384
+ digest_class = @options[:digest_class] || ::Digest::MD5
375
385
  max_length_before_namespace = 212 - (namespace || '').size
376
- key = "#{key[0, max_length_before_namespace]}:md5:#{Digest::MD5.hexdigest(key)}"
386
+ key = "#{key[0, max_length_before_namespace]}:md5:#{digest_class.hexdigest(key)}"
377
387
  end
378
388
  return key
379
389
  end
@@ -401,6 +411,9 @@ module Dalli
401
411
  rescue NoMethodError
402
412
  raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
403
413
  end
414
+ if opts[:digest_class] && !opts[:digest_class].respond_to?(:hexdigest)
415
+ raise ArgumentError, "The digest_class object must respond to the hexdigest method"
416
+ end
404
417
  opts
405
418
  end
406
419
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'zlib'
2
3
  require 'stringio'
3
4
 
@@ -14,7 +15,7 @@ module Dalli
14
15
 
15
16
  class GzipCompressor
16
17
  def self.compress(data)
17
- io = StringIO.new("w")
18
+ io = StringIO.new(String.new(""), "w")
18
19
  gz = Zlib::GzipWriter.new(io)
19
20
  gz.write(data)
20
21
  gz.close
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'thread'
2
3
  require 'monitor'
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dalli
2
3
  class Railtie < ::Rails::Railtie
3
4
  config.before_configuration do
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'digest/sha1'
2
3
  require 'zlib'
3
4
 
@@ -20,7 +21,7 @@ module Dalli
20
21
  continuum << Dalli::Ring::Entry.new(value, server)
21
22
  end
22
23
  end
23
- @continuum = continuum.sort { |a, b| a.value <=> b.value }
24
+ @continuum = continuum.sort_by(&:value)
24
25
  end
25
26
 
26
27
  threadsafe! unless options[:threadsafe] == false
@@ -110,7 +111,6 @@ module Dalli
110
111
  def binary_search(ary, value)
111
112
  upper = ary.size - 1
112
113
  lower = 0
113
- idx = 0
114
114
 
115
115
  while (lower <= upper) do
116
116
  idx = (lower + upper) / 2
@@ -124,7 +124,7 @@ module Dalli
124
124
  lower = idx + 1
125
125
  end
126
126
  end
127
- return upper
127
+ upper
128
128
  end
129
129
  end
130
130
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'socket'
2
3
  require 'timeout'
3
4
 
@@ -14,7 +15,7 @@ module Dalli
14
15
  DEFAULT_WEIGHT = 1
15
16
  DEFAULTS = {
16
17
  # seconds between trying to contact a remote server
17
- :down_retry_delay => 1,
18
+ :down_retry_delay => 60,
18
19
  # connect/read/write timeout for socket operations
19
20
  :socket_timeout => 0.5,
20
21
  # times a socket operation may fail before considering the server dead
@@ -23,6 +24,8 @@ module Dalli
23
24
  :socket_failure_delay => 0.01,
24
25
  # max size of value in bytes (default is 1 MB, can be overriden with "memcached -I <size>")
25
26
  :value_max_bytes => 1024 * 1024,
27
+ # surpassing value_max_bytes either warns (false) or throws (true)
28
+ :error_when_over_max_size => false,
26
29
  :compressor => Compressor,
27
30
  # min byte size to attempt compression
28
31
  :compression_min_size => 1024,
@@ -65,16 +68,12 @@ module Dalli
65
68
  raise Dalli::NetworkError, "#{name} is down: #{@error} #{@msg}. If you are sure it is running, ensure memcached version is > 1.4." unless alive?
66
69
  begin
67
70
  send(op, *args)
68
- rescue Dalli::NetworkError
69
- raise
70
71
  rescue Dalli::MarshalError => ex
71
72
  Dalli.logger.error "Marshalling error for key '#{args.first}': #{ex.message}"
72
73
  Dalli.logger.error "You are trying to cache a Ruby object which cannot be serialized to memcached."
73
74
  Dalli.logger.error ex.backtrace.join("\n\t")
74
75
  false
75
- rescue Dalli::DalliError
76
- raise
77
- rescue Timeout::Error
76
+ rescue Dalli::DalliError, Dalli::NetworkError, Dalli::ValueOverMaxSize, Timeout::Error
78
77
  raise
79
78
  rescue => ex
80
79
  Dalli.logger.error "Unexpected exception during Dalli request: #{ex.class.name}: #{ex.message}"
@@ -128,7 +127,7 @@ module Dalli
128
127
  def multi_response_start
129
128
  verify_state
130
129
  write_noop
131
- @multi_buffer = ''
130
+ @multi_buffer = String.new('')
132
131
  @position = 0
133
132
  @inprogress = true
134
133
  end
@@ -274,7 +273,7 @@ module Dalli
274
273
  end
275
274
 
276
275
  def send_multiget(keys)
277
- req = ""
276
+ req = String.new("")
278
277
  keys.each do |key|
279
278
  req << [REQUEST, OPCODES[:getkq], key.bytesize, 0, 0, 0, key.bytesize, 0, 0, key].pack(FORMAT[:getkq])
280
279
  end
@@ -448,6 +447,9 @@ module Dalli
448
447
  rescue ArgumentError
449
448
  raise if $!.message !~ /undefined class|marshal data too short/
450
449
  raise UnmarshalError, "Unable to unmarshal value: #{$!.message}"
450
+ rescue NameError
451
+ raise if $!.message !~ /uninitialized constant/
452
+ raise UnmarshalError, "Unable to unmarshal value: #{$!.message}"
451
453
  rescue Zlib::Error
452
454
  raise UnmarshalError, "Unable to uncompress value: #{$!.message}"
453
455
  end
@@ -475,19 +477,24 @@ module Dalli
475
477
  if value.bytesize <= @options[:value_max_bytes]
476
478
  yield
477
479
  else
478
- Dalli.logger.warn "Value for #{key} over max size: #{@options[:value_max_bytes]} <= #{value.bytesize}"
480
+ message = "Value for #{key} over max size: #{@options[:value_max_bytes]} <= #{value.bytesize}"
481
+ raise Dalli::ValueOverMaxSize, message if @options[:error_when_over_max_size]
482
+
483
+ Dalli.logger.error "#{message} - this value may be truncated by memcached"
479
484
  false
480
485
  end
481
486
  end
482
487
 
483
- # https://code.google.com/p/memcached/wiki/NewCommands#Standard_Protocol
488
+ # https://github.com/memcached/memcached/blob/master/doc/protocol.txt#L79
484
489
  # > An expiration time, in seconds. Can be up to 30 days. After 30 days, is treated as a unix timestamp of an exact date.
485
490
  MAX_ACCEPTABLE_EXPIRATION_INTERVAL = 30*24*60*60 # 30 days
486
491
  def sanitize_ttl(ttl)
487
492
  ttl_as_i = ttl.to_i
488
493
  return ttl_as_i if ttl_as_i <= MAX_ACCEPTABLE_EXPIRATION_INTERVAL
494
+ now = Time.now.to_i
495
+ return ttl_as_i if ttl_as_i > now # already a timestamp
489
496
  Dalli.logger.debug "Expiration interval (#{ttl_as_i}) too long for Memcached, converting to an expiration timestamp"
490
- Time.now.to_i + ttl_as_i
497
+ now + ttl_as_i
491
498
  end
492
499
 
493
500
  # Implements the NullObject pattern to store an application-defined value for 'Key not found' responses.
@@ -604,7 +611,7 @@ module Dalli
604
611
  RESPONSE = 0x81
605
612
 
606
613
  # Response codes taken from:
607
- # https://code.google.com/p/memcached/wiki/BinaryProtocolRevamped#Response_Status
614
+ # https://github.com/memcached/memcached/wiki/BinaryProtocolRevamped#response-status
608
615
  RESPONSE_CODES = {
609
616
  0 => 'No error',
610
617
  1 => 'Key not found',
@@ -698,7 +705,7 @@ module Dalli
698
705
  req = [REQUEST, OPCODES[:auth_negotiation], 0, 0, 0, 0, 0, 0, 0].pack(FORMAT[:noop])
699
706
  write(req)
700
707
 
701
- (extras, type, status, count) = read_header.unpack(NORMAL_HEADER)
708
+ (extras, _type, status, count) = read_header.unpack(NORMAL_HEADER)
702
709
  raise Dalli::NetworkError, "Unexpected message format: #{extras} #{count}" unless extras == 0 && count > 0
703
710
  content = read(count).gsub(/\u0000/, ' ')
704
711
  return (Dalli.logger.debug("Authentication not required/supported by server")) if status == 0x81
@@ -711,7 +718,7 @@ module Dalli
711
718
  req = [REQUEST, OPCODES[:auth_request], mechanism.bytesize, 0, 0, 0, mechanism.bytesize + msg.bytesize, 0, 0, mechanism, msg].pack(FORMAT[:auth_request])
712
719
  write(req)
713
720
 
714
- (extras, type, status, count) = read_header.unpack(NORMAL_HEADER)
721
+ (extras, _type, status, count) = read_header.unpack(NORMAL_HEADER)
715
722
  raise Dalli::NetworkError, "Unexpected message format: #{extras} #{count}" unless extras == 0 && count > 0
716
723
  content = read(count)
717
724
  return Dalli.logger.info("Dalli/SASL: #{content}") if status == 0
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'rbconfig'
2
3
 
3
4
  module Dalli::Server::TCPSocketOptions
@@ -27,7 +28,7 @@ begin
27
28
  alias :write :kgio_write
28
29
 
29
30
  def readfull(count)
30
- value = ''
31
+ value = String.new('')
31
32
  while true
32
33
  value << kgio_read!(count - value.bytesize)
33
34
  break if value.bytesize == count
@@ -36,7 +37,7 @@ begin
36
37
  end
37
38
 
38
39
  def read_available
39
- value = ''
40
+ value = String.new('')
40
41
  while true
41
42
  ret = kgio_tryread(8196)
42
43
  case ret
@@ -63,6 +64,9 @@ begin
63
64
  sock.server = server
64
65
  sock.kgio_wait_writable
65
66
  sock
67
+ rescue Timeout::Error
68
+ sock.close if sock
69
+ raise
66
70
  end
67
71
  end
68
72
 
@@ -74,6 +78,9 @@ begin
74
78
  sock.server = server
75
79
  sock.kgio_wait_writable
76
80
  sock
81
+ rescue Timeout::Error
82
+ sock.close if sock
83
+ raise
77
84
  end
78
85
  end
79
86
 
@@ -88,7 +95,7 @@ rescue LoadError
88
95
  module Dalli::Server::KSocket
89
96
  module InstanceMethods
90
97
  def readfull(count)
91
- value = ''
98
+ value = String.new('')
92
99
  begin
93
100
  while true
94
101
  value << read_nonblock(count - value.bytesize)
@@ -98,14 +105,15 @@ rescue LoadError
98
105
  if IO.select([self], nil, nil, options[:socket_timeout])
99
106
  retry
100
107
  else
101
- raise Timeout::Error, "IO timeout: #{options.inspect}"
108
+ safe_options = options.reject{|k,v| [:username, :password].include? k}
109
+ raise Timeout::Error, "IO timeout: #{safe_options.inspect}"
102
110
  end
103
111
  end
104
112
  value
105
113
  end
106
114
 
107
115
  def read_available
108
- value = ''
116
+ value = String.new('')
109
117
  while true
110
118
  begin
111
119
  value << read_nonblock(8196)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dalli
2
- VERSION = '2.7.6'
3
+ VERSION = '2.7.11'
3
4
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'rack/session/abstract/id'
2
3
  require 'dalli'
3
4
 
@@ -6,46 +7,98 @@ module Rack
6
7
  class Dalli < defined?(Abstract::Persisted) ? Abstract::Persisted : Abstract::ID
7
8
  attr_reader :pool, :mutex
8
9
 
9
- DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
10
+ DEFAULT_DALLI_OPTIONS = {
10
11
  :namespace => 'rack:session',
11
12
  :memcache_server => 'localhost:11211'
13
+ }
12
14
 
15
+ # Brings in a new Rack::Session::Dalli middleware with the given
16
+ # `:memcache_server`. The server is either a hostname, or a
17
+ # host-with-port string in the form of "host_name:port", or an array of
18
+ # such strings. For example:
19
+ #
20
+ # use Rack::Session::Dalli,
21
+ # :memcache_server => "mc.example.com:1234"
22
+ #
23
+ # If no `:memcache_server` option is specified, Rack::Session::Dalli will
24
+ # connect to localhost, port 11211 (the default memcached port). If
25
+ # `:memcache_server` is set to nil, Dalli::Client will look for
26
+ # ENV['MEMCACHE_SERVERS'] and use that value if it is available, or fall
27
+ # back to the same default behavior described above.
28
+ #
29
+ # Rack::Session::Dalli is intended to be a drop-in replacement for
30
+ # Rack::Session::Memcache. It accepts additional options that control the
31
+ # behavior of Rack::Session, Dalli::Client, and an optional
32
+ # ConnectionPool. First and foremost, if you wish to instantiate your own
33
+ # Dalli::Client (or ConnectionPool) and use that instead of letting
34
+ # Rack::Session::Dalli instantiate it on your behalf, simply pass it in
35
+ # as the `:cache` option. Please note that you will be responsible for
36
+ # setting the namespace and any other options on Dalli::Client.
37
+ #
38
+ # Secondly, if you're not using the `:cache` option, Rack::Session::Dalli
39
+ # accepts the same options as Dalli::Client, so it's worth reviewing its
40
+ # documentation. Perhaps most importantly, if you don't specify a
41
+ # `:namespace` option, Rack::Session::Dalli will default to using
42
+ # "rack:session".
43
+ #
44
+ # Whether you are using the `:cache` option or not, it is not recommend
45
+ # to set `:expires_in`. Instead, use `:expire_after`, which will control
46
+ # both the expiration of the client cookie as well as the expiration of
47
+ # the corresponding entry in memcached.
48
+ #
49
+ # Rack::Session::Dalli also accepts a host of options that control how
50
+ # the sessions and session cookies are managed, including the
51
+ # aforementioned `:expire_after` option. Please see the documentation for
52
+ # Rack::Session::Abstract::Persisted for a detailed explanation of these
53
+ # options and their default values.
54
+ #
55
+ # Finally, if your web application is multithreaded, the
56
+ # Rack::Session::Dalli middleware can become a source of contention. You
57
+ # can use a connection pool of Dalli clients by passing in the
58
+ # `:pool_size` and/or `:pool_timeout` options. For example:
59
+ #
60
+ # use Rack::Session::Dalli,
61
+ # :memcache_server => "mc.example.com:1234",
62
+ # :pool_size => 10
63
+ #
64
+ # You must include the `connection_pool` gem in your project if you wish
65
+ # to use pool support. Please see the documentation for ConnectionPool
66
+ # for more information about it and its default options (which would only
67
+ # be applicable if you supplied one of the two options, but not both).
68
+ #
13
69
  def initialize(app, options={})
70
+ # Parent uses DEFAULT_OPTIONS to build @default_options for Rack::Session
14
71
  super
15
- @mutex = Mutex.new
16
- mserv = @default_options[:memcache_server]
17
- mopts = @default_options.reject{|k,v| !DEFAULT_OPTIONS.include? k }
18
- @pool = options[:cache] || ::Dalli::Client.new(mserv, mopts)
19
- @pool.alive!
20
- end
21
72
 
22
- if defined?(Abstract::Persisted)
23
- def find_session(req, sid)
24
- get_session req.env, sid
25
- end
73
+ # Determine the default TTL for newly-created sessions
74
+ @default_ttl = ttl @default_options[:expire_after]
26
75
 
27
- def write_session(req, sid, session, options)
28
- set_session req.env, sid, session, options
29
- end
76
+ # Normalize and validate passed options
77
+ cache, mserv, mopts, popts = extract_dalli_options options
30
78
 
31
- def delete_session(req, sid, options)
32
- destroy_session req.env, sid, options
33
- end
34
- end
79
+ @pool =
80
+ if cache # caller passed a Dalli::Client or ConnectionPool instance
81
+ cache
82
+ elsif popts # caller passed ConnectionPool options
83
+ ConnectionPool.new(popts) { ::Dalli::Client.new(mserv, mopts) }
84
+ else
85
+ ::Dalli::Client.new(mserv, mopts)
86
+ end
35
87
 
36
- def generate_sid
37
- while true
38
- sid = super
39
- break sid unless @pool.get(sid)
88
+ if @pool.respond_to?(:alive!) # is a Dalli::Client
89
+ @mutex = Mutex.new
90
+
91
+ @pool.alive!
40
92
  end
41
93
  end
42
94
 
43
95
  def get_session(env, sid)
44
- with_lock(env, [nil, {}]) do
45
- unless sid and !sid.empty? and session = @pool.get(sid)
46
- sid, session = generate_sid, {}
47
- unless @pool.add(sid, session)
48
- raise "Session collision on '#{sid.inspect}'"
96
+ with_block(env, [nil, {}]) do |dc|
97
+ unless sid and !sid.empty? and session = dc.get(sid)
98
+ old_sid, sid, session = sid, generate_sid_with(dc), {}
99
+ unless dc.add(sid, session, @default_ttl)
100
+ sid = old_sid
101
+ redo # generate a new sid and try again
49
102
  end
50
103
  end
51
104
  [sid, session]
@@ -54,25 +107,66 @@ module Rack
54
107
 
55
108
  def set_session(env, session_id, new_session, options)
56
109
  return false unless session_id
57
- expiry = options[:expire_after]
58
- expiry = expiry.nil? ? 0 : expiry + 1
59
110
 
60
- with_lock(env, false) do
61
- @pool.set session_id, new_session, expiry
111
+ with_block(env, false) do |dc|
112
+ dc.set(session_id, new_session, ttl(options[:expire_after]))
62
113
  session_id
63
114
  end
64
115
  end
65
116
 
66
117
  def destroy_session(env, session_id, options)
67
- with_lock(env) do
68
- @pool.delete(session_id)
69
- generate_sid unless options[:drop]
118
+ with_block(env) do |dc|
119
+ dc.delete(session_id)
120
+ generate_sid_with(dc) unless options[:drop]
121
+ end
122
+ end
123
+
124
+ if defined?(Abstract::Persisted)
125
+ def find_session(req, sid)
126
+ get_session req.env, sid
127
+ end
128
+
129
+ def write_session(req, sid, session, options)
130
+ set_session req.env, sid, session, options
131
+ end
132
+
133
+ def delete_session(req, sid, options)
134
+ destroy_session req.env, sid, options
70
135
  end
71
136
  end
72
137
 
73
- def with_lock(env, default=nil)
74
- @mutex.lock if env['rack.multithread']
75
- yield
138
+ private
139
+
140
+ def extract_dalli_options(options)
141
+ return [options[:cache]] if options[:cache]
142
+
143
+ # Filter out Rack::Session-specific options and apply our defaults
144
+ mopts = DEFAULT_DALLI_OPTIONS.merge \
145
+ options.reject {|k, _| DEFAULT_OPTIONS.key? k }
146
+ mserv = mopts.delete :memcache_server
147
+
148
+ if mopts[:pool_size] || mopts[:pool_timeout]
149
+ popts = {}
150
+ popts[:size] = mopts.delete :pool_size if mopts[:pool_size]
151
+ popts[:timeout] = mopts.delete :pool_timeout if mopts[:pool_timeout]
152
+
153
+ # For a connection pool, locking is handled at the pool level
154
+ mopts[:threadsafe] = false unless mopts.key? :threadsafe
155
+ end
156
+
157
+ [nil, mserv, mopts, popts]
158
+ end
159
+
160
+ def generate_sid_with(dc)
161
+ while true
162
+ sid = generate_sid
163
+ break sid unless dc.get(sid)
164
+ end
165
+ end
166
+
167
+ def with_block(env, default=nil, &block)
168
+ @mutex.lock if @mutex and env['rack.multithread']
169
+ @pool.with(&block)
76
170
  rescue ::Dalli::DalliError, Errno::ECONNREFUSED
77
171
  raise if $!.message =~ /undefined class/
78
172
  if $VERBOSE
@@ -81,9 +175,12 @@ module Rack
81
175
  end
82
176
  default
83
177
  ensure
84
- @mutex.unlock if @mutex.locked?
178
+ @mutex.unlock if @mutex and @mutex.locked?
85
179
  end
86
180
 
181
+ def ttl(expire_after)
182
+ expire_after.nil? ? 0 : expire_after + 1
183
+ end
87
184
  end
88
185
  end
89
186
  end
metadata CHANGED
@@ -1,128 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dalli
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.6
4
+ version: 2.7.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter M. Goldstein
8
8
  - Mike Perham
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-02-14 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: minitest
16
- requirement: !ruby/object:Gem::Requirement
17
- requirements:
18
- - - ">="
19
- - !ruby/object:Gem::Version
20
- version: 4.2.0
21
- type: :development
22
- prerelease: false
23
- version_requirements: !ruby/object:Gem::Requirement
24
- requirements:
25
- - - ">="
26
- - !ruby/object:Gem::Version
27
- version: 4.2.0
28
- - !ruby/object:Gem::Dependency
29
- name: mocha
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - ">="
33
- - !ruby/object:Gem::Version
34
- version: '0'
35
- type: :development
36
- prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - ">="
40
- - !ruby/object:Gem::Version
41
- version: '0'
42
- - !ruby/object:Gem::Dependency
43
- name: rails
44
- requirement: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - "~>"
47
- - !ruby/object:Gem::Version
48
- version: '4'
49
- type: :development
50
- prerelease: false
51
- version_requirements: !ruby/object:Gem::Requirement
52
- requirements:
53
- - - "~>"
54
- - !ruby/object:Gem::Version
55
- version: '4'
56
- - !ruby/object:Gem::Dependency
57
- name: rake
58
- requirement: !ruby/object:Gem::Requirement
59
- requirements:
60
- - - ">="
61
- - !ruby/object:Gem::Version
62
- version: '0'
63
- type: :development
64
- prerelease: false
65
- version_requirements: !ruby/object:Gem::Requirement
66
- requirements:
67
- - - ">="
68
- - !ruby/object:Gem::Version
69
- version: '0'
70
- - !ruby/object:Gem::Dependency
71
- name: appraisal
72
- requirement: !ruby/object:Gem::Requirement
73
- requirements:
74
- - - ">="
75
- - !ruby/object:Gem::Version
76
- version: '0'
77
- type: :development
78
- prerelease: false
79
- version_requirements: !ruby/object:Gem::Requirement
80
- requirements:
81
- - - ">="
82
- - !ruby/object:Gem::Version
83
- version: '0'
84
- - !ruby/object:Gem::Dependency
85
- name: connection_pool
86
- requirement: !ruby/object:Gem::Requirement
87
- requirements:
88
- - - ">="
89
- - !ruby/object:Gem::Version
90
- version: '0'
91
- type: :development
92
- prerelease: false
93
- version_requirements: !ruby/object:Gem::Requirement
94
- requirements:
95
- - - ">="
96
- - !ruby/object:Gem::Version
97
- version: '0'
98
- - !ruby/object:Gem::Dependency
99
- name: rdoc
100
- requirement: !ruby/object:Gem::Requirement
101
- requirements:
102
- - - ">="
103
- - !ruby/object:Gem::Version
104
- version: '0'
105
- type: :development
106
- prerelease: false
107
- version_requirements: !ruby/object:Gem::Requirement
108
- requirements:
109
- - - ">="
110
- - !ruby/object:Gem::Version
111
- version: '0'
112
- - !ruby/object:Gem::Dependency
113
- name: simplecov
114
- requirement: !ruby/object:Gem::Requirement
115
- requirements:
116
- - - ">="
117
- - !ruby/object:Gem::Version
118
- version: '0'
119
- type: :development
120
- prerelease: false
121
- version_requirements: !ruby/object:Gem::Requirement
122
- requirements:
123
- - - ">="
124
- - !ruby/object:Gem::Version
125
- version: '0'
12
+ date: 2020-09-26 00:00:00.000000000 Z
13
+ dependencies: []
126
14
  description: High performance memcached client for Ruby
127
15
  email:
128
16
  - peter.m.goldstein@gmail.com
@@ -152,9 +40,8 @@ homepage: https://github.com/petergoldstein/dalli
152
40
  licenses:
153
41
  - MIT
154
42
  metadata: {}
155
- post_install_message:
156
- rdoc_options:
157
- - "--charset=UTF-8"
43
+ post_install_message:
44
+ rdoc_options: []
158
45
  require_paths:
159
46
  - lib
160
47
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -168,9 +55,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
168
55
  - !ruby/object:Gem::Version
169
56
  version: '0'
170
57
  requirements: []
171
- rubyforge_project:
172
- rubygems_version: 2.4.5.1
173
- signing_key:
58
+ rubyforge_project:
59
+ rubygems_version: 2.7.6.2
60
+ signing_key:
174
61
  specification_version: 4
175
62
  summary: High performance memcached client for Ruby
176
63
  test_files: []