dalli 2.7.6 → 3.2.2

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 +5 -5
  2. data/Gemfile +5 -1
  3. data/History.md +175 -0
  4. data/README.md +27 -213
  5. data/lib/dalli/cas/client.rb +2 -57
  6. data/lib/dalli/client.rb +228 -254
  7. data/lib/dalli/compressor.rb +13 -2
  8. data/lib/dalli/key_manager.rb +113 -0
  9. data/lib/dalli/options.rb +7 -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 +95 -84
  30. data/lib/dalli/server.rb +4 -743
  31. data/lib/dalli/servers_arg_normalizer.rb +54 -0
  32. data/lib/dalli/socket.rb +118 -130
  33. data/lib/dalli/version.rb +5 -1
  34. data/lib/dalli.rb +45 -14
  35. data/lib/rack/session/dalli.rb +156 -50
  36. metadata +64 -27
  37. data/lib/action_dispatch/middleware/session/dalli_store.rb +0 -81
  38. data/lib/active_support/cache/dalli_store.rb +0 -403
  39. data/lib/dalli/railtie.rb +0 -7
@@ -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.fetch(ENV_VAR_NAME, nil) || 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,162 +1,150 @@
1
- require 'rbconfig'
1
+ # frozen_string_literal: true
2
2
 
3
- module Dalli::Server::TCPSocketOptions
4
- def setsockopts(sock, options)
5
- sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
6
- sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if options[:keepalive]
7
- sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVBUF, options[:rcvbuf]) if options[:rcvbuf]
8
- sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, options[:sndbuf]) if options[:sndbuf]
9
- end
10
- end
3
+ require 'openssl'
4
+ require 'rbconfig'
11
5
 
12
- begin
13
- require 'kgio'
14
- 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
15
24
 
16
- class Dalli::Server::KSocket < Kgio::Socket
17
- 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
18
31
 
19
- def kgio_wait_readable
20
- IO.select([self], nil, nil, options[:socket_timeout]) || raise(Timeout::Error, "IO timeout")
21
- end
32
+ value << result
33
+ end
34
+ value
35
+ end
22
36
 
23
- def kgio_wait_writable
24
- IO.select(nil, [self], nil, options[:socket_timeout]) || raise(Timeout::Error, "IO timeout")
25
- end
37
+ WAIT_RCS = %i[wait_writable wait_readable].freeze
26
38
 
27
- 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
28
42
 
29
- def readfull(count)
30
- value = ''
31
- while true
32
- value << kgio_read!(count - value.bytesize)
33
- break if value.bytesize == count
43
+ !WAIT_RCS.include?(result)
34
44
  end
35
- value
36
- end
37
45
 
38
- def read_available
39
- value = ''
40
- while true
41
- ret = kgio_tryread(8196)
42
- case ret
43
- when nil
44
- raise EOFError, 'end of stream'
45
- when :wait_readable
46
- break
47
- else
48
- value << ret
49
- end
50
- end
51
- value
52
- end
53
- end
46
+ def nonblock_timed_out?(result)
47
+ return true if result == :wait_readable && !wait_readable(options[:socket_timeout])
54
48
 
55
- class Dalli::Server::KSocket::TCP < Dalli::Server::KSocket
56
- extend Dalli::Server::TCPSocketOptions
57
-
58
- def self.open(host, port, server, options = {})
59
- addr = Socket.pack_sockaddr_in(port, host)
60
- sock = start(addr)
61
- setsockopts(sock, options)
62
- sock.options = options
63
- sock.server = server
64
- sock.kgio_wait_writable
65
- sock
66
- end
67
- 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
68
52
 
69
- class Dalli::Server::KSocket::UNIX < Dalli::Server::KSocket
70
- def self.open(path, server, options = {})
71
- addr = Socket.pack_sockaddr_un(path)
72
- sock = start(addr)
73
- sock.options = options
74
- sock.server = server
75
- sock.kgio_wait_writable
76
- sock
53
+ FILTERED_OUT_OPTIONS = %i[username password].freeze
54
+ def logged_options
55
+ options.reject { |k, _| FILTERED_OUT_OPTIONS.include? k }
56
+ end
77
57
  end
78
- end
79
58
 
80
- if ::Kgio.respond_to?(:wait_readable=)
81
- ::Kgio.wait_readable = :kgio_wait_readable
82
- ::Kgio.wait_writable = :kgio_wait_writable
83
- end
84
-
85
- 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
86
69
 
