dalli 3.1.1 → 3.1.5

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
2
  SHA256:
3
- metadata.gz: 89dd04ff2b4fd6812f2fae613cc984c7d34029a2637ebf5c11d5398361fa7993
4
- data.tar.gz: caf5ad6315ddb803ba61418d189c99ce7849f1acced49cf6e32e78e5ca35817f
3
+ metadata.gz: 0a9612ab509bc2a6b7bd2560a7a10564183b3b9fbb2aa0655f339b30067407fa
4
+ data.tar.gz: 510c3ed03a4e2b4ae4e7f8540eac19e7d28508341b768edc938f9886d5504fbe
5
5
  SHA512:
6
- metadata.gz: e425b9704308ce7f3737c7e07dcd2699448ba173fd8fa3ac1e9d3d8867890ce1029dd69f98fb4856bab6c8d81685d34be1c41a192039fccb631c2d4b4749b85a
7
- data.tar.gz: 0afdb9aa3fde689a6b84b72aa44aa228e23994f747cce60f8a3471f250a7fa60fdbb3481e4ab4f2665de80be4799b3294f308bb86efa815867e121cd7dc223c9
6
+ metadata.gz: 81e31363e781e948f66c3a143cd506162e4b51b4c67bb76a8cbdbfb2efdcdbd279216b24cdc077c3c2e4ad88b356ffec1a74f4428c556b05d17591b171b874d2
7
+ data.tar.gz: ef45c25f8552e203751a51cb32172b5670b6b509ac45cbcc41b19a4f70688f0f50eb01b09af32c3faa05d643dd93acce2e748d8cedbba5dded585070b6ed7ec6
data/History.md CHANGED
@@ -4,6 +4,29 @@ Dalli Changelog
4
4
  Unreleased
5
5
  ==========
6
6
 
7
+ 3.1.5
8
+ ==========
9
+
10
+ - Fix bug with get_cas key with "Not found" value [petergoldstein]
11
+ - Replace should return nil, not raise error, on miss (petergoldstein)
12
+
13
+ 3.1.4
14
+ ==========
15
+
16
+ - Improve response parsing performance (casperisfine)
17
+ - Reorganize binary protocol parsing a bit (petergoldstein)
18
+ - Fix handling of non-ASCII keys in get_multi (petergoldstein)
19
+
20
+ 3.1.3
21
+ ==========
22
+
23
+ - Restore falsey behavior on delete/delete_cas for nonexistent key (petergoldstein)
24
+
25
+ 3.1.2
26
+ ==========
27
+
28
+ - Make quiet? / multi? public on Dalli::Protocol::Binary (petergoldstein)
29
+
7
30
  3.1.1
8
31
  ==========
9
32
 
data/lib/dalli/client.rb CHANGED
@@ -86,8 +86,6 @@ module Dalli
86
86
  # value and CAS will be passed to the block.
87
87
  def get_cas(key)
88
88
  (value, cas) = perform(:cas, key)
89
- # TODO: This is odd. Confirm this is working as expected.
90
- value = nil if !value || value == 'Not found'
91
89
  return [value, cas] unless block_given?
92
90
 
93
91
  yield value, cas
