dalli 2.7.8 → 3.2.1

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.

Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +12 -1
  3. data/History.md +151 -0
  4. data/README.md +27 -223
  5. data/lib/dalli/cas/client.rb +1 -57
  6. data/lib/dalli/client.rb +227 -254
  7. data/lib/dalli/compressor.rb +12 -2
  8. data/lib/dalli/key_manager.rb +113 -0
  9. data/lib/dalli/options.rb +6 -7
  10. data/lib/dalli/pipelined_getter.rb +177 -0
  11. data/lib/dalli/protocol/base.rb +241 -0
  12. data/lib/dalli/protocol/binary/request_formatter.rb +117 -0
  13. data/lib/dalli/protocol/binary/response_header.rb +36 -0
  14. data/lib/dalli/protocol/binary/response_processor.rb +239 -0
  15. data/lib/dalli/protocol/binary/sasl_authentication.rb +60 -0
  16. data/lib/dalli/protocol/binary.rb +173 -0
  17. data/lib/dalli/protocol/connection_manager.rb +252 -0
  18. data/lib/dalli/protocol/meta/key_regularizer.rb +31 -0
  19. data/lib/dalli/protocol/meta/request_formatter.rb +108 -0
  20. data/lib/dalli/protocol/meta/response_processor.rb +211 -0
  21. data/lib/dalli/protocol/meta.rb +177 -0
  22. data/lib/dalli/protocol/response_buffer.rb +54 -0
  23. data/lib/dalli/protocol/server_config_parser.rb +84 -0
  24. data/lib/dalli/protocol/ttl_sanitizer.rb +45 -0
  25. data/lib/dalli/protocol/value_compressor.rb +85 -0
  26. data/lib/dalli/protocol/value_marshaller.rb +59 -0
  27. data/lib/dalli/protocol/value_serializer.rb +91 -0
  28. data/lib/dalli/protocol.rb +8 -0
  29. data/lib/dalli/ring.rb +94 -83
  30. data/lib/dalli/server.rb +3 -746
  31. data/lib/dalli/servers_arg_normalizer.rb +54 -0
  32. data/lib/dalli/socket.rb +117 -137
  33. data/lib/dalli/version.rb +4 -1
  34. data/lib/dalli.rb +43 -15
  35. data/lib/rack/session/dalli.rb +95 -95
  36. metadata +43 -48
  37. data/lib/action_dispatch/middleware/session/dalli_store.rb +0 -82
  38. data/lib/active_support/cache/dalli_store.rb +0 -429
  39. data/lib/dalli/railtie.rb +0 -8
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dalli
4
+ ##
5
+ # This module contains methods for validating and normalizing the servers
6
+ # argument passed to the client. This argument can be nil, a string, or
7
+ # an array of strings. Each string value in the argument can represent
8
+ # a single server or a comma separated list of servers.
9
+ #
10
+ # If nil, it falls back to the values of ENV['MEMCACHE_SERVERS'] if the latter is
11
+ # defined. If that environment value is not defined, a default of '127.0.0.1:11211'
12
+ # is used.
13
+ #
14
+ # A server config string can take one of three forms:
15
+ # * A colon separated string of (host, port, weight) where both port and
16
+ # weight are optional (e.g. 'localhost', 'abc.com:12345', 'example.org:22222:3')
17
+ # * A colon separated string of (UNIX socket, weight) where the weight is optional
18
+ # (e.g. '/var/run/memcached/socket', '/tmp/xyz:3') (not supported on Windows)
19
+ # * A URI with a 'memcached' protocol, which will typically include a username/password
20
+ #
21
+ # The methods in this module do not validate the format of individual server strings, but
22
+ # rather normalize the argument into a compact array, wherein each array entry corresponds
23
+ # to a single server config string. If that normalization is not possible, then an
24
+ # ArgumentError is thrown.
25
+ ##
26
+ module ServersArgNormalizer
27
+ ENV_VAR_NAME = 'MEMCACHE_SERVERS'
28
+ DEFAULT_SERVERS = ['127.0.0.1:11211'].freeze
29
+
30
+ ##
31
+ # Normalizes the argument into an array of servers.
32
+ # If the argument is a string, or an array containing strings, it's expected that the URIs are comma separated e.g.
33
+ # "memcache1.example.com:11211,memcache2.example.com:11211,memcache3.example.com:11211"
34
+ def self.normalize_servers(arg)
35
+ arg = apply_defaults(arg)
36
+ validate_type(arg)
37
+ Array(arg).flat_map { |s| s.split(',') }.reject(&:empty?)
38
+ end
39
+
40
+ def self.apply_defaults(arg)
41
+ return arg unless arg.nil?
42
+
43
+ ENV[ENV_VAR_NAME] || DEFAULT_SERVERS
44
+ end
45
+
46
+ def self.validate_type(arg)
47
+ return if arg.is_a?(String)
48
+ return if arg.is_a?(Array) && arg.all?(String)
49
+
50
+ raise ArgumentError,
51
+ 'An explicit servers argument must be a comma separated string or an array containing strings.'
52
+ end
53
+ end
54
+ end
data/lib/dalli/socket.rb CHANGED
@@ -1,170 +1,150 @@
1
1
  # frozen_string_literal: true