87
- puts "Using standard socket IO (#{RUBY_DESCRIPTION})" if defined?($TESTING) && $TESTING
88
- module Dalli::Server::KSocket
89
- module InstanceMethods
90
- def readfull(count)
91
- value = ''
92
- begin
93
- while true
94
- value << read_nonblock(count - value.bytesize)
95
- break if value.bytesize == count
96
- end
97
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
98
- if IO.select([self], nil, nil, options[:socket_timeout])
99
- retry
100
- else
101
- raise Timeout::Error, "IO timeout: #{options.inspect}"
102
- end
70
+ unless method_defined?(:wait_readable)
71
+ def wait_readable(timeout = nil)
72
+ to_io.wait_readable(timeout)
103
73
  end
104
- value
105
74
  end
106
75
 
107
- def read_available
108
- value = ''
109
- while true
110
- begin
111
- value << read_nonblock(8196)
112
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
113
- break
114
- end
76
+ unless method_defined?(:wait_writable)
77
+ def wait_writable(timeout = nil)
78
+ to_io.wait_writable(timeout)
115
79
  end
116
- value
117
80
  end
118
81
  end
119
82
 
120
- def self.included(receiver)
121
- receiver.send(:attr_accessor, :options, :server)
122
- receiver.send(:include, InstanceMethods)
123
- end
124
- 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
125
90
 
126
- class Dalli::Server::KSocket::TCP < TCPSocket
127
- extend Dalli::Server::TCPSocketOptions
128
- include Dalli::Server::KSocket
129
-
130
- def self.open(host, port, server, options = {})
131
- Timeout.timeout(options[:socket_timeout]) do
132
- sock = new(host, port)
133
- setsockopts(sock, options)
134
- sock.options = {:host => host, :port => port}.merge(options)
135
- sock.server = server
136
- 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]
137
106
  end
138
- end
139
- end
140
107
 
141
- if RbConfig::CONFIG['host_os'] =~ /mingw|mswin/
142
- class Dalli::Server::KSocket::UNIX
143
- def initialize(*args)
144
- 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
145
114
  end
146
115
  end
147
- else
148
- class Dalli::Server::KSocket::UNIX < UNIXSocket
149
- include Dalli::Server::KSocket
150
116
 
151
- def self.open(path, server, options = {})
152
- Timeout.timeout(options[:socket_timeout]) do
153
- sock = new(path)
154
- sock.options = {:path => path}.merge(options)
155
- sock.server = server
156
- 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
157
146
  end
158
147
  end
159
148
  end
160
-
161
149
  end
162
150
  end
data/lib/dalli/version.rb CHANGED
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dalli
2
- VERSION = '2.7.6'
4
+ VERSION = '3.2.2'
5
+
6
+ MIN_SUPPORTED_MEMCACHED_VERSION = '1.4'
3
7
  end
data/lib/dalli.rb CHANGED
@@ -1,36 +1,50 @@
1
- require 'dalli/compressor'
2
- require 'dalli/client'
3
- require 'dalli/ring'
4
- require 'dalli/server'
5
- require 'dalli/socket'
6
- require 'dalli/version'
7
- require 'dalli/options'
8
- require 'dalli/railtie' if defined?(::Rails::Railtie)
1
+ # frozen_string_literal: true
9
2
 
3
+ ##
4
+ # Namespace for all Dalli code.
5
+ ##
10
6
  module Dalli
7
+ autoload :Server, 'dalli/server'
8
+
11
9
  # generic error
12
10
  class DalliError < RuntimeError; end
11
+
13
12
  # socket/server communication error
14
13
  class NetworkError < DalliError; end
14
+
15
15
  # no server available/alive error
16
16
  class RingError < DalliError; end
17
+
17
18
  # application error in marshalling serialization
18
19
  class MarshalError < DalliError; end
20
+
19
21
  # application error in marshalling deserialization or decompression
20
22
  class UnmarshalError < DalliError; end
21
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
+
22
36
  def self.logger
23
37
  @logger ||= (rails_logger || default_logger)
24
38
  end
25
39
 
26
40
  def self.rails_logger
27
41
  (defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger) ||
28
- (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)
29
43
  end
30
44
 
31
45
  def self.default_logger
32
46
  require 'logger'
33
- l = Logger.new(STDOUT)
47
+ l = Logger.new($stdout)
34
48
  l.level = Logger::INFO
35
49
  l
36
50
  end
@@ -38,9 +52,26 @@ module Dalli
38
52
  def self.logger=(logger)
39
53
  @logger = logger
40
54
  end
41
-
42
55
  end
43
56
 
44
- if defined?(RAILS_VERSION) && RAILS_VERSION < '3'
45
- raise Dalli::DalliError, "Dalli #{Dalli::VERSION} does not support Rails version < 3.0"
46
- 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'