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/socket.rb CHANGED
@@ -1,76 +1,177 @@
1
- begin
2
- require 'kgio'
3
- puts "Using kgio socket IO" if defined?($TESTING) && $TESTING
1
+ # frozen_string_literal: true
4
2
 
5
- class Dalli::Server::KSocket < Kgio::Socket
6
- attr_accessor :options
3
+ require 'openssl'
4
+ require 'rbconfig'
7
5
 
8
- def kgio_wait_readable
9
- IO.select([self], nil, nil, options[:socket_timeout]) || raise(Timeout::Error, "IO timeout")
10
- end
6
+ module Dalli
7
+ ##
8
+ # Various socket implementations used by Dalli.
9
+ ##
10
+ module Socket
11
+ ##
12
+ # Common methods for all socket implementations.
13
+ ##
14
+ module InstanceMethods
15
+ def readfull(count)
16
+ value = String.new(capacity: count + 1)
17
+ loop do
18
+ result = read_nonblock(count - value.bytesize, exception: false)
19
+ value << result if append_to_buffer?(result)
20
+ break if value.bytesize == count
21
+ end
22
+ value
23
+ end
11
24
 
12
- def kgio_wait_writable
13
- IO.select(nil, [self], nil, options[:socket_timeout]) || raise(Timeout::Error, "IO timeout")
14
- end
25
+ def read_available
26
+ value = +''
27
+ loop do
28
+ result = read_nonblock(8196, exception: false)
29
+ break if WAIT_RCS.include?(result)
30
+ raise Errno::ECONNRESET, "Connection reset: #{logged_options.inspect}" unless result
15
31
 
16
- def self.open(host, port, options = {})
17
- addr = Socket.pack_sockaddr_in(port, host)
18
- sock = start(addr)
19
- sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if options[:keepalive]
20
- sock.options = options
21
- sock.kgio_wait_writable
22
- sock
23
- end
32
+ value << result
33
+ end
34
+ value
35
+ end
36
+
37
+ WAIT_RCS = %i[wait_writable wait_readable].freeze
24
38
 
25
- alias :write :kgio_write
39
+ def append_to_buffer?(result)
40
+ raise Timeout::Error, "IO timeout: #{logged_options.inspect}" if nonblock_timed_out?(result)
41
+ raise Errno::ECONNRESET, "Connection reset: #{logged_options.inspect}" unless result
26
42
 
27
- def readfull(count)
28
- value = ''
29
- loop do
30
- value << kgio_read!(count - value.bytesize)
31
- break if value.bytesize == count
43
+ !WAIT_RCS.include?(result)
32
44
  end
33
- value
34
- end
35
45
 
36
- end
46
+ def nonblock_timed_out?(result)
47
+ return true if result == :wait_readable && !wait_readable(options[:socket_timeout])
37
48
 
38
- if ::Kgio.respond_to?(:wait_readable=)
39
- ::Kgio.wait_readable = :kgio_wait_readable
40
- ::Kgio.wait_writable = :kgio_wait_writable
41
- end
49
+ # TODO: Do we actually need this? Looks to be only used in read_nonblock
50
+ result == :wait_writable && !wait_writable(options[:socket_timeout])
51
+ end
42
52
 
43
- rescue LoadError
53
+ FILTERED_OUT_OPTIONS = %i[username password].freeze
54
+ def logged_options
55
+ options.reject { |k, _| FILTERED_OUT_OPTIONS.include? k }
56
+ end
57
+ end
44
58
 
45
- puts "Using standard socket IO (#{RUBY_DESCRIPTION})" if defined?($TESTING) && $TESTING
46
- class Dalli::Server::KSocket < TCPSocket
47
- attr_accessor :options
59
+ ##
60
+ # Wraps the below TCP socket class in the case where the client
61
+ # has configured a TLS/SSL connection between Dalli and the
62
+ # Memcached server.
63
+ ##
64
+ class SSLSocket < ::OpenSSL::SSL::SSLSocket
65
+ include Dalli::Socket::InstanceMethods
66
+ def options
67
+ io.options
68
+ end
48
69
 