2
- require 'rbconfig'
3
2
 
4
- module Dalli::Server::TCPSocketOptions
5
- def setsockopts(sock, options)
6
- sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
7
- sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if options[:keepalive]
8
- sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVBUF, options[:rcvbuf]) if options[:rcvbuf]
9
- sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, options[:sndbuf]) if options[:sndbuf]
10
- end
11
- end
3
+ require 'openssl'
4
+ require 'rbconfig'
12
5
 
13
- begin
14
- require 'kgio'
15
- puts "Using kgio socket IO" if defined?($TESTING) && $TESTING
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 = +''
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
16
24
 
17
- class Dalli::Server::KSocket < Kgio::Socket
18
- attr_accessor :options, :server
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
19
31
 
20
- def kgio_wait_readable
21
- IO.select([self], nil, nil, options[:socket_timeout]) || raise(Timeout::Error, "IO timeout")
22
- end
32
+ value << result
33
+ end
34
+ value
35
+ end
23
36
 
24
- def kgio_wait_writable
25
- IO.select(nil, [self], nil, options[:socket_timeout]) || raise(Timeout::Error, "IO timeout")
26
- end
37
+ WAIT_RCS = %i[wait_writable wait_readable].freeze
27
38
 
28
- 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
29
42
 
30
- def readfull(count)
31
- value = String.new('')
32
- while true
33
- value << kgio_read!(count - value.bytesize)
34
- break if value.bytesize == count
43
+ !WAIT_RCS.include?(result)
35
44
  end
36
- value
37
- end
38
45
 
39
- def read_available
40
- value = String.new('')
41
- while true
42
- ret = kgio_tryread(8196)
43
- case ret
44
- when nil
45
- raise EOFError, 'end of stream'
46
- when :wait_readable
47
- break
48
- else
49
- value << ret
50
- end
51
- end
52
- value
53
- end
54
- end
46
+ def nonblock_timed_out?(result)
47
+ return true if result == :wait_readable && !wait_readable(options[:socket_timeout])
55
48
 
56
- class Dalli::Server::KSocket::TCP < Dalli::Server::KSocket
57
- extend Dalli::Server::TCPSocketOptions
58
-
59
- def self.open(host, port, server, options = {})
60
- addr = Socket.pack_sockaddr_in(port, host)
61
- sock = start(addr)
62
- setsockopts(sock, options)
63
- sock.options = options
64
- sock.server = server
65
- sock.kgio_wait_writable
66
- sock
67
- rescue Timeout::Error
68
- sock.close if sock
69
- raise
70
- end
71
- 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
72
52
 