@@ -0,0 +1,234 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'socket'
5
+ require 'timeout'
6
+
7
+ module Dalli
8
+ module Protocol
9
+ ##
10
+ # Base class for a single Memcached server, containing logic common to all
11
+ # protocols. Contains logic for managing connection state to the server and value
12
+ # handling.
13
+ ##
14
+ class Base
15
+ extend Forwardable
16
+
17
+ attr_accessor :weight, :options
18
+
19
+ def_delegators :@value_marshaller, :serializer, :compressor, :compression_min_size, :compress_by_default?
20
+ def_delegators :@connection_manager, :name, :sock, :hostname, :port, :close, :connected?, :socket_timeout,
21
+ :socket_type, :up!, :down!, :write, :reconnect_down_server?, :raise_down_error
22
+
23
+ def initialize(attribs, client_options = {})
24
+ hostname, port, socket_type, @weight, user_creds = ServerConfigParser.parse(attribs)
25
+ @options = client_options.merge(user_creds)
26
+ @value_marshaller = ValueMarshaller.new(@options)
27
+ @connection_manager = ConnectionManager.new(hostname, port, socket_type, @options)
28
+ end
29
+
30
+ # Chokepoint method for error handling and ensuring liveness
31
+ def request(opkey, *args)
32
+ verify_state(opkey)
33
+
34
+ begin
35
+ send(opkey, *args)
36
+ rescue Dalli::MarshalError => e
37
+ log_marshal_err(args.first, e)
38
+ raise
39
+ rescue Dalli::DalliError
40
+ raise
41
+ rescue StandardError => e
42
+ log_unexpected_err(e)
43
+ down!
44
+ end
45
+ end
46
+
47
+ ##
48
+ # Boolean method used by clients of this class to determine if this
49
+ # particular memcached instance is available for use.
50
+ def alive?
51
+ ensure_connected!
52
+ rescue Dalli::NetworkError
53
+ # ensure_connected! raises a NetworkError if connection fails. We
54
+ # want to capture that error and convert it to a boolean value here.
55
+ false
56
+ end
57
+
58
+ def lock!; end
59
+
60
+ def unlock!; end
61
+
62
+ # Start reading key/value pairs from this connection. This is usually called
63
+ # after a series of GETKQ commands. A NOOP is sent, and the server begins
64
+ # flushing responses for kv pairs that were found.
65
+ #
66
+ # Returns nothing.
67
+ def pipeline_response_setup
68
+ verify_state(:getkq)
69
+ write_noop
70
+ response_buffer.reset
71
+ @connection_manager.start_request!
72
+ end
73
+
74
+ # Attempt to receive and parse as many key/value pairs as possible
75
+ # from this server. After #pipeline_response_setup, this should be invoked
76
+ # repeatedly whenever this server's socket is readable until
77
+ # #pipeline_complete?.
78
+ #
79
+ # Returns a Hash of kv pairs received.
80
+ def pipeline_next_responses
81
+ reconnect_on_pipeline_complete!
82
+ values = {}
83
+
84
+ response_buffer.read
85
+
86
+ status, cas, key, value = response_buffer.process_single_getk_response
87
+ # status is not nil only if we have a full response to parse
88
+ # in the buffer
89
+ until status.nil?
90
+ # If the status is ok and key is nil, then this is the response
91
+ # to the noop at the end of the pipeline
92
+ finish_pipeline && break if status && key.nil?
93
+
94
+ # If the status is ok and the key is not nil, then this is a
95
+ # getkq response with a value that we want to set in the response hash
96
+ values[key] = [value, cas] unless key.nil?
97
+
98
+ # Get the next response from the buffer
99
+ status, cas, key, value = response_buffer.process_single_getk_response
100
+ end
101
+
102
+ values
103
+ rescue SystemCallError, Timeout::Error, EOFError => e
104
+ @connection_manager.error_on_request!(e)
105
+ end
106
+
107
+ # Abort current pipelined get. Generally used to signal an external
108
+ # timeout during pipelined get. The underlying socket is
109
+ # disconnected, and the exception is swallowed.
110
+ #
111
+ # Returns nothing.
112
+ def pipeline_abort
113
+ response_buffer.clear
114
+ @connection_manager.abort_request!
115
+ return true unless connected?
116
+
117
+ # Closes the connection, which ensures that our connection
118
+ # is in a clean state for future requests
119
+ @connection_manager.error_on_request!('External timeout')
120
+ rescue NetworkError
121
+ true
122
+ end
123
+
124
+ # Did the last call to #pipeline_response_setup complete successfully?
125
+ def pipeline_complete?
126
+ !response_buffer.in_progress?
127
+ end
128
+
129
+ def username
130
+ @options[:username] || ENV['MEMCACHE_USERNAME']
131
+ end
132
+
133
+ def password
134
+ @options[:password] || ENV['MEMCACHE_PASSWORD']
135
+ end
136
+
137
+ def require_auth?
138
+ !username.nil?
139
+ end
140
+
141
+ def quiet?
142
+ Thread.current[::Dalli::QUIET]
143
+ end
144
+ alias multi? quiet?
145
+
146
+ # NOTE: Additional public methods should be overridden in Dalli::Threadsafe
147
+
148
+ private
149
+
150
+ ##
151
+ # Checks to see if we can execute the specified operation. Checks
152
+ # whether the connection is in use, and whether the command is allowed
153
+ ##
154
+ def verify_state(opkey)
155
+ @connection_manager.confirm_ready!
156
+ verify_allowed_quiet!(opkey) if quiet?
157
+
158
+ # The ensure_connected call has the side effect of connecting the
159
+ # underlying socket if it is not connected, or there's been a disconnect
160
+ # because of timeout or other error. Method raises an error
161
+ # if it can't connect
162
+ raise_down_error unless ensure_connected!
163
+ end
164
+
165
+ # The socket connection to the underlying server is initialized as a side
166
+ # effect of this call. In fact, this is the ONLY place where that
167
+ # socket connection is initialized.
168
+ #
169
+ # Both this method and connect need to be in this class so we can do auth
170
+ # as required
171
+ #
172
+ # Since this is invoked exclusively in verify_state!, we don't need to worry about
173
+ # thread safety. Using it elsewhere may require revisiting that assumption.
174
+ def ensure_connected!
175
+ return true if connected?
176
+ return false unless reconnect_down_server?
177
+
178
+ connect # This call needs to be in this class so we can do auth
179
+ connected?
180
+ end
181
+
182
+ def cache_nils?(opts)
183
+ return false unless opts.is_a?(Hash)
184
+
185
+ opts[:cache_nils] ? true : false
186
+ end
187
+
188
+ def connect
189
+ @connection_manager.establish_connection
190
+ authenticate_connection if require_auth?
191
+ @version = version # Connect socket if not authed
192
+ up!
193
+ rescue Dalli::DalliError
194
+ raise
195
+ end
196
+
197
+ def pipelined_get(keys)
198
+ req = +''
199
+ keys.each do |key|
200
+ req << quiet_get_request(key)
201
+ end
202
+ # Could send noop here instead of in pipeline_response_setup
203
+ write(req)
204
+ end
205
+
206
+ def response_buffer
207
+ @response_buffer ||= ResponseBuffer.new(@connection_manager, response_processor)
208
+ end
209
+
210
+ # Called after the noop response is received at the end of a set
211
+ # of pipelined gets
212
+ def finish_pipeline
213
+ response_buffer.clear
214
+ @connection_manager.finish_request!
215
+
216
+ true # to simplify response
217
+ end
218
+
219
+ def reconnect_on_pipeline_complete!
220
+ @connection_manager.reconnect! 'pipelined get has completed' if pipeline_complete?
221
+ end
222
+
223
+ def log_marshal_err(key, err)
224
+ Dalli.logger.error "Marshalling error for key '#{key}': #{err.message}"
225
+ Dalli.logger.error 'You are trying to cache a Ruby object which cannot be serialized to memcached.'
226
+ end
227
+
228
+ def log_unexpected_err(err)
229
+ Dalli.logger.error "Unexpected exception during Dalli request: #{err.class.name}: #{err.message}"
230
+ Dalli.logger.error err.backtrace.join("\n\t")
231
+ end
232
+ end
233
+ end
234
+ end
@@ -46,11 +46,13 @@ module Dalli
46
46
  [resp_header, body]