49
- def self.open(host, port, options = {})
50
- Timeout.timeout(options[:socket_timeout]) do
51
- sock = new(host, port)
52
- sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if options[:keepalive]
53
- sock.options = { :host => host, :port => port }.merge(options)
54
- sock
70
+ unless method_defined?(:wait_readable)
71
+ def wait_readable(timeout = nil)
72
+ to_io.wait_readable(timeout)
73
+ end
74
+ end
75
+
76
+ unless method_defined?(:wait_writable)
77
+ def wait_writable(timeout = nil)
78
+ to_io.wait_writable(timeout)
79
+ end
55
80
  end
56
81
  end
57
82
 
58
- def readfull(count)
59
- value = ''
60
- begin
61
- loop do
62
- value << read_nonblock(count - value.bytesize)
63
- break if value.bytesize == count
83
+ ##
84
+ # A standard TCP socket between the Dalli client and the Memcached server.
85
+ ##
86
+ class TCP < TCPSocket
87
+ include Dalli::Socket::InstanceMethods
88
+ # options - supports enhanced logging in the case of a timeout
89
+ attr_accessor :options
90
+
91
+ def self.open(host, port, options = {})
92
+ create_socket_with_timeout(host, port, options) do |sock|
93
+ sock.options = { host: host, port: port }.merge(options)
94
+ init_socket_options(sock, options)
95
+
96
+ options[:ssl_context] ? wrapping_ssl_socket(sock, host, options[:ssl_context]) : sock
64
97
  end
65
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
66
- if IO.select([self], nil, nil, options[:socket_timeout])
67
- retry
98
+ end
99
+
100
+ def self.create_socket_with_timeout(host, port, options)
101
+ # Check that TCPSocket#initialize was not overwritten by resolv-replace gem
102
+ # (part of ruby standard library since 3.0.0, should be removed in 3.4.0),
103
+ # as it does not handle keyword arguments correctly.
104
+ # To check this we are using the fact that resolv-replace
105
+ # aliases TCPSocket#initialize method to #original_resolv_initialize.
106
+ # https://github.com/ruby/resolv-replace/blob/v0.1.1/lib/resolv-replace.rb#L21
107
+ if RUBY_VERSION >= '3.0' &&
108
+ !::TCPSocket.private_instance_methods.include?(:original_resolv_initialize)
109
+ sock = new(host, port, connect_timeout: options[:socket_timeout])
110
+ yield(sock)
68
111
  else
69
- raise Timeout::Error, "IO timeout: #{options.inspect}"
112
+ Timeout.timeout(options[:socket_timeout]) do
113
+ sock = new(host, port)
114
+ yield(sock)
115
+ end
70
116
  end
71
117
  end
72
- value
118
+
119
+ def self.init_socket_options(sock, options)
120
+ sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
121
+ sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options[:keepalive]
122
+ sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_RCVBUF, options[:rcvbuf]) if options[:rcvbuf]
123
+ sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_SNDBUF, options[:sndbuf]) if options[:sndbuf]
124
+
125
+ return unless options[:socket_timeout]
126
+
127
+ seconds, fractional = options[:socket_timeout].divmod(1)
128
+ microseconds = fractional * 1_000_000
129
+ timeval = [seconds, microseconds].pack('l_2')
130
+
131
+ sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_RCVTIMEO, timeval)
132
+ sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_SNDTIMEO, timeval)
133
+ end
134
+
135
+ def self.wrapping_ssl_socket(tcp_socket, host, ssl_context)
136
+ ssl_socket = Dalli::Socket::SSLSocket.new(tcp_socket, ssl_context)
137
+ ssl_socket.hostname = host
138
+ ssl_socket.sync_close = true
139
+ ssl_socket.connect
140
+ ssl_socket
141
+ end
73
142
  end
74
143
 
144
+ if /mingw|mswin/.match?(RbConfig::CONFIG['host_os'])
145
+ ##
146
+ # UNIX domain sockets are not supported on Windows platforms.
147
+ ##
148
+ class UNIX
149
+ def initialize(*_args)
150
+ raise Dalli::DalliError, 'Unix sockets are not supported on Windows platform.'
151
+ end
152
+ end
153
+ else
154
+
155
+ ##
156
+ # UNIX represents a UNIX domain socket, which is an interprocess communication
157
+ # mechanism between processes on the same host. Used when the Memcached server
158
+ # is running on the same machine as the Dalli client.
159
+ ##
160
+ class UNIX < UNIXSocket
161
+ include Dalli::Socket::InstanceMethods
162
+
163
+ # options - supports enhanced logging in the case of a timeout
164
+ # server - used to support IO.select in the pipelined getter
165
+ attr_accessor :options
166
+
167
+ def self.open(path, options = {})
168
+ Timeout.timeout(options[:socket_timeout]) do
169
+ sock = new(path)
170
+ sock.options = { path: path }.merge(options)
171
+ sock
172
+ end
173
+ end
174
+ end
175
+ end
75
176
  end
