dalli 3.1.2 → 3.1.6

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: 74310302432b6cdf905f2e581145a10a3dc1c08f97b47e3a8e1b91a113920d4d
4
- data.tar.gz: b62f80ba194b9feba95ac7db0fac632381bdf5b2d0ecd24ec3b7a748c79e9d26
3
+ metadata.gz: 5a922a58b5cbaaf248ee0d3078b4bb6acd5827e7b2bf123aeabcda3a9f6ae4ba
4
+ data.tar.gz: a1ce97c6d6f4421deb4b46b0a3221f99c7939cec036ccaba07972be39aa7cf4c
5
5
  SHA512:
6
- metadata.gz: ccf0a1722111940e132879abc0e4bb1899f6e337b8ef0ded051bd50eac76bbd9f79a50b22584b8893ed97ff99df0687edb5734ab958b56ed437adbed15176389
7
- data.tar.gz: ba43861323882292b98e8dc12ea96cd42ca76ee146df523726e23dc1bca27fbc8e659d1a67bfbfcf529597626562d1731e7c5ee2dd56497e905cc594f6ed9569
6
+ metadata.gz: 5f7d9bb0dc9a911fe698a86f99a0a0c2786a68b35fc685a1aeb5a769ba53990681a77b1b99c95280a3250a8d226e8c62f79889e101cddd9c94d1c08464eadd0d
7
+ data.tar.gz: 9d078a4628973989cc53254e2a4398c8697d07d36682c90f3abce88b26f0c579fb6d46da58553c95cd59e190b663dad26cb65f6cad358681bb37653912e5b61f
data/History.md CHANGED
@@ -4,6 +4,31 @@ Dalli Changelog
4
4
  Unreleased
5
5
  ==========
6
6
 
7
+ 3.1.6
8
+ ==========
9
+
10
+ - Fix bug with cas/cas! with "Not found" value (petergoldstein)
11
+ - Add Ruby 3.1 to CI (petergoldstein)
12
+ - Replace reject(&:nil?) with compact (petergoldstein)
13
+
14
+ 3.1.5
15
+ ==========
16
+
17
+ - Fix bug with get_cas key with "Not found" value (petergoldstein)
18
+ - Replace should return nil, not raise error, on miss (petergoldstein)
19
+
20
+ 3.1.4
21
+ ==========
22
+
23
+ - Improve response parsing performance (casperisfine)
24
+ - Reorganize binary protocol parsing a bit (petergoldstein)
25
+ - Fix handling of non-ASCII keys in get_multi (petergoldstein)
26
+
27
+ 3.1.3
28
+ ==========
29
+
30
+ - Restore falsey behavior on delete/delete_cas for nonexistent key (petergoldstein)
31
+
7
32
  3.1.2
8
33
  ==========
9
34
 
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
@@ -377,7 +375,6 @@ module Dalli
377
375
 
378
376
  def cas_core(key, always_set, ttl = nil, req_options = nil)
379
377
  (value, cas) = perform(:cas, key)
380
- value = nil if !value || value == 'Not found'
381
378
  return if value.nil? && !always_set
382
379
 
383
380
  newvalue = yield(value)
@@ -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
@@ -94,7 +94,7 @@ module Dalli
94
94
  key_len = key.nil? ? 0 : key.bytesize
95
95
  value_len = value.nil? ? 0 : value.bytesize
96
96
  header = [REQUEST, OPCODES[opkey], key_len, extra_len, 0, 0, extra_len + key_len + value_len, opaque, cas]
97
- body = [bitflags, ttl, key, value].reject(&:nil?)
97
+ body = [bitflags, ttl, key, value].compact
98
98
  (header + body).pack(FORMAT[opkey])
99
99
  end
100
100
  # rubocop:enable Metrics/ParameterLists
@@ -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,175 +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
- def quiet?
148
- Thread.current[::Dalli::QUIET]
149
- end
150
- alias multi? quiet?
151
-
152
- # NOTE: Additional public methods should be overridden in Dalli::Threadsafe
153
-
154
19
  private
155
20
 