73
- class Dalli::Server::KSocket::UNIX < Dalli::Server::KSocket
74
- def self.open(path, server, options = {})
75
- addr = Socket.pack_sockaddr_un(path)
76
- sock = start(addr)
77
- sock.options = options
78
- sock.server = server
79
- sock.kgio_wait_writable
80
- sock
81
- rescue Timeout::Error
82
- sock.close if sock
83
- raise
53
+ FILTERED_OUT_OPTIONS = %i[username password].freeze
54
+ def logged_options
55
+ options.reject { |k, _| FILTERED_OUT_OPTIONS.include? k }
56
+ end
84
57
  end
85
- end
86
58
 
87
- if ::Kgio.respond_to?(:wait_readable=)
88
- ::Kgio.wait_readable = :kgio_wait_readable
89
- ::Kgio.wait_writable = :kgio_wait_writable
90
- end
91
-
92
- rescue LoadError
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
93
69
 
94
- puts "Using standard socket IO (#{RUBY_DESCRIPTION})" if defined?($TESTING) && $TESTING
95
- module Dalli::Server::KSocket
96
- module InstanceMethods
97
- def readfull(count)
98
- value = String.new('')
99
- begin
100
- while true
101
- value << read_nonblock(count - value.bytesize)
102
- break if value.bytesize == count
103
- end
104
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
105
- if IO.select([self], nil, nil, options[:socket_timeout])
106
- retry
107
- else
108
- safe_options = options.reject{|k,v| [:username, :password].include? k}
109
- raise Timeout::Error, "IO timeout: #{safe_options.inspect}"
110
- end
70
+ unless method_defined?(:wait_readable)
71
+ def wait_readable(timeout = nil)
72
+ to_io.wait_readable(timeout)
111
73
  end
112
- value
113
74
  end
114
75
 
115
- def read_available
116
- value = String.new('')
117
- while true
118
- begin
119
- value << read_nonblock(8196)
120
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
121
- break
122
- end
76
+ unless method_defined?(:wait_writable)
77
+ def wait_writable(timeout = nil)
78
+ to_io.wait_writable(timeout)
123
79
  end
124
- value
125
80
  end
126
81
  end
127
82
 
128
- def self.included(receiver)
129
- receiver.send(:attr_accessor, :options, :server)
130
- receiver.send(:include, InstanceMethods)
131
- end
132
- end
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
133
90
 
134
- class Dalli::Server::KSocket::TCP < TCPSocket
135
- extend Dalli::Server::TCPSocketOptions
136
- include Dalli::Server::KSocket
137
-
138
- def self.open(host, port, server, options = {})
139
- Timeout.timeout(options[:socket_timeout]) do
140
- sock = new(host, port)
141
- setsockopts(sock, options)
142
- sock.options = {:host => host, :port => port}.merge(options)
143
- sock.server = server
144
- sock
91
+ def self.open(host, port, options = {})
92
+ Timeout.timeout(options[:socket_timeout]) do
93
+ sock = new(host, port)
94
+ sock.options = { host: host, port: port }.merge(options)
95
+ init_socket_options(sock, options)
96
+
97
+ options[:ssl_context] ? wrapping_ssl_socket(sock, host, options[:ssl_context]) : sock
98
+ end
99
+ end
100
+
101
+ def self.init_socket_options(sock, options)
102
+ sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
103
+ sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options[:keepalive]
104
+ sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_RCVBUF, options[:rcvbuf]) if options[:rcvbuf]
105
+ sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_SNDBUF, options[:sndbuf]) if options[:sndbuf]
145
106
  end
146
- end
147
- end
148
107
 
149
- if RbConfig::CONFIG['host_os'] =~ /mingw|mswin/
150
- class Dalli::Server::KSocket::UNIX
151
- def initialize(*args)
152
- raise Dalli::DalliError, "Unix sockets are not supported on Windows platform."
108
+ def self.wrapping_ssl_socket(tcp_socket, host, ssl_context)
109
+ ssl_socket = Dalli::Socket::SSLSocket.new(tcp_socket, ssl_context)
110
+ ssl_socket.hostname = host
111
+ ssl_socket.sync_close = true
112
+ ssl_socket.connect
113
+ ssl_socket
153
114
  end
154
115
  end