76
177
  end
data/lib/dalli/version.rb CHANGED
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dalli
2
- VERSION = '2.0.1'
4
+ VERSION = '3.2.8'
5
+
6
+ MIN_SUPPORTED_MEMCACHED_VERSION = '1.4'
3
7
  end
data/lib/dalli.rb CHANGED
@@ -1,32 +1,50 @@
1
- require 'dalli/client'
2
- require 'dalli/ring'
3
- require 'dalli/server'
4
- require 'dalli/socket'
5
- require 'dalli/version'
6
- require 'dalli/options'
1
+ # frozen_string_literal: true
7
2
 
3
+ ##
4
+ # Namespace for all Dalli code.
5
+ ##
8
6
  module Dalli
7
+ autoload :Server, 'dalli/server'
8
+
9
9
  # generic error
10
10
  class DalliError < RuntimeError; end
11
+
11
12
  # socket/server communication error
12
13
  class NetworkError < DalliError; end
14
+
13
15
  # no server available/alive error
14
16
  class RingError < DalliError; end
15
- # application error in marshalling
17
+
18
+ # application error in marshalling serialization
16
19
  class MarshalError < DalliError; end
17
20
 
21
+ # application error in marshalling deserialization or decompression
22
+ class UnmarshalError < DalliError; end
23
+
24
+ # payload too big for memcached
25
+ class ValueOverMaxSize < DalliError; end
26
+
27
+ # operation is not permitted in a multi block
28
+ class NotPermittedMultiOpError < DalliError; end
29
+
30
+ # Implements the NullObject pattern to store an application-defined value for 'Key not found' responses.
31
+ class NilObject; end # rubocop:disable Lint/EmptyClass
32
+ NOT_FOUND = NilObject.new
33
+
34
+ QUIET = :dalli_multi
35
+
18
36
  def self.logger
19
- @logger ||= (rails_logger || default_logger)
37
+ @logger ||= rails_logger || default_logger
20
38
  end
21
39
 
22
40
  def self.rails_logger
23
41
  (defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger) ||
24
- (defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:debug) && RAILS_DEFAULT_LOGGER)
42
+ (defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:debug) && RAILS_DEFAULT_LOGGER)
25
43
  end
26
44
 
27
45
  def self.default_logger
28
46
  require 'logger'
29
- l = Logger.new(STDOUT)
47
+ l = Logger.new($stdout)
30
48
  l.level = Logger::INFO
31
49
  l
32
50
  end
@@ -36,6 +54,24 @@ module Dalli
36
54
  end
37
55
  end
38
56
 
39
- if defined?(RAILS_VERSION) && RAILS_VERSION < '3'
40
- raise Dalli::DalliError, "Dalli #{Dalli::VERSION} does not support Rails version < 3.0"
41
- end
57
+ require_relative 'dalli/version'
58
+
59
+ require_relative 'dalli/compressor'
60
+ require_relative 'dalli/client'
61
+ require_relative 'dalli/key_manager'
62
+ require_relative 'dalli/pipelined_getter'
63
+ require_relative 'dalli/ring'
64
+ require_relative 'dalli/protocol'
65
+ require_relative 'dalli/protocol/base'
66
+ require_relative 'dalli/protocol/binary'
67
+ require_relative 'dalli/protocol/connection_manager'
68
+ require_relative 'dalli/protocol/meta'
69
+ require_relative 'dalli/protocol/response_buffer'
70
+ require_relative 'dalli/protocol/server_config_parser'
71
+ require_relative 'dalli/protocol/ttl_sanitizer'
72
+ require_relative 'dalli/protocol/value_compressor'
73
+ require_relative 'dalli/protocol/value_marshaller'
74
+ require_relative 'dalli/protocol/value_serializer'
75
+ require_relative 'dalli/servers_arg_normalizer'
76
+ require_relative 'dalli/socket'
77
+ require_relative 'dalli/options'
@@ -1,52 +1,195 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/session/abstract/id'
2
4
  require 'dalli'
5
+ require 'connection_pool'
6
+ require 'English'
3
7
 
4
8
  module Rack
