dalli 2.7.6 → 2.7.7

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: be20ed3535eb5ddc41d8d53e596adc7fe7e7d4241ff345e79f29fb1c6b732a01
4
+ data.tar.gz: 39e7c9ea59332d26a2b8ca7276664f3028bbe0b3db3481aa24bc478b5019f3aa
5
5
  SHA512:
6
- metadata.gz: 962196b3d7c2a75b928f32e896106ead0316b6d2614f1176041a3751c82816e5669a70d9d293ef1762df190bade229955b20c5673562600575774b3bfed6c978
7
- data.tar.gz: c890f77ce55953a3f0fe47629ddb1eda40ece7b58628c7168e45c3733c5120e72c7dba992f6b15267ea5025283bf8f5c2e213648c65e6a87131219bfe318f5a1
6
+ metadata.gz: 9ca82a471a6ae90c123c1888117e87fbc6e348a3401b3db77f5982574d5dc46e0765681df22815773c27d204fa9023841b0012db7a37ec72569eeeddc5e53a3b
7
+ data.tar.gz: 93cef3703a4b7c48e1127228b11d03565feba4523dfc1057f6689f50627b1bbb29c0fcea2d703e8500bfdb5fc70da2b975736032452610550f6acf7dace76025
data/History.md CHANGED
@@ -1,6 +1,8 @@
1
1
  Dalli Changelog
2
2
  =====================
3
3
 
4
+ - Instrument DalliStore errors with instrument_errors configuration option. (btatnall)
5
+
4
6
  2.7.6
5
7
  ==========