156
- ##
157
- # Checks to see if we can execute the specified operation. Checks
158
- # whether the connection is in use, and whether the command is allowed
159
- ##
160
- def verify_state(opkey)
161
- @connection_manager.confirm_ready!
162
- verify_allowed_quiet!(opkey) if quiet?
163
-
164
- # The ensure_connected call has the side effect of connecting the
165
- # underlying socket if it is not connected, or there's been a disconnect
166
- # because of timeout or other error. Method raises an error
167
- # if it can't connect
168
- raise_down_error unless ensure_connected!
169
- end
170
-
171
- # The socket connection to the underlying server is initialized as a side
172
- # effect of this call. In fact, this is the ONLY place where that
173
- # socket connection is initialized.
174
- #
175
- # Both this method and connect need to be in this class so we can do auth
176
- # as required
177
- #
178
- # Since this is invoked exclusively in verify_state!, we don't need to worry about
179
- # thread safety. Using it elsewhere may require revisiting that assumption.
180
- def ensure_connected!
181
- return true if connected?
182
- return false unless reconnect_down_server?
183
-
184
- connect # This call needs to be in this class so we can do auth
185
- connected?
186
- end
187
-
188
21
  ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
189
22
  def verify_allowed_quiet!(opkey)
190
23
  return if ALLOWED_QUIET_OPS.include?(opkey)
@@ -192,30 +25,28 @@ module Dalli
192
25
  raise Dalli::NotPermittedMultiOpError, "The operation #{opkey} is not allowed in a quiet block."
193
26
  end
194
27
 
195
- def cache_nils?(opts)
196
- return false unless opts.is_a?(Hash)
197
-
198
- opts[:cache_nils] ? true : false
199
- end
200
-
201
28
  # Retrieval Commands
202
29
  def get(key, options = nil)
203
30
  req = RequestFormatter.standard_request(opkey: :get, key: key)
204
31
  write(req)
205
- @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)
206
37
  end
207
38
 
208
39
  def gat(key, ttl, options = nil)
209
40
  ttl = TtlSanitizer.sanitize(ttl)
210
41
  req = RequestFormatter.standard_request(opkey: :gat, key: key, ttl: ttl)
211
42
  write(req)
212
- @response_processor.generic_response(unpack: true, cache_nils: cache_nils?(options))
43
+ response_processor.get(cache_nils: cache_nils?(options))
213
44
  end
214
45
 
215
46
  def touch(key, ttl)
216
47
  ttl = TtlSanitizer.sanitize(ttl)
217
48
  write(RequestFormatter.standard_request(opkey: :touch, key: key, ttl: ttl))
218
- @response_processor.generic_response
49
+ response_processor.generic_response
219
50
  end
220
51
 
221
52
  # TODO: This is confusing, as there's a cas command in memcached
@@ -223,7 +54,7 @@ module Dalli
223
54
  def cas(key)
224
55
  req = RequestFormatter.standard_request(opkey: :get, key: key)
225
56
  write(req)
226
- @response_processor.data_cas_response
57
+ response_processor.data_cas_response
227
58
  end
228
59
 
229
60
  # Storage Commands
@@ -251,7 +82,7 @@ module Dalli
251
82
  value: value, bitflags: bitflags,
252
83
  ttl: ttl, cas: cas)
253
84
  write(req)
254
- @response_processor.storage_response unless quiet?
85
+ response_processor.storage_response unless quiet?
255
86
  end
256
87
  # rubocop:enable Metrics/ParameterLists
257
88
 
@@ -267,7 +98,7 @@ module Dalli
267
98
 
268
99
  def write_append_prepend(opkey, key, value)
269
100
  write(RequestFormatter.standard_request(opkey: opkey, key: key, value: value))
270
- @response_processor.no_body_response unless quiet?
101
+ response_processor.no_body_response unless quiet?
271
102
  end
272
103
 
273
104
  # Delete Commands
@@ -275,7 +106,7 @@ module Dalli
275
106
  opkey = quiet? ? :deleteq : :delete
276
107
  req = RequestFormatter.standard_request(opkey: opkey, key: key, cas: cas)
277
108
  write(req)
278
- @response_processor.no_body_response unless quiet?
109
+ response_processor.delete unless quiet?
279
110
  end
280
111
 
281
112
  # Arithmetic Commands
@@ -301,37 +132,37 @@ module Dalli
301
132
  initial ||= 0