5
9
  module Session
6
- class Dalli < Abstract::ID
7
- attr_reader :pool
10
+ # Rack::Session::Dalli provides memcached based session management.
11
+ class Dalli < Abstract::PersistedSecure
12
+ attr_reader :data
8
13
 
9
- DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
10
- :namespace => 'rack:session',
11
- :memcache_server => 'localhost:11211'
14
+ # Don't freeze this until we fix the specs/implementation
15
+ # rubocop:disable Style/MutableConstant
16
+ DEFAULT_DALLI_OPTIONS = {
17
+ namespace: 'rack:session'
18
+ }
19
+ # rubocop:enable Style/MutableConstant
12
20
 
13
- def initialize(app, options={})
21
+ # Brings in a new Rack::Session::Dalli middleware with the given
22
+ # `:memcache_server`. The server is either a hostname, or a
23
+ # host-with-port string in the form of "host_name:port", or an array of
24
+ # such strings. For example:
25
+ #
26
+ # use Rack::Session::Dalli,
27
+ # :memcache_server => "mc.example.com:1234"
28
+ #
29
+ # If no `:memcache_server` option is specified, Rack::Session::Dalli will
30
+ # connect to localhost, port 11211 (the default memcached port). If
31
+ # `:memcache_server` is set to nil, Dalli::Client will look for
32
+ # ENV['MEMCACHE_SERVERS'] and use that value if it is available, or fall
33
+ # back to the same default behavior described above.
34
+ #
35
+ # Rack::Session::Dalli accepts the same options as Dalli::Client, so
36
+ # it's worth reviewing its documentation. Perhaps most importantly,
37
+ # if you don't specify a `:namespace` option, Rack::Session::Dalli
38
+ # will default to using 'rack:session'.
39
+ #
40
+ # It is not recommended to set `:expires_in`. Instead, use `:expire_after`,
41
+ # which will control both the expiration of the client cookie as well
42
+ # as the expiration of the corresponding entry in memcached.
43
+ #
44
+ # Rack::Session::Dalli also accepts a host of options that control how
45
+ # the sessions and session cookies are managed, including the
46
+ # aforementioned `:expire_after` option. Please see the documentation for
47
+ # Rack::Session::Abstract::Persisted for a detailed explanation of these
48
+ # options and their default values.
49
+ #
50
+ # Finally, if your web application is multithreaded, the
51
+ # Rack::Session::Dalli middleware can become a source of contention. You
52
+ # can use a connection pool of Dalli clients by passing in the
53
+ # `:pool_size` and/or `:pool_timeout` options. For example:
54
+ #
55
+ # use Rack::Session::Dalli,
56
+ # :memcache_server => "mc.example.com:1234",
57
+ # :pool_size => 10
58
+ #
59
+ # You must include the `connection_pool` gem in your project if you wish
60
+ # to use pool support. Please see the documentation for ConnectionPool
61
+ # for more information about it and its default options (which would only
62
+ # be applicable if you supplied one of the two options, but not both).
63
+ #
64
+ def initialize(app, options = {})
65
+ # Parent uses DEFAULT_OPTIONS to build @default_options for Rack::Session
14
66
  super
15
- mserv = @default_options[:memcache_server]
16
- mopts = @default_options.reject{|k,v| !DEFAULT_OPTIONS.include? k }
17
- @pool = options[:cache] || ::Dalli::Client.new(mserv, mopts)
67
+
68
+ # Determine the default TTL for newly-created sessions
69
+ @default_ttl = ttl(@default_options[:expire_after])
70
+ @data = build_data_source(options)
71
+ end
72
+
73
+ def find_session(_req, sid)
74
+ with_dalli_client([nil, {}]) do |dc|
75
+ existing_session = existing_session_for_sid(dc, sid)
76
+ return [sid, existing_session] unless existing_session.nil?
77
+
78
+ [create_sid_with_empty_session(dc), {}]
79
+ end
80
+ end
81
+
82
+ def write_session(_req, sid, session, options)
83
+ return false unless sid
84
+
85
+ key = memcached_key_from_sid(sid)
86
+ return false unless key
87
+
88
+ with_dalli_client(false) do |dc|
89
+ dc.set(memcached_key_from_sid(sid), session, ttl(options[:expire_after]))
90
+ sid
91
+ end
92
+ end
93
+
94
+ def delete_session(_req, sid, options)
95
+ with_dalli_client do |dc|
96
+ key = memcached_key_from_sid(sid)
97
+ dc.delete(key) if key
98
+ generate_sid_with(dc) unless options[:drop]
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ def memcached_key_from_sid(sid)
105
+ sid.private_id if sid.respond_to?(:private_id)
106
+ end
107
+
108
+ def existing_session_for_sid(client, sid)
109
+ return nil unless sid && !sid.empty?
110
+
111
+ key = memcached_key_from_sid(sid)
112
+ return nil if key.nil?
113
+
114
+ client.get(key)
115
+ end
116
+
117
+ def create_sid_with_empty_session(client)
118
+ loop do
119
+ sid = generate_sid_with(client)
120
+ key = memcached_key_from_sid(sid)
121
+
122
+ break sid if key && client.add(key, {}, @default_ttl)
123
+ end
18
124
  end