47
47
  end
48
48
 
49
- def unpack_response_body(extra_len, key_len, body, unpack)
50
- bitflags = extra_len.positive? ? body.byteslice(0, extra_len).unpack1('N') : 0x0
51
- key = body.byteslice(extra_len, key_len) if key_len.positive?
52
- value = body.byteslice(extra_len + key_len, body.bytesize - (extra_len + key_len))
53
- value = unpack ? @value_marshaller.retrieve(value, bitflags) : value
49
+ def unpack_response_body(resp_header, body, parse_as_stored_value)
50
+ extra_len = resp_header.extra_len
51
+ key_len = resp_header.key_len
52
+ bitflags = extra_len.positive? ? body.unpack1('N') : 0x0
53
+ key = body.byteslice(extra_len, key_len).force_encoding('UTF-8') if key_len.positive?
54
+ value = body.byteslice((extra_len + key_len)..-1)
55
+ value = parse_as_stored_value ? @value_marshaller.retrieve(value, bitflags) : value
54
56
  [key, value]
55
57
  end
56
58
 
@@ -64,7 +66,7 @@ module Dalli
64
66
  raise Dalli::DalliError, "Response error #{resp_header.status}: #{RESPONSE_CODES[resp_header.status]}"
65
67
  end
66
68
 
67
- def generic_response(unpack: false, cache_nils: false)
69
+ def get(cache_nils: false)
68
70
  resp_header, body = read_response
69
71
 
70
72
  return false if resp_header.not_stored? # Not stored, normal status for add operation
@@ -73,7 +75,7 @@ module Dalli
73
75
  raise_on_not_ok!(resp_header)
74
76
  return true unless body
75
77
 
76
- unpack_response_body(resp_header.extra_len, resp_header.key_len, body, unpack).last
78
+ unpack_response_body(resp_header, body, true).last
77
79
  end
78
80
 
79
81
  ##
@@ -83,21 +85,22 @@ module Dalli
83
85
  ##