302
133
  write(RequestFormatter.decr_incr_request(opkey: opkey, key: key,
303
134
  count: count, initial: initial, expiry: expiry))
304
- @response_processor.decr_incr_response unless quiet?
135
+ response_processor.decr_incr unless quiet?
305
136
  end
306
137
 
307
138
  # Other Commands
308
139
  def flush(ttl = 0)
309
140
  opkey = quiet? ? :flushq : :flush
310
141
  write(RequestFormatter.standard_request(opkey: opkey, ttl: ttl))
311
- @response_processor.no_body_response unless quiet?
142
+ response_processor.no_body_response unless quiet?
312
143
  end
313
144
 
314
145
  # Noop is a keepalive operation but also used to demarcate the end of a set of pipelined commands.
315
146
  # We need to read all the responses at once.
316
147
  def noop
317
148
  write_noop
318
- @response_processor.multi_with_keys_response
149
+ response_processor.consume_all_responses_until_noop
319
150
  end
320
151
 
321
152
  def stats(info = '')
322
153
  req = RequestFormatter.standard_request(opkey: :stat, key: info)
323
154
  write(req)
324
- @response_processor.multi_with_keys_response
155
+ response_processor.stats
325
156
  end
326
157
 
327
158
  def reset_stats
328
159
  write(RequestFormatter.standard_request(opkey: :stat, key: 'reset'))
329
- @response_processor.generic_response
160
+ response_processor.reset
330
161
  end
331
162
 
332
163
  def version
333
164
  write(RequestFormatter.standard_request(opkey: :version))
334
- @response_processor.generic_response
165
+ response_processor.version
335
166
  end
336
167
 
337
168
  def write_noop
@@ -339,55 +170,10 @@ module Dalli
339
170
  write(req)
340
171
  end
341
172
 
342
- def connect
343
- @connection_manager.establish_connection
344
- authenticate_connection if require_auth?
345
- @version = version # Connect socket if not authed
346
- up!
347
- rescue Dalli::DalliError
348
- raise
349
- end
350
-
351
- def pipelined_get(keys)
352
- req = +''
353
- keys.each do |key|
354
- req << RequestFormatter.standard_request(opkey: :getkq, key: key)
355
- end
356
- # Could send noop here instead of in pipeline_response_setup
357
- write(req)
358
- end
359
-
360
- def response_buffer
361
- @response_buffer ||= ResponseBuffer.new(@connection_manager, @response_processor)
362
- end
363
-
364
- def pipeline_response
365
- response_buffer.process_single_getk_response
366
- end
367
-
368
- # Called after the noop response is received at the end of a set
369
- # of pipelined gets
370
- def finish_pipeline
371
- response_buffer.clear
372
- @connection_manager.finish_request!
373
-
374
- true # to simplify response
375
- end
376
-
377
- def reconnect_on_pipeline_complete!
378
- @connection_manager.reconnect! 'pipelined get has completed' if pipeline_complete?
379
- end
380
-
381
- def log_marshal_err(key, err)
382
- Dalli.logger.error "Marshalling error for key '#{key}': #{err.message}"
383
- Dalli.logger.error 'You are trying to cache a Ruby object which cannot be serialized to memcached.'
384
- end
385
-
386
- def log_unexpected_err(err)
387
- Dalli.logger.error "Unexpected exception during Dalli request: #{err.class.name}: #{err.message}"
388
- Dalli.logger.error err.backtrace.join("\n\t")
389
- end
390
-
173
+ require_relative 'binary/request_formatter'
174
+ require_relative 'binary/response_header'
175
+ require_relative 'binary/response_processor'
176
+ require_relative 'binary/sasl_authentication'
391
177
  include SaslAuthentication
392
178
  end
393
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.2'
4
+ VERSION = '3.1.6'
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.2
4
+ version: 3.1.6
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-13 00:00:00.000000000 Z
12
+ date: 2022-01-01 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
@@ -159,7 +160,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
159
160
  - !ruby/object:Gem::Version
160
161
  version: '0'
161
162
  requirements: []
162
- rubygems_version: 3.2.31
163
+ rubygems_version: 3.3.4
163
164
  signing_key:
164
165
  specification_version: 4
165
166
  summary: High performance memcached client for Ruby