19
125
 
20
- def generate_sid
126
+ def generate_sid_with(client)
21
127
  loop do
22
- sid = super
23
- break sid unless @pool.get(sid)
128
+ raw_sid = generate_sid
129
+ sid = raw_sid.is_a?(String) ? Rack::Session::SessionId.new(raw_sid) : raw_sid
130
+ key = memcached_key_from_sid(sid)
131
+ break sid unless key && client.get(key)
24
132
  end
25
133
  end
26
134
 
27
- def get_session(env, sid)
28
- unless sid and session = @pool.get(sid)
29
- sid, session = generate_sid, {}
30
- unless @pool.add(sid, session)
31
- raise "Session collision on '#{sid.inspect}'"
135
+ def build_data_source(options)
136
+ server_configurations, client_options, pool_options = extract_dalli_options(options)
137
+
138
+ if pool_options.empty?
139
+ ::Dalli::Client.new(server_configurations, client_options)
140
+ else
141
+ ensure_connection_pool_added!
142
+ ConnectionPool.new(pool_options) do
143
+ ::Dalli::Client.new(server_configurations, client_options.merge(threadsafe: false))
32
144
  end
33
145
  end
34
- [sid, session]
35
146
  end
36
147
 
37
- def set_session(env, session_id, new_session, options)
38
- expiry = options[:expire_after]
39
- expiry = expiry.nil? ? 0 : expiry + 1
148
+ def extract_dalli_options(options)
149
+ raise 'Rack::Session::Dalli no longer supports the :cache option.' if options[:cache]
40
150
 
41
- @pool.set session_id, new_session, expiry
42
- session_id
151
+ client_options = retrieve_client_options(options)
152
+ server_configurations = client_options.delete(:memcache_server)
153
+
154
+ [server_configurations, client_options, retrieve_pool_options(options)]
43
155
  end
44
156
 
45
- def destroy_session(env, session_id, options)
46
- @pool.delete(session_id)
47
- generate_sid unless options[:drop]
157
+ def retrieve_client_options(options)
158
+ # Filter out Rack::Session-specific options and apply our defaults
159
+ filtered_opts = options.reject { |k, _| DEFAULT_OPTIONS.key? k }
160
+ DEFAULT_DALLI_OPTIONS.merge(filtered_opts)
48
161
  end
49
162
 
163
+ def retrieve_pool_options(options)
164
+ {}.tap do |pool_options|
165
+ pool_options[:size] = options.delete(:pool_size) if options[:pool_size]
166
+ pool_options[:timeout] = options.delete(:pool_timeout) if options[:pool_timeout]
167
+ end
168
+ end
169
+
170
+ def ensure_connection_pool_added!
171
+ require 'connection_pool'
172
+ rescue LoadError => e
173
+ warn "You don't have connection_pool installed in your application. " \
174
+ 'Please add it to your Gemfile and run bundle install'
175
+ raise e
176
+ end
177
+
178
+ def with_dalli_client(result_on_error = nil, &block)
179
+ @data.with(&block)
180
+ rescue ::Dalli::DalliError, Errno::ECONNREFUSED
181
+ raise if $ERROR_INFO.message.include?('undefined class')
182
+
183
+ if $VERBOSE
184
+ warn "#{self} is unable to find memcached server."
185
+ warn $ERROR_INFO.inspect
186
+ end
187
+ result_on_error
188
+ end
189
+
190
+ def ttl(expire_after)
191
+ expire_after.nil? ? 0 : expire_after + 1
192
+ end
50
193
  end
51
194
  end
52
195
  end