84
86
  def storage_response
85
87
  resp_header, = read_response
88
+ return nil if resp_header.not_found?
86
89
  return false if resp_header.not_stored? # Not stored, normal status for add operation
87
90
 
88
91
  raise_on_not_ok!(resp_header)
89
92
  resp_header.cas
90
93
  end
91
94
 
92
- def no_body_response
95
+ def delete
93
96
  resp_header, = read_response
94
- return false if resp_header.not_stored? # Not stored, possible status for append/prepend
97
+ return false if resp_header.not_found? || resp_header.not_stored?
95
98
 
96
99
  raise_on_not_ok!(resp_header)
97
100
  true
98
101
  end
99
102
 
100
- def data_cas_response(unpack: true)
103
+ def data_cas_response
101
104
  resp_header, body = read_response
102
105
  return [nil, resp_header.cas] if resp_header.not_found?
103
106
  return [nil, false] if resp_header.not_stored?
@@ -105,14 +108,16 @@ module Dalli
105
108
  raise_on_not_ok!(resp_header)
106
109
  return [nil, resp_header.cas] unless body
107
110
 
108
- [unpack_response_body(resp_header.extra_len, resp_header.key_len, body, unpack).last, resp_header.cas]
111
+ [unpack_response_body(resp_header, body, true).last, resp_header.cas]
109
112
  end
110
113
 
111
- def cas_response
112
- data_cas_response.last
114
+ # Returns the new value for the key, if found and updated
115
+ def decr_incr
116
+ body = generic_response
117
+ body ? body.unpack1('Q>') : body
113
118
  end
114
119
 
115
- def multi_with_keys_response
120
+ def stats
116
121
  hash = {}
117
122
  loop do
118
123
  resp_header, body = read_response
@@ -125,14 +130,49 @@ module Dalli
125
130
  # block to clear any error responses from inside the multi.
126
131
  next unless resp_header.ok?
127
132
 
128
- key, value = unpack_response_body(resp_header.extra_len, resp_header.key_len, body, true)
133
+ key, value = unpack_response_body(resp_header, body, true)
129
134
  hash[key] = value
130
135
  end
131
136
  end
132
137
 
133
- def decr_incr_response
134
- body = generic_response
135
- body ? body.unpack1('Q>') : body
138
+ def flush
139
+ no_body_response
140
+ end
141
+
142
+ def reset
143
+ generic_response
144
+ end
145
+
146
+ def version
147
+ generic_response
148
+ end
149
+
150
+ def consume_all_responses_until_noop
151
+ loop do
152
+ resp_header, = read_response
153
+ # This is the response to the terminating noop / end of stat
154
+ return true if resp_header.ok? && resp_header.key_len.zero?
155
+ end
156
+ end
157
+
158
+ def generic_response
159
+ resp_header, body = read_response
160
+
161
+ return false if resp_header.not_stored? # Not stored, normal status for add operation
162
+ return nil if resp_header.not_found?
163
+
164
+ raise_on_not_ok!(resp_header)
165
+ return true unless body
166
+
167
+ unpack_response_body(resp_header, body, false).last
168
+ end
169
+
170
+ def no_body_response
171
+ resp_header, = read_response
172
+ return false if resp_header.not_stored? # Not stored, possible status for append/prepend/delete
173
+
174
+ raise_on_not_ok!(resp_header)
175
+ true
136
176
  end
137
177
 
138
178
  def validate_auth_format(extra_len, count)
@@ -156,8 +196,7 @@ module Dalli
156
196
  end
157
197
 
158
198
  def response_header_from_buffer(buf)
159
- header = buf.slice(0, ResponseHeader::SIZE)
160
- ResponseHeader.new(header)
199
+ ResponseHeader.new(buf)
161
200
  end
162
201
 
163
202
  ##
@@ -172,7 +211,7 @@ module Dalli
172
211
  ##
173
212
  def getk_response_from_buffer(buf)
174
213
  # There's no header in the buffer, so don't advance
175
- return [0, nil, nil, nil] unless contains_header?(buf)
214
+ return [0, nil, nil, nil, nil] unless contains_header?(buf)
176
215
 
177
216
  resp_header = response_header_from_buffer(buf)
178
217
  body_len = resp_header.body_len
@@ -181,18 +220,18 @@ module Dalli
181
220
  # This is either the response to the terminating
182
221
  # noop or, if the status is not zero, an intermediate
183
222
  # error response that needs to be discarded.