155
- else
156
- class Dalli::Server::KSocket::UNIX < UNIXSocket
157
- include Dalli::Server::KSocket
158
116
 
159
- def self.open(path, server, options = {})
160
- Timeout.timeout(options[:socket_timeout]) do
161
- sock = new(path)
162
- sock.options = {:path => path}.merge(options)
163
- sock.server = server
164
- sock
117
+ if /mingw|mswin/.match?(RbConfig::CONFIG['host_os'])
118
+ ##
119
+ # UNIX domain sockets are not supported on Windows platforms.
120
+ ##
121
+ class UNIX
122
+ def initialize(*_args)
123
+ raise Dalli::DalliError, 'Unix sockets are not supported on Windows platform.'
124
+ end
125
+ end
126
+ else
127
+
128
+ ##
129
+ # UNIX represents a UNIX domain socket, which is an interprocess communication
130
+ # mechanism between processes on the same host. Used when the Memcached server
131
+ # is running on the same machine as the Dalli client.
132
+ ##
133
+ class UNIX < UNIXSocket
134
+ include Dalli::Socket::InstanceMethods
135
+
136
+ # options - supports enhanced logging in the case of a timeout
137
+ # server - used to support IO.select in the pipelined getter
138
+ attr_accessor :options
139
+
140
+ def self.open(path, options = {})
141
+ Timeout.timeout(options[:socket_timeout]) do
142
+ sock = new(path)
143
+ sock.options = { path: path }.merge(options)
144
+ sock
145
+ end
165
146
  end
166
147
  end
167
148
  end
168
-
169
149
  end
170
150
  end
data/lib/dalli/version.rb CHANGED
@@ -1,4 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Dalli
3
- VERSION = '2.7.8'
4
+ VERSION = '3.2.1'
5
+
6
+ MIN_SUPPORTED_MEMCACHED_VERSION = '1.4'
4
7
  end
data/lib/dalli.rb CHANGED
@@ -1,26 +1,37 @@
1
1
  # frozen_string_literal: true
2
- require 'dalli/compressor'
3
- require 'dalli/client'
4
- require 'dalli/ring'
5
- require 'dalli/server'
6
- require 'dalli/socket'
7
- require 'dalli/version'
8
- require 'dalli/options'
9
- require 'dalli/railtie' if defined?(::Rails::Railtie)
10
2
 
3
+ ##
4
+ # Namespace for all Dalli code.
5
+ ##
11
6
  module Dalli
7
+ autoload :Server, 'dalli/server'
8
+
12
9
  # generic error
13
10
  class DalliError < RuntimeError; end
11
+
14
12
  # socket/server communication error
15
13
  class NetworkError < DalliError; end
14
+
16
15
  # no server available/alive error
17
16
  class RingError < DalliError; end
17
+
18
18
  # application error in marshalling serialization
19
19
  class MarshalError < DalliError; end
20
+
20
21
  # application error in marshalling deserialization or decompression
21
22
  class UnmarshalError < DalliError; end
23
+
22
24
  # payload too big for memcached
23
- class ValueOverMaxSize < RuntimeError; end
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
24
35
 
25
36
  def self.logger
26
37
  @logger ||= (rails_logger || default_logger)
@@ -28,12 +39,12 @@ module Dalli
28
39
 
29
40
  def self.rails_logger
30
41
  (defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger) ||
31
- (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)
32
43
  end
33
44
 
34
45
  def self.default_logger
35
46
  require 'logger'
36
- l = Logger.new(STDOUT)
47
+ l = Logger.new($stdout)
37
48
  l.level = Logger::INFO
38
49
  l
39
50
  end
@@ -41,9 +52,26 @@ module Dalli
41
52
  def self.logger=(logger)
42
53
  @logger = logger
43
54
  end
44
-
45
55
  end
46
56
 
47
- if defined?(RAILS_VERSION) && RAILS_VERSION < '3'
48
- raise Dalli::DalliError, "Dalli #{Dalli::VERSION} does not support Rails version < 3.0"
49
- 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'