6
8
  - 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) [![Dependency Status](https://gemnasium.com/petergoldstein/dalli.svg)](https://gemnasium.com/petergoldstein/dalli) [![Code Climate](https://codeclimate.com/github/petergoldstein/dalli.svg)](https://codeclimate.com/github/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.
@@ -90,10 +90,12 @@ config.cache_store = :dalli_store
90
90
  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
91
 
92
92
  ```ruby
93
- config.cache_store = :dalli_store, 'cache-1.example.com', 'cache-2.example.com',
93
+ config.cache_store = :dalli_store, 'cache-1.example.com', 'cache-2.example.com:11211:2',
94
94
  { :namespace => NAME_OF_RAILS_APP, :expires_in => 1.day, :compress => true }
95
95
  ```
96
96
 
97
+ 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` )
98
+
97
99
  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
100
 
99
101
  ```ruby
@@ -147,6 +149,8 @@ end
147
149
  Configuration
148
150
  ------------------------
149
151
 
152
+ **servers**: An Array of "host:port:weight" where weight allows you to distribute cache unevenly.
153
+
150
154
  Dalli::Client accepts the following options. All times are in seconds.
151
155
 
152
156
  **expires_in**: Global default for key TTL. Default is 0, which means no expiry.
@@ -178,10 +182,12 @@ If serving compressed data using nginx's HttpMemcachedModule, set `memcached_gzi
178
182
 
179
183
  **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
184
 
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.
185
+ **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
186
 
183
187
  **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
188
 
189
+ **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.
190
+
185
191
  **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
192
 
187
193
  **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,6 +198,10 @@ If serving compressed data using nginx's HttpMemcachedModule, set `memcached_gzi
192
198
 
193
199
  **cache_nils**: Boolean. If true Dalli will not treat cached `nil` values as 'not found' for `#fetch` operations. Default is false.
194
200
 
201
+ **raise_errors**: Boolean. When true DalliStore will reraise Dalli:DalliError instead swallowing the error. Default is false.
202
+
203
+ **instrument_errors**: Boolean. When true DalliStore will send notification of Dalli::DalliError via a 'cache_error.active_support' event. Default is false.
204
+
195
205
  Features and Changes
196
206
  ------------------------
197
207
 
@@ -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
@@ -98,24 +99,24 @@ module ActiveSupport
98
99
  if block_given?
99
100
  entry = not_found
100
101
  unless options[:force]
101
- entry = instrument(:read, namespaced_name, options) do |payload|
102
+ entry = instrument_with_log(:read, namespaced_name, options) do |payload|
102
103
  read_entry(namespaced_name, options).tap do |result|
103
104
  if payload
104
105
  payload[:super_operation] = :fetch
105
- payload[:hit] = result != not_found
106
+ payload[:hit] = not_found != result
106
107
  end
107
108
  end
108
109
  end
109
110
  end
110
111
 
111
- if entry == not_found
112
- result = instrument(:generate, namespaced_name, options) do |payload|
112
+ if not_found == entry
113
+ result = instrument_with_log(:generate, namespaced_name, options) do |payload|
113
114
  yield
114
115
  end
115
116
  write(name, result, options)
116
117
  result
117
118
  else
118
- instrument(:fetch_hit, namespaced_name, options) { |payload| }
119
+ instrument_with_log(:fetch_hit, namespaced_name, options) { |payload| }
119
120
  entry
120
121
  end
121
122
  else
@@ -127,7 +128,7 @@ module ActiveSupport
127
128
  options ||= {}
128
129
  name = namespaced_key(name, options)
129
130
 
130
- instrument(:read, name, options) do |payload|
131
+ instrument_with_log(:read, name, options) do |payload|
131
132
  entry = read_entry(name, options)
132
133
  payload[:hit] = !entry.nil? if payload
133
134
  entry
@@ -138,7 +139,7 @@ module ActiveSupport
138
139
  options ||= {}
139
140
  name = namespaced_key(name, options)
140
141
 
141
- instrument(:write, name, options) do |payload|
142
+ instrument_with_log(:write, name, options) do |payload|
142
143
  with do |connection|
143
144
  options = options.merge(:connection => connection)
144
145
  write_entry(name, value, options)
@@ -158,7 +159,7 @@ module ActiveSupport
158
159
  options ||= {}
159
160
  name = namespaced_key(name, options)
160
161
 
161
- instrument(:delete, name, options) do |payload|
162
+ instrument_with_log(:delete, name, options) do |payload|
162
163
  delete_entry(name, options)
163
164
  end
164
165
  end
@@ -168,7 +169,7 @@ module ActiveSupport
168
169
  def read_multi(*names)
169
170
  options = names.extract_options!
170
171
  mapping = names.inject({}) { |memo, name| memo[namespaced_key(name, options)] = name; memo }
171
- instrument(:read_multi, mapping.keys) do
172
+ instrument_with_log(:read_multi, mapping.keys) do
172
173
  results = {}
173
174
  if local_cache
174
175
  mapping.each_key do |key|
@@ -199,7 +200,7 @@ module ActiveSupport
199
200
  options = names.extract_options!
200
201
  mapping = names.inject({}) { |memo, name| memo[namespaced_key(name, options)] = name; memo }
201
202
 
202
- instrument(:fetch_multi, mapping.keys) do
203
+ instrument_with_log(:fetch_multi, mapping.keys) do
203
204
  with do |connection|
204
205
  results = connection.get_multi(mapping.keys)
205
206
 
@@ -230,11 +231,12 @@ module ActiveSupport
230
231
  name = namespaced_key(name, options)
231
232
  initial = options.has_key?(:initial) ? options[:initial] : amount
232
233
  expires_in = options[:expires_in]
233
- instrument(:increment, name, :amount => amount) do
234
+ instrument_with_log(:increment, name, :amount => amount) do
234
235
  with { |c| c.incr(name, amount, expires_in, initial) }
235
236
  end
236
237
  rescue Dalli::DalliError => e
237
- logger.error("DalliError: #{e.message}") if logger
238
+ log_dalli_error(e)
239
+ instrument_error(e) if instrument_errors?
238
240
  raise if raise_errors?
239
241
  nil
240
242
  end
@@ -249,11 +251,12 @@ module ActiveSupport
249
251
  name = namespaced_key(name, options)
250
252
  initial = options.has_key?(:initial) ? options[:initial] : 0
251
253
  expires_in = options[:expires_in]
252
- instrument(:decrement, name, :amount => amount) do
254
+ instrument_with_log(:decrement, name, :amount => amount) do
253
255
  with { |c| c.decr(name, amount, expires_in, initial) }
254
256
  end
255
257
  rescue Dalli::DalliError => e
256
- logger.error("DalliError: #{e.message}") if logger
258
+ log_dalli_error(e)
259
+ instrument_error(e) if instrument_errors?
257
260
  raise if raise_errors?
258
261
  nil
259
262
  end
@@ -261,11 +264,12 @@ module ActiveSupport
261
264
  # Clear the entire cache on all memcached servers. This method should
262
265
  # be used with care when using a shared cache.
263
266
  def clear(options=nil)
264
- instrument(:clear, 'flushing all keys') do
267
+ instrument_with_log(:clear, 'flushing all keys') do
265
268
  with { |c| c.flush_all }
266
269
  end
267
270
  rescue Dalli::DalliError => e
268
- logger.error("DalliError: #{e.message}") if logger
271
+ log_dalli_error(e)
272
+ instrument_error(e) if instrument_errors?
269
273
  raise if raise_errors?
270
274
  nil
271
275
  end
@@ -299,7 +303,8 @@ module ActiveSupport
299
303
  # NB Backwards data compatibility, to be removed at some point
300
304
  entry.is_a?(ActiveSupport::Cache::Entry) ? entry.value : entry
301
305
  rescue Dalli::DalliError => e
302
- logger.error("DalliError: #{e.message}") if logger
306
+ log_dalli_error(e)
307
+ instrument_error(e) if instrument_errors?
303
308
  raise if raise_errors?
304
309
  nil
305
310
  end
@@ -313,7 +318,8 @@ module ActiveSupport
313
318
  connection = options.delete(:connection)
314
319
  connection.send(method, key, value, expires_in, options)
315
320
  rescue Dalli::DalliError => e
316
- logger.error("DalliError: #{e.message}") if logger
321
+ log_dalli_error(e)
322
+ instrument_error(e) if instrument_errors?
317
323
  raise if raise_errors?
318
324
  false
319
325
  end
@@ -322,7 +328,8 @@ module ActiveSupport
322
328
  def delete_entry(key, options) # :nodoc:
323
329
  with { |c| c.delete(key) }
324
330
  rescue Dalli::DalliError => e
325
- logger.error("DalliError: #{e.message}") if logger
331
+ log_dalli_error(e)
332
+ instrument_error(e) if instrument_errors?
326
333
  raise if raise_errors?
327
334
  false
328
335
  end
@@ -334,6 +341,7 @@ module ActiveSupport
334
341
  namespace = options[:namespace] if options
335
342
  prefix = namespace.is_a?(Proc) ? namespace.call : namespace
336
343
  key = "#{prefix}:#{key}" if prefix
344
+ key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key && key.size > 250
337
345
  key
338
346
  end
339
347
  alias :normalize_key :namespaced_key
@@ -363,12 +371,26 @@ module ActiveSupport
363
371
  key
364
372
  end
365
373
 
366
- def instrument(operation, key, options=nil)
374
+ def log_dalli_error(error)
375
+ logger.error("DalliError: #{error.message}") if logger
376
+ end
377
+
378
+ def instrument_with_log(operation, key, options=nil)
367
379
  log(operation, key, options)
368
380
 
369
381
  payload = { :key => key }
370
382
  payload.merge!(options) if options.is_a?(Hash)
371
- ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
383
+ instrument(operation, payload) { |p| yield(p) }
384
+ end
385
+
386
+ def instrument_error(error)
387
+ instrument(:error, { :key => 'DalliError', :message => error.message })
388
+ end
389
+
390
+ def instrument(operation, payload)
391
+ ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) do
392
+ yield(payload) if block_given?
393
+ end
372
394
  end
373
395
 
374
396
  def log(operation, key, options=nil)
@@ -380,6 +402,10 @@ module ActiveSupport
380
402
  !!@options[:raise_errors]
381
403
  end
382
404
 
405
+ def instrument_errors?
406
+ !!@options[:instrument_errors]
407
+ end
408
+
383
409
  # Make sure LocalCache is giving raw values, not `Entry`s, and
384
410
  # respect `raw` option.
385
411
  module LocalCacheEntryUnwrapAndRaw # :nodoc:
@@ -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 < RuntimeError; 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
 
@@ -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
@@ -475,19 +474,24 @@ module Dalli
475
474
  if value.bytesize <= @options[:value_max_bytes]
476
475
  yield
477
476
  else
478
- Dalli.logger.warn "Value for #{key} over max size: #{@options[:value_max_bytes]} <= #{value.bytesize}"
477
+ message = "Value for #{key} over max size: #{@options[:value_max_bytes]} <= #{value.bytesize}"
478
+ raise Dalli::ValueOverMaxSize, message if @options[:error_when_over_max_size]
479
+
480
+ Dalli.logger.warn message
479
481
  false
480
482
  end
481
483
  end
482
484
 
483
- # https://code.google.com/p/memcached/wiki/NewCommands#Standard_Protocol
485
+ # https://github.com/memcached/memcached/blob/master/doc/protocol.txt#L79
484
486
  # > 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
487
  MAX_ACCEPTABLE_EXPIRATION_INTERVAL = 30*24*60*60 # 30 days
486
488
  def sanitize_ttl(ttl)
487
489
  ttl_as_i = ttl.to_i
488
490
  return ttl_as_i if ttl_as_i <= MAX_ACCEPTABLE_EXPIRATION_INTERVAL
491
+ now = Time.now.to_i
492
+ return ttl_as_i if ttl_as_i > now # already a timestamp
489
493
  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
494
+ now + ttl_as_i
491
495
  end
492
496
 
493
497
  # Implements the NullObject pattern to store an application-defined value for 'Key not found' responses.
@@ -604,7 +608,7 @@ module Dalli
604
608
  RESPONSE = 0x81
605
609
 
606
610
  # Response codes taken from:
607
- # https://code.google.com/p/memcached/wiki/BinaryProtocolRevamped#Response_Status
611
+ # https://github.com/memcached/memcached/wiki/BinaryProtocolRevamped#response-status
608
612
  RESPONSE_CODES = {
609
613
  0 => 'No error',
610
614
  1 => 'Key not found',
@@ -698,7 +702,7 @@ module Dalli
698
702
  req = [REQUEST, OPCODES[:auth_negotiation], 0, 0, 0, 0, 0, 0, 0].pack(FORMAT[:noop])
699
703
  write(req)
700
704
 
701
- (extras, type, status, count) = read_header.unpack(NORMAL_HEADER)
705
+ (extras, _type, status, count) = read_header.unpack(NORMAL_HEADER)
702
706
  raise Dalli::NetworkError, "Unexpected message format: #{extras} #{count}" unless extras == 0 && count > 0
703
707
  content = read(count).gsub(/\u0000/, ' ')
704
708
  return (Dalli.logger.debug("Authentication not required/supported by server")) if status == 0x81
@@ -711,7 +715,7 @@ module Dalli
711
715
  req = [REQUEST, OPCODES[:auth_request], mechanism.bytesize, 0, 0, 0, mechanism.bytesize + msg.bytesize, 0, 0, mechanism, msg].pack(FORMAT[:auth_request])
712
716
  write(req)
713
717
 
714
- (extras, type, status, count) = read_header.unpack(NORMAL_HEADER)
718
+ (extras, _type, status, count) = read_header.unpack(NORMAL_HEADER)
715
719
  raise Dalli::NetworkError, "Unexpected message format: #{extras} #{count}" unless extras == 0 && count > 0
716
720
  content = read(count)
717
721
  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.7'
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,17 +7,118 @@ 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!
72
+
73
+ # Determine the default TTL for newly-created sessions
74
+ @default_ttl = ttl @default_options[:expire_after]
75
+
76
+ # Normalize and validate passed options
77
+ cache, mserv, mopts, popts = extract_dalli_options options
78
+
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
87
+
88
+ if @pool.respond_to?(:alive!) # is a Dalli::Client
89
+ @mutex = Mutex.new
90
+
91
+ @pool.alive!
92
+ end
93
+ end
94
+
95
+ def get_session(env, sid)
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
102
+ end
103
+ end
104
+ [sid, session]
105
+ end
106
+ end
107
+
108
+ def set_session(env, session_id, new_session, options)
109
+ return false unless session_id
110
+
111
+ with_block(env, false) do |dc|
112
+ dc.set(session_id, new_session, ttl(options[:expire_after]))
113
+ session_id
114
+ end
115
+ end
116
+
117
+ def destroy_session(env, session_id, options)
118
+ with_block(env) do |dc|
119
+ dc.delete(session_id)
120
+ generate_sid_with(dc) unless options[:drop]
121
+ end
20
122
  end
21
123
 
22
124
  if defined?(Abstract::Persisted)
@@ -33,46 +135,47 @@ module Rack
33
135
  end
34
136
  end
35
137
 
36
- def generate_sid
37
- while true
38
- sid = super
39
- break sid unless @pool.get(sid)
40
- end
41
- end
138
+ private
42
139
 
43
- 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}'"
49
- end
50
- end
51
- [sid, session]
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
52
155
  end
156
+
157
+ [nil, mserv, mopts, popts]
53
158
  end
54
159
 
55
- def set_session(env, session_id, new_session, options)
56
- return false unless session_id
57
- expiry = options[:expire_after]
58
- expiry = expiry.nil? ? 0 : expiry + 1
160
+ # Capture generate_sid's super so we can call it from generate_sid_with
161
+ alias_method :generate_sid_super, :generate_sid
59
162
 
60
- with_lock(env, false) do
61
- @pool.set session_id, new_session, expiry
62
- session_id
63
- end
163
+ def generate_sid
164
+ # no way to check env['rack.multithread'] here so fall back on
165
+ # Dalli::Client or ConnectionPool's internal mutex cf. our own
166
+ @pool.with {|dc| generate_sid_with(dc) }
64
167
  end
65
168
 
66
- def destroy_session(env, session_id, options)
67
- with_lock(env) do
68
- @pool.delete(session_id)
69
- generate_sid unless options[:drop]
169
+ def generate_sid_with(dc)
170
+ while true
171
+ sid = generate_sid_super
172
+ break sid unless dc.get(sid)
70
173
  end
71
174
  end
72
175
 
73
- def with_lock(env, default=nil)
74
- @mutex.lock if env['rack.multithread']
75
- yield
176
+ def with_block(env, default=nil, &block)
177
+ @mutex.lock if @mutex and env['rack.multithread']
178
+ @pool.with(&block)
76
179
  rescue ::Dalli::DalliError, Errno::ECONNREFUSED
77
180
  raise if $!.message =~ /undefined class/
78
181
  if $VERBOSE
@@ -81,9 +184,12 @@ module Rack
81
184
  end
82
185
  default
83
186
  ensure
84
- @mutex.unlock if @mutex.locked?
187
+ @mutex.unlock if @mutex and @mutex.locked?
85
188
  end
86
189
 
190
+ def ttl(expire_after)
191
+ expire_after.nil? ? 0 : expire_after + 1
192
+ end
87
193
  end
88
194
  end
89
195
  end
metadata CHANGED
@@ -1,7 +1,7 @@
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.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter M. Goldstein
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-02-14 00:00:00.000000000 Z
12
+ date: 2018-03-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -169,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
169
  version: '0'
170
170
  requirements: []
171
171
  rubyforge_project:
172
- rubygems_version: 2.4.5.1
172
+ rubygems_version: 2.7.5
173
173
  signing_key:
174
174
  specification_version: 4
175
175
  summary: High performance memcached client for Ruby