184
- return [ResponseHeader::SIZE, resp_header, nil, nil] if body_len.zero?
223
+ return [ResponseHeader::SIZE, resp_header.ok?, resp_header.cas, nil, nil] if body_len.zero?
185
224
 
186
225
  resp_size = ResponseHeader::SIZE + body_len
187
226
  # The header is in the buffer, but the body is not. As we don't have
188
227
  # a complete response, don't advance the buffer
189
- return [0, nil, nil, nil] unless buf.bytesize >= resp_size
228
+ return [0, nil, nil, nil, nil] unless buf.bytesize >= resp_size
190
229
 
191
230
  # The full response is in our buffer, so parse it and return
192
231
  # the values
193
- body = buf.slice(ResponseHeader::SIZE, body_len)
194
- key, value = unpack_response_body(resp_header.extra_len, resp_header.key_len, body, true)
195
- [resp_size, resp_header, key, value]
232
+ body = buf.byteslice(ResponseHeader::SIZE, body_len)
233
+ key, value = unpack_response_body(resp_header, body, true)
234
+ [resp_size, resp_header.ok?, resp_header.cas, key, value]
196
235
  end
197
236
  end
198
237
  end
@@ -10,7 +10,7 @@ module Dalli
10
10
  def perform_auth_negotiation
11
11
  write(RequestFormatter.standard_request(opkey: :auth_negotiation))
12
12
 
13
- status, content = @response_processor.auth_response
13
+ status, content = response_processor.auth_response
14
14
  return [status, []] if content.nil?
15
15
 
16
16
  # Substitute spaces for the \x00 returned by
@@ -4,11 +4,6 @@ require 'forwardable'
4
4
  require 'socket'
5
5
  require 'timeout'
6
6
 
7
- require_relative 'binary/request_formatter'
8
- require_relative 'binary/response_header'
9
- require_relative 'binary/response_processor'
10
- require_relative 'binary/sasl_authentication'
11
-
12
7
  module Dalli
13
8
  module Protocol
14
9
  ##
@@ -16,170 +11,13 @@ module Dalli
16
11
  # protocol. Contains logic for managing connection state to the server (retries, etc),
17
12
  # formatting requests to the server, and unpacking responses.
18
13
  ##
19
- class Binary
20
- extend Forwardable
21
-
22
- attr_accessor :weight, :options
23
-
24
- def_delegators :@value_marshaller, :serializer, :compressor, :compression_min_size, :compress_by_default?
25
- def_delegators :@connection_manager, :name, :sock, :hostname, :port, :close, :connected?, :socket_timeout,
26
- :socket_type, :up!, :down!, :write, :reconnect_down_server?, :raise_down_error
27
-
28
- def initialize(attribs, client_options = {})
29
- hostname, port, socket_type, @weight, user_creds = ServerConfigParser.parse(attribs)
30
- @options = client_options.merge(user_creds)
31
- @value_marshaller = ValueMarshaller.new(@options)
32
- @connection_manager = ConnectionManager.new(hostname, port, socket_type, @options)
33
- @response_processor = ResponseProcessor.new(@connection_manager, @value_marshaller)
34
- end
35
-
36
- # Chokepoint method for error handling and ensuring liveness
37
- def request(opkey, *args)
38
- verify_state(opkey)
39
-
40
- begin
41
- send(opkey, *args)
42
- rescue Dalli::MarshalError => e
43
- log_marshal_err(args.first, e)
44
- raise
45
- rescue Dalli::DalliError
46
- raise
47
- rescue StandardError => e
48
- log_unexpected_err(e)
49
- down!
50
- end
51
- end
52
-
53
- ##
54
- # Boolean method used by clients of this class to determine if this
55
- # particular memcached instance is available for use.
56
- def alive?
57
- ensure_connected!
58
- rescue Dalli::NetworkError
59
- # ensure_connected! raises a NetworkError if connection fails. We
60
- # want to capture that error and convert it to a boolean value here.
61
- false
62
- end
63
-
64
- def lock!; end
65
-
66
- def unlock!; end
67
-
68
- # Start reading key/value pairs from this connection. This is usually called
69
- # after a series of GETKQ commands. A NOOP is sent, and the server begins
70
- # flushing responses for kv pairs that were found.
71
- #
72
- # Returns nothing.
73
- def pipeline_response_setup
74
- verify_state(:getkq)
75
- write_noop
76
- response_buffer.reset
77
- @connection_manager.start_request!
78
- end
79
-
80
- # Attempt to receive and parse as many key/value pairs as possible
81
- # from this server. After #pipeline_response_setup, this should be invoked
82
- # repeatedly whenever this server's socket is readable until
83
- # #pipeline_complete?.
84
- #
85
- # Returns a Hash of kv pairs received.
86
- def pipeline_next_responses
87
- reconnect_on_pipeline_complete!
88
- values = {}
89
-
90
- response_buffer.read
91
-
92
- resp_header, key, value = pipeline_response
93
- # resp_header is not nil only if we have a full response to parse
94
- # in the buffer
95
- while resp_header
96
- # If the status is ok and key is nil, then this is the response
97
- # to the noop at the end of the pipeline
98
- finish_pipeline && break if resp_header.ok? && key.nil?
99
-
100
- # If the status is ok and the key is not nil, then this is a
101
- # getkq response with a value that we want to set in the response hash
102
- values[key] = [value, resp_header.cas] unless key.nil?
103
-
104
- # Get the next response from the buffer
105
- resp_header, key, value = pipeline_response
106
- end
107
-
108
- values
109
- rescue SystemCallError, Timeout::Error, EOFError => e
110
- @connection_manager.error_on_request!(e)
111
- end
112
-
113
- # Abort current pipelined get. Generally used to signal an external
114
- # timeout during pipelined get. The underlying socket is
115
- # disconnected, and the exception is swallowed.
116
- #
117
- # Returns nothing.
118
- def pipeline_abort
119
- response_buffer.clear
120
- @connection_manager.abort_request!
121
- return true unless connected?
122
-
123
- # Closes the connection, which ensures that our connection
124
- # is in a clean state for future requests
125
- @connection_manager.error_on_request!('External timeout')
126
- rescue NetworkError
127
- true
128
- end
129
-
130
- # Did the last call to #pipeline_response_setup complete successfully?
131
- def pipeline_complete?
132
- !response_buffer.in_progress?
133
- end
134
-
135
- def username
136
- @options[:username] || ENV['MEMCACHE_USERNAME']
137
- end
138
-
139
- def password
140
- @options[:password] || ENV['MEMCACHE_PASSWORD']
141
- end
142
-
143
- def require_auth?
144
- !username.nil?
14
+ class Binary < Base
15
+ def response_processor
16
+ @response_processor ||= ResponseProcessor.new(@connection_manager, @value_marshaller)
145
17
  end
146
18
 
147
- # NOTE: Additional public methods should be overridden in Dalli::Threadsafe
148
-
149
19
  private
150
20
 
151
- ##
152
- # Checks to see if we can execute the specified operation. Checks
153
- # whether the connection is in use, and whether the command is allowed
154
- ##
155
- def verify_state(opkey)
156
- @connection_manager.confirm_ready!
157
- verify_allowed_quiet!(opkey) if quiet?
158
-
159
- # The ensure_connected call has the side effect of connecting the
160
- # underlying socket if it is not connected, or there's been a disconnect
161
- # because of timeout or other error. Method raises an error
162
- # if it can't connect
163
- raise_down_error unless ensure_connected!
164
- end
165
-
166
- # The socket connection to the underlying server is initialized as a side
167
- # effect of this call. In fact, this is the ONLY place where that
168
- # socket connection is initialized.
169
- #
170
- # Both this method and connect need to be in this class so we can do auth
171
- # as required
172
- #
173
- # Since this is invoked exclusively in verify_state!, we don't need to worry about
174
- # thread safety. Using it elsewhere may require revisiting that assumption.
175
- def ensure_connected!
176
- return true if connected?
177
- return false unless reconnect_down_server?
178
-
179
- connect # This call needs to be in this class so we can do auth
180
- connected?
181
- end
182
-
183
21
  ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
184
22
  def verify_allowed_quiet!(opkey)
185
23
  return if ALLOWED_QUIET_OPS.include?(opkey)
@@ -187,34 +25,28 @@ module Dalli
187
25
  raise Dalli::NotPermittedMultiOpError, "The operation #{opkey} is not allowed in a quiet block."
188
26
  end
189
27
 
190
- def quiet?
191
- Thread.current[::Dalli::QUIET]
192
- end
193
-
194
- def cache_nils?(opts)
195
- return false unless opts.is_a?(Hash)
196
-
197
- opts[:cache_nils] ? true : false
198
- end
199
-
200
28
  # Retrieval Commands
201
29
  def get(key, options = nil)
202
30
  req = RequestFormatter.standard_request(opkey: :get, key: key)
203
31
  write(req)
204
- @response_processor.generic_response(unpack: true, cache_nils: cache_nils?(options))
32
+ response_processor.get(cache_nils: cache_nils?(options))
33
+ end
34
+
35
+ def quiet_get_request(key)
36
+ RequestFormatter.standard_request(opkey: :getkq, key: key)
205
37
  end
206
38
 
207
39
  def gat(key, ttl, options = nil)
208
40
  ttl = TtlSanitizer.sanitize(ttl)
209
41
  req = RequestFormatter.standard_request(opkey: :gat, key: key, ttl: ttl)
210
42
  write(req)
211
- @response_processor.generic_response(unpack: true, cache_nils: cache_nils?(options))
43
+ response_processor.get(cache_nils: cache_nils?(options))
212
44
  end
213
45
 
214
46
  def touch(key, ttl)
215
47
  ttl = TtlSanitizer.sanitize(ttl)
216
48
  write(RequestFormatter.standard_request(opkey: :touch, key: key, ttl: ttl))
217
- @response_processor.generic_response
49
+ response_processor.generic_response
218
50
  end
219
51
 
220
52
  # TODO: This is confusing, as there's a cas command in memcached
@@ -222,7 +54,7 @@ module Dalli
222
54
  def cas(key)
223
55
  req = RequestFormatter.standard_request(opkey: :get, key: key)
224
56
  write(req)
225
- @response_processor.data_cas_response
57
+ response_processor.data_cas_response
226
58
  end
227
59
 
228
60
  # Storage Commands
@@ -250,7 +82,7 @@ module Dalli
250
82
  value: value, bitflags: bitflags,
251
83
  ttl: ttl, cas: cas)
252
84
  write(req)
253
- @response_processor.storage_response unless quiet?
85
+ response_processor.storage_response unless quiet?
254
86
  end
255
87
  # rubocop:enable Metrics/ParameterLists
256
88
 
@@ -266,7 +98,7 @@ module Dalli
266
98
 
267
99
  def write_append_prepend(opkey, key, value)
268
100
  write(RequestFormatter.standard_request(opkey: opkey, key: key, value: value))
269
- @response_processor.no_body_response unless quiet?
101
+ response_processor.no_body_response unless quiet?
270
102
  end
271
103
 
272
104
  # Delete Commands
@@ -274,7 +106,7 @@ module Dalli
274
106
  opkey = quiet? ? :deleteq : :delete
275
107
  req = RequestFormatter.standard_request(opkey: opkey, key: key, cas: cas)
276
108
  write(req)
277
- @response_processor.no_body_response unless quiet?
109
+ response_processor.delete unless quiet?
278
110
  end
279
111
 
280
112
  # Arithmetic Commands
@@ -300,37 +132,37 @@ module Dalli
300
132
  initial ||= 0
301
133
  write(RequestFormatter.decr_incr_request(opkey: opkey, key: key,
302
134
  count: count, initial: initial, expiry: expiry))
303
- @response_processor.decr_incr_response unless quiet?
135
+ response_processor.decr_incr unless quiet?
304
136
  end
305
137
 
306
138
  # Other Commands
307
139
  def flush(ttl = 0)
308
140
  opkey = quiet? ? :flushq : :flush
309
141
  write(RequestFormatter.standard_request(opkey: opkey, ttl: ttl))
310
- @response_processor.no_body_response unless quiet?
142
+ response_processor.no_body_response unless quiet?
311
143
  end
312
144
 
313
145
  # Noop is a keepalive operation but also used to demarcate the end of a set of pipelined commands.
314
146
  # We need to read all the responses at once.
315
147
  def noop
316
148
  write_noop
317
- @response_processor.multi_with_keys_response
149
+ response_processor.consume_all_responses_until_noop
318
150
  end
319
151
 
320
152
  def stats(info = '')
321
153
  req = RequestFormatter.standard_request(opkey: :stat, key: info)
322
154
  write(req)
323
- @response_processor.multi_with_keys_response
155
+ response_processor.stats
324
156
  end
325
157
 
326
158
  def reset_stats
327
159
  write(RequestFormatter.standard_request(opkey: :stat, key: 'reset'))
328
- @response_processor.generic_response
160
+ response_processor.reset
329
161
  end
330
162
 
331
163
  def version
332
164
  write(RequestFormatter.standard_request(opkey: :version))
333
- @response_processor.generic_response
165
+ response_processor.version
334
166
  end
335
167
 
336
168
  def write_noop
@@ -338,55 +170,10 @@ module Dalli
338
170
  write(req)
339
171
  end
340
172
 
341
- def connect
342
- @connection_manager.establish_connection
343
- authenticate_connection if require_auth?
344
- @version = version # Connect socket if not authed
345
- up!
346
- rescue Dalli::DalliError
347
- raise
348
- end
349
-
350
- def pipelined_get(keys)
351
- req = +''
352
- keys.each do |key|
353
- req << RequestFormatter.standard_request(opkey: :getkq, key: key)
354
- end
355
- # Could send noop here instead of in pipeline_response_setup
356
- write(req)
357
- end
358
-
359
- def response_buffer
360
- @response_buffer ||= ResponseBuffer.new(@connection_manager, @response_processor)
361
- end
362
-
363
- def pipeline_response
364
- response_buffer.process_single_getk_response
365
- end
366
-
367
- # Called after the noop response is received at the end of a set
368
- # of pipelined gets
369
- def finish_pipeline
370
- response_buffer.clear
371
- @connection_manager.finish_request!
372
-
373
- true # to simplify response
374
- end
375
-
376
- def reconnect_on_pipeline_complete!
377
- @connection_manager.reconnect! 'pipelined get has completed' if pipeline_complete?
378
- end
379
-
380
- def log_marshal_err(key, err)
381
- Dalli.logger.error "Marshalling error for key '#{key}': #{err.message}"
382
- Dalli.logger.error 'You are trying to cache a Ruby object which cannot be serialized to memcached.'
383
- end
384
-
385
- def log_unexpected_err(err)
386
- Dalli.logger.error "Unexpected exception during Dalli request: #{err.class.name}: #{err.message}"
387
- Dalli.logger.error err.backtrace.join("\n\t")
388
- end
389
-
173
+ require_relative 'binary/request_formatter'
174
+ require_relative 'binary/response_header'
175
+ require_relative 'binary/response_processor'
176
+ require_relative 'binary/sasl_authentication'
390
177
  include SaslAuthentication
391
178
  end
392
179
  end
@@ -21,9 +21,9 @@ module Dalli
21
21
  # Attempts to process a single response from the buffer. Starts
22
22
  # by advancing the buffer to the specified start position
23
23
  def process_single_getk_response
24
- bytes, resp_header, key, value = @response_processor.getk_response_from_buffer(@buffer)
24
+ bytes, status, cas, key, value = @response_processor.getk_response_from_buffer(@buffer)
25
25
  advance(bytes)
26
- [resp_header, key, value]
26
+ [status, cas, key, value]
27
27
  end
28
28
 
29
29
  # Advances the internal response buffer by bytes_to_advance
@@ -31,13 +31,13 @@ module Dalli
31
31
  def advance(bytes_to_advance)
32
32
  return unless bytes_to_advance.positive?
33
33
 
34
- @buffer = @buffer[bytes_to_advance..-1]
34
+ @buffer = @buffer.byteslice(bytes_to_advance..-1)
35
35
  end
36
36
 
37
37
  # Resets the internal buffer to an empty state,
38
38
  # so that we're ready to read pipelined responses
39
39
  def reset
40
- @buffer = +''
40
+ @buffer = ''.b
41
41
  end
42
42
 
43
43
  # Clear the internal response buffer
data/lib/dalli/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dalli
4
- VERSION = '3.1.1'
4
+ VERSION = '3.1.5'
5
5
 
6
6
  MIN_SUPPORTED_MEMCACHED_VERSION = '1.4'
7
7
  end
data/lib/dalli.rb CHANGED
@@ -62,6 +62,7 @@ require_relative 'dalli/key_manager'
62
62
  require_relative 'dalli/pipelined_getter'
63
63
  require_relative 'dalli/ring'
64
64
  require_relative 'dalli/protocol'
65
+ require_relative 'dalli/protocol/base'
65
66
  require_relative 'dalli/protocol/binary'
66
67
  require_relative 'dalli/protocol/connection_manager'
67
68
  require_relative 'dalli/protocol/response_buffer'
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: 3.1.1
4
+ version: 3.1.5
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: 2021-12-10 00:00:00.000000000 Z
12
+ date: 2021-12-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: connection_pool
@@ -121,6 +121,7 @@ files:
121
121
  - lib/dalli/options.rb
122
122
  - lib/dalli/pipelined_getter.rb
123
123
  - lib/dalli/protocol.rb
124
+ - lib/dalli/protocol/base.rb
124
125
  - lib/dalli/protocol/binary.rb
125
126
  - lib/dalli/protocol/binary/request_formatter.rb
126
127
  - lib/dalli/protocol/binary/response_header.rb