dalli 3.0.5 → 3.1.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.

@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'English'
4
3
  require 'forwardable'
5
4
  require 'socket'
6
5
  require 'timeout'
7
6
 
8
7
  require_relative 'binary/request_formatter'
8
+ require_relative 'binary/response_header'
9
9
  require_relative 'binary/response_processor'
10
10
  require_relative 'binary/sasl_authentication'
11
11
 
@@ -19,59 +19,30 @@ module Dalli
19
19
  class Binary
20
20
  extend Forwardable
21
21
 
22
- attr_accessor :hostname, :port, :weight, :options
23
- attr_reader :sock, :socket_type
22
+ attr_accessor :weight, :options
24
23
 
25
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
26
27
 
27
- DEFAULTS = {
28
- # seconds between trying to contact a remote server
29
- down_retry_delay: 30,
30
- # connect/read/write timeout for socket operations
31
- socket_timeout: 1,
32
- # times a socket operation may fail before considering the server dead
33
- socket_max_failures: 2,
34
- # amount of time to sleep between retries when a failure occurs
35
- socket_failure_delay: 0.1,
36
- username: nil,
37
- password: nil
38
- }.freeze
39
-
40
- def initialize(attribs, options = {})
41
- @hostname, @port, @weight, @socket_type, options = ServerConfigParser.parse(attribs, options)
42
- @options = DEFAULTS.merge(options)
28
+ def initialize(attribs, client_options = {})
29
+ hostname, port, socket_type, @weight, user_creds = ServerConfigParser.parse(attribs)
30
+ @options = client_options.merge(user_creds)
43
31
  @value_marshaller = ValueMarshaller.new(@options)
44
- @response_processor = ResponseProcessor.new(self, @value_marshaller)
45
-
46
- reset_down_info
47
- @sock = nil
48
- @pid = nil
49
- @request_in_progress = false
50
- end
51
-
52
- def name
53
- if socket_type == :unix
54
- hostname
55
- else
56
- "#{hostname}:#{port}"
57
- end
32
+ @connection_manager = ConnectionManager.new(hostname, port, socket_type, @options)
33
+ @response_processor = ResponseProcessor.new(@connection_manager, @value_marshaller)
58
34
  end
59
35
 
60
36
  # Chokepoint method for error handling and ensuring liveness
61
- def request(opcode, *args)
62
- verify_state
63
- # The alive? call has the side effect of connecting the underlying
64
- # socket if it is not connected, or there's been a disconnect
65
- # because of timeout or other error. Method raises an error
66
- # if it can't connect
67
- raise_memcached_down_err unless alive?
37
+ def request(opkey, *args)
38
+ verify_state(opkey)
68
39
 
69
40
  begin
70
- send(opcode, *args)
41
+ send(opkey, *args)
71
42
  rescue Dalli::MarshalError => e
72
- log_marshall_err(args.first, e)
43
+ log_marshal_err(args.first, e)
73
44
  raise
74
- rescue Dalli::DalliError, Dalli::NetworkError, Dalli::ValueOverMaxSize, Timeout::Error
45
+ rescue Dalli::DalliError
75
46
  raise
76
47
  rescue StandardError => e
77
48
  log_unexpected_err(e)
@@ -79,63 +50,17 @@ module Dalli
79
50
  end
80
51
  end
81
52
 
82
- def raise_memcached_down_err
83
- raise Dalli::NetworkError,
84
- "#{name} is down: #{@error} #{@msg}. If you are sure it is running, "\
85
- "ensure memcached version is > #{::Dalli::MIN_SUPPORTED_MEMCACHED_VERSION}."
86
- end
87
-
88
- def log_marshall_err(key, err)
89
- Dalli.logger.error "Marshalling error for key '#{key}': #{err.message}"
90
- Dalli.logger.error 'You are trying to cache a Ruby object which cannot be serialized to memcached.'
91
- end
92
-
93
- def log_unexpected_err(err)
94
- Dalli.logger.error "Unexpected exception during Dalli request: #{err.class.name}: #{err.message}"
95
- Dalli.logger.error err.backtrace.join("\n\t")
96
- end
97
-
98
- # The socket connection to the underlying server is initialized as a side
99
- # effect of this call. In fact, this is the ONLY place where that
100
- # socket connection is initialized.
53
+ ##
54
+ # Boolean method used by clients of this class to determine if this
55
+ # particular memcached instance is available for use.
101
56
  def alive?
102
- return true if @sock
103
- return false unless reconnect_down_server?
104
-
105
- connect
106
- !!@sock
57
+ ensure_connected!
107
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.
108
61
  false
109
62
  end
110
63
 
111
- def reconnect_down_server?
112
- return true unless @last_down_at
113
-
114
- time_to_next_reconnect = @last_down_at + options[:down_retry_delay] - Time.now
115
- return true unless time_to_next_reconnect.positive?
116
-
117
- Dalli.logger.debug do
118
- format('down_retry_delay not reached for %<name>s (%<time>.3f seconds left)', name: name,
119
- time: time_to_next_reconnect)
120
- end
121
- false
122
- end
123
-
124
- # Closes the underlying socket and cleans up
125
- # socket state.
126
- def close
127
- return unless @sock
128
-
129
- begin
130
- @sock.close
131
- rescue StandardError
132
- nil
133
- end
134
- @sock = nil
135
- @pid = nil
136
- abort_request!
137
- end
138
-
139
64
  def lock!; end
140
65
 
141
66
  def unlock!; end
@@ -145,213 +70,126 @@ module Dalli
145
70
  # flushing responses for kv pairs that were found.
146
71
  #
147
72
  # Returns nothing.
148
- def multi_response_start
149
- verify_state
73
+ def pipeline_response_setup
74
+ verify_state(:getkq)
150
75
  write_noop
151
- @multi_buffer = +''
152
- @position = 0
153
- start_request!
154
- end
155
-
156
- # Did the last call to #multi_response_start complete successfully?
157
- def multi_response_completed?
158
- @multi_buffer.nil?
76
+ response_buffer.reset
77
+ @connection_manager.start_request!
159
78
  end
160
79
 
161
80
  # Attempt to receive and parse as many key/value pairs as possible
162
- # from this server. After #multi_response_start, this should be invoked
81
+ # from this server. After #pipeline_response_setup, this should be invoked
163
82
  # repeatedly whenever this server's socket is readable until
164
- # #multi_response_completed?.
83
+ # #pipeline_complete?.
165
84
  #
166
85
  # Returns a Hash of kv pairs received.
167
- def multi_response_nonblock
168
- reconnect! 'multi_response has completed' if @multi_buffer.nil?
169
-
170
- @multi_buffer << @sock.read_available
171
- buf = @multi_buffer
172
- pos = @position
86
+ def pipeline_next_responses
87
+ reconnect_on_pipeline_complete!
173
88
  values = {}
174
89
 
175
- while buf.bytesize - pos >= ResponseProcessor::RESP_HEADER_SIZE
176
- header = buf.slice(pos, ResponseProcessor::RESP_HEADER_SIZE)
177
- _, extra_len, key_len, body_len, cas = @response_processor.unpack_header(header)
178
-
179
- # We've reached the noop at the end of the pipeline
180
- if key_len.zero?
181
- finish_multi_response
182
- break
183
- end
184
-
185
- # Break and read more unless we already have the entire response for this header
186
- resp_size = ResponseProcessor::RESP_HEADER_SIZE + body_len
187
- break unless buf.bytesize - pos >= resp_size
188
-
189
- body = buf.slice(pos + ResponseProcessor::RESP_HEADER_SIZE, body_len)
190
- begin
191
- key, value = @response_processor.unpack_response_body(extra_len, key_len, body, true)
192
- values[key] = [value, cas]
193
- rescue DalliError
194
- # TODO: Determine if we should be swallowing
195
- # this error
196
- end
197
-
198
- pos = pos + ResponseProcessor::RESP_HEADER_SIZE + body_len
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
199
106
  end
200
- # TODO: We should be discarding the already processed buffer at this point
201
- @position = pos
202
107
 
203
108
  values
204
109
  rescue SystemCallError, Timeout::Error, EOFError => e
205
- failure!(e)
206
- end
207
-
208
- def finish_multi_response
209
- @multi_buffer = nil
210
- @position = nil
211
- finish_request!
110
+ @connection_manager.error_on_request!(e)
212
111
  end
213
112
 
214
- # Abort an earlier #multi_response_start. Used to signal an external
215
- # timeout. The underlying socket is disconnected, and the exception is
216
- # swallowed.
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.
217
116
  #
218
117
  # Returns nothing.
219
- def multi_response_abort
220
- @multi_buffer = nil
221
- @position = nil
222
- abort_request!
223
- return true unless @sock
224
-
225
- failure!(RuntimeError.new('External timeout'))
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')
226
126
  rescue NetworkError
227
127
  true
228
128
  end
229
129
 
230
- def read(count)
231
- start_request!
232
- data = @sock.readfull(count)
233
- finish_request!
234
- data
235
- rescue SystemCallError, Timeout::Error, EOFError => e
236
- failure!(e)
130
+ # Did the last call to #pipeline_response_setup complete successfully?
131
+ def pipeline_complete?
132
+ !response_buffer.in_progress?
237
133
  end
238
134
 
239
- def write(bytes)
240
- start_request!
241
- result = @sock.write(bytes)
242
- finish_request!
243
- result
244
- rescue SystemCallError, Timeout::Error => e
245
- failure!(e)
246
- end
247
-
248
- def socket_timeout
249
- @socket_timeout ||= @options[:socket_timeout]
250
- end
251
-
252
- # NOTE: Additional public methods should be overridden in Dalli::Threadsafe
253
-
254
- private
255
-
256
- def request_in_progress?
257
- @request_in_progress
258
- end
259
-
260
- def start_request!
261
- @request_in_progress = true
262
- end
263
-
264
- def finish_request!
265
- @request_in_progress = false
266
- end
267
-
268
- def abort_request!
269
- @request_in_progress = false
270
- end
271
-
272
- def verify_state
273
- failure!(RuntimeError.new('Already writing to socket')) if request_in_progress?
274
- reconnect_on_fork if fork_detected?
135
+ def username
136
+ @options[:username] || ENV['MEMCACHE_USERNAME']
275
137
  end
276
138
 
277
- def fork_detected?
278
- @pid && @pid != Process.pid
139
+ def password
140
+ @options[:password] || ENV['MEMCACHE_PASSWORD']
279
141
  end
280
142
 
281
- def reconnect_on_fork
282
- message = 'Fork detected, re-connecting child process...'
283
- Dalli.logger.info { message }
284
- reconnect! message
143
+ def require_auth?
144
+ !username.nil?
285
145
  end
286
146
 
287
- # Marks the server instance as needing reconnect. Raises a
288
- # Dalli::NetworkError with the specified message. Calls close
289
- # to clean up socket state
290
- def reconnect!(message)
291
- close
292
- sleep(options[:socket_failure_delay]) if options[:socket_failure_delay]
293
- raise Dalli::NetworkError, message
147
+ def quiet?
148
+ Thread.current[::Dalli::QUIET]
294
149
  end
150
+ alias multi? quiet?
295
151
 
296
- # Raises Dalli::NetworkError
297
- def failure!(exception)
298
- message = "#{name} failed (count: #{@fail_count}) #{exception.class}: #{exception.message}"
299
- Dalli.logger.warn { message }
300
-
301
- @fail_count += 1
302
- if @fail_count >= options[:socket_max_failures]
303
- down!
304
- else
305
- reconnect! 'Socket operation failed, retrying...'
306
- end
307
- end
308
-
309
- # Marks the server instance as down. Updates the down_at state
310
- # and raises an Dalli::NetworkError that includes the underlying
311
- # error in the message. Calls close to clean up socket state
312
- def down!
313
- close
314
- log_down_detected
152
+ # NOTE: Additional public methods should be overridden in Dalli::Threadsafe
315
153
 
316
- @error = $ERROR_INFO&.class&.name
317
- @msg ||= $ERROR_INFO&.message
318
- raise Dalli::NetworkError, "#{name} is down: #{@error} #{@msg}"
319
- end
154
+ private
320
155
 
321
- def log_down_detected
322
- @last_down_at = Time.now
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?
323
163
 
324
- if @down_at
325
- time = Time.now - @down_at
326
- Dalli.logger.debug { format('%<name>s is still down (for %<time>.3f seconds now)', name: name, time: time) }
327
- else
328
- @down_at = @last_down_at
329
- Dalli.logger.warn("#{name} is down")
330
- end
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!
331
169
  end
332
170
 
333
- def log_up_detected
334
- return unless @down_at
335
-
336
- time = Time.now - @down_at
337
- Dalli.logger.warn { format('%<name>s is back (downtime was %<time>.3f seconds)', name: name, time: time) }
338
- end
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?
339
183
 
340
- def up!
341
- log_up_detected
342
- reset_down_info
184
+ connect # This call needs to be in this class so we can do auth
185
+ connected?
343
186
  end
344
187
 
345
- def reset_down_info
346
- @fail_count = 0
347
- @down_at = nil
348
- @last_down_at = nil
349
- @msg = nil
350
- @error = nil
351
- end
188
+ ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
189
+ def verify_allowed_quiet!(opkey)
190
+ return if ALLOWED_QUIET_OPS.include?(opkey)
352
191
 
353
- def multi?
354
- Thread.current[:dalli_multi]
192
+ raise Dalli::NotPermittedMultiOpError, "The operation #{opkey} is not allowed in a quiet block."
355
193
  end
356
194
 
357
195
  def cache_nils?(opts)
@@ -360,39 +198,52 @@ module Dalli
360
198
  opts[:cache_nils] ? true : false
361
199
  end
362
200
 
201
+ # Retrieval Commands
363
202
  def get(key, options = nil)
364
203
  req = RequestFormatter.standard_request(opkey: :get, key: key)
365
204
  write(req)
366
205
  @response_processor.generic_response(unpack: true, cache_nils: cache_nils?(options))
367
206
  end
368
207
 
369
- def send_multiget(keys)
370
- req = +''
371
- keys.each do |key|
372
- req << RequestFormatter.standard_request(opkey: :getkq, key: key)
373
- end
374
- # Could send noop here instead of in multi_response_start
208
+ def gat(key, ttl, options = nil)
209
+ ttl = TtlSanitizer.sanitize(ttl)
210
+ req = RequestFormatter.standard_request(opkey: :gat, key: key, ttl: ttl)
375
211
  write(req)
212
+ @response_processor.generic_response(unpack: true, cache_nils: cache_nils?(options))
213
+ end
214
+
215
+ def touch(key, ttl)
216
+ ttl = TtlSanitizer.sanitize(ttl)
217
+ write(RequestFormatter.standard_request(opkey: :touch, key: key, ttl: ttl))
218
+ @response_processor.generic_response
376
219
  end
377
220
 
221
+ # TODO: This is confusing, as there's a cas command in memcached
222
+ # and this isn't it. Maybe rename? Maybe eliminate?
223
+ def cas(key)
224
+ req = RequestFormatter.standard_request(opkey: :get, key: key)
225
+ write(req)
226
+ @response_processor.data_cas_response
227
+ end
228
+
229
+ # Storage Commands
378
230
  def set(key, value, ttl, cas, options)
379
- opkey = multi? ? :setq : :set
380
- process_value_req(opkey, key, value, ttl, cas, options)
231
+ opkey = quiet? ? :setq : :set
232
+ storage_req(opkey, key, value, ttl, cas, options)
381
233
  end
382
234
 
383
235
  def add(key, value, ttl, options)
384
- opkey = multi? ? :addq : :add
385
- cas = 0
386
- process_value_req(opkey, key, value, ttl, cas, options)
236
+ opkey = quiet? ? :addq : :add
237
+ storage_req(opkey, key, value, ttl, 0, options)
387
238
  end
388
239
 
389
240
  def replace(key, value, ttl, cas, options)
390
- opkey = multi? ? :replaceq : :replace
391
- process_value_req(opkey, key, value, ttl, cas, options)
241
+ opkey = quiet? ? :replaceq : :replace
242
+ storage_req(opkey, key, value, ttl, cas, options)
392
243
  end
393
244
 
394
245
  # rubocop:disable Metrics/ParameterLists
395
- def process_value_req(opkey, key, value, ttl, cas, options)
246
+ def storage_req(opkey, key, value, ttl, cas, options)
396
247
  (value, bitflags) = @value_marshaller.store(key, value, options)
397
248
  ttl = TtlSanitizer.sanitize(ttl)
398
249
 
@@ -400,21 +251,42 @@ module Dalli
400
251
  value: value, bitflags: bitflags,
401
252
  ttl: ttl, cas: cas)
402
253
  write(req)
403
- @response_processor.cas_response unless multi?
254
+ @response_processor.storage_response unless quiet?
404
255
  end
405
256
  # rubocop:enable Metrics/ParameterLists
406
257
 
258
+ def append(key, value)
259
+ opkey = quiet? ? :appendq : :append
260
+ write_append_prepend opkey, key, value
261
+ end
262
+
263
+ def prepend(key, value)
264
+ opkey = quiet? ? :prependq : :prepend
265
+ write_append_prepend opkey, key, value
266
+ end
267
+
268
+ def write_append_prepend(opkey, key, value)
269
+ write(RequestFormatter.standard_request(opkey: opkey, key: key, value: value))
270
+ @response_processor.no_body_response unless quiet?
271
+ end
272
+
273
+ # Delete Commands
407
274
  def delete(key, cas)
408
- opkey = multi? ? :deleteq : :delete
275
+ opkey = quiet? ? :deleteq : :delete
409
276
  req = RequestFormatter.standard_request(opkey: opkey, key: key, cas: cas)
410
277
  write(req)
411
- @response_processor.generic_response unless multi?
278
+ @response_processor.no_body_response unless quiet?
412
279
  end
413
280
 
414
- def flush(ttl = 0)
415
- req = RequestFormatter.standard_request(opkey: :flush, ttl: ttl)
416
- write(req)
417
- @response_processor.generic_response
281
+ # Arithmetic Commands
282
+ def decr(key, count, ttl, initial)
283
+ opkey = quiet? ? :decrq : :decr
284
+ decr_incr opkey, key, count, ttl, initial
285
+ end
286
+
287
+ def incr(key, count, ttl, initial)
288
+ opkey = quiet? ? :incrq : :incr
289
+ decr_incr opkey, key, count, ttl, initial
418
290
  end
419
291
 
420
292
  # This allows us to special case a nil initial value, and
@@ -429,29 +301,14 @@ module Dalli
429
301
  initial ||= 0
430
302
  write(RequestFormatter.decr_incr_request(opkey: opkey, key: key,
431
303
  count: count, initial: initial, expiry: expiry))
432
- @response_processor.decr_incr_response
433
- end
434
-
435
- def decr(key, count, ttl, initial)
436
- decr_incr :decr, key, count, ttl, initial
437
- end
438
-
439
- def incr(key, count, ttl, initial)
440
- decr_incr :incr, key, count, ttl, initial
304
+ @response_processor.decr_incr_response unless quiet?
441
305
  end
442
306
 
443
- def write_append_prepend(opkey, key, value)
444
- write_generic RequestFormatter.standard_request(opkey: opkey, key: key, value: value)
445
- end
446
-
447
- def write_generic(bytes)
448
- write(bytes)
449
- @response_processor.generic_response
450
- end
451
-
452
- def write_noop
453
- req = RequestFormatter.standard_request(opkey: :noop)
454
- write(req)
307
+ # Other Commands
308
+ def flush(ttl = 0)
309
+ opkey = quiet? ? :flushq : :flush
310
+ write(RequestFormatter.standard_request(opkey: opkey, ttl: ttl))
311
+ @response_processor.no_body_response unless quiet?
455
312
  end
456
313
 
457
314
  # Noop is a keepalive operation but also used to demarcate the end of a set of pipelined commands.
@@ -461,14 +318,6 @@ module Dalli
461
318
  @response_processor.multi_with_keys_response
462
319
  end
463
320
 
464
- def append(key, value)
465
- write_append_prepend :append, key, value
466
- end
467
-
468
- def prepend(key, value)
469
- write_append_prepend :prepend, key, value
470
- end
471
-
472
321
  def stats(info = '')
473
322
  req = RequestFormatter.standard_request(opkey: :stat, key: info)
474
323
  write(req)
@@ -476,66 +325,67 @@ module Dalli
476
325
  end
477
326
 
478
327
  def reset_stats
479
- write_generic RequestFormatter.standard_request(opkey: :stat, key: 'reset')
328
+ write(RequestFormatter.standard_request(opkey: :stat, key: 'reset'))
329
+ @response_processor.generic_response
480
330
  end
481
331
 
482
- def cas(key)
483
- req = RequestFormatter.standard_request(opkey: :get, key: key)
484
- write(req)
485
- @response_processor.data_cas_response
332
+ def version
333
+ write(RequestFormatter.standard_request(opkey: :version))
334
+ @response_processor.generic_response
486
335
  end
487
336
 
488
- def version
489
- write_generic RequestFormatter.standard_request(opkey: :version)
337
+ def write_noop
338
+ req = RequestFormatter.standard_request(opkey: :noop)
339
+ write(req)
490
340
  end
491
341
 
492
- def touch(key, ttl)
493
- ttl = TtlSanitizer.sanitize(ttl)
494
- write_generic RequestFormatter.standard_request(opkey: :touch, key: key, ttl: ttl)
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
495
349
  end
496
350
 
497
- def gat(key, ttl, options = nil)
498
- ttl = TtlSanitizer.sanitize(ttl)
499
- req = RequestFormatter.standard_request(opkey: :gat, key: key, ttl: ttl)
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
500
357
  write(req)
501
- @response_processor.generic_response(unpack: true, cache_nils: cache_nils?(options))
502
358
  end
503
359
 
504
- def connect
505
- Dalli.logger.debug { "Dalli::Server#connect #{name}" }
360
+ def response_buffer
361
+ @response_buffer ||= ResponseBuffer.new(@connection_manager, @response_processor)
362
+ end
506
363
 
507
- begin
508
- @pid = Process.pid
509
- @sock = memcached_socket
510
- authenticate_connection if require_auth?
511
- @version = version # Connect socket if not authed
512
- up!
513
- rescue Dalli::DalliError # SASL auth failure
514
- raise
515
- rescue SystemCallError, Timeout::Error, EOFError, SocketError => e
516
- # SocketError = DNS resolution failure
517
- failure!(e)
518
- end
364
+ def pipeline_response
365
+ response_buffer.process_single_getk_response
519
366
  end
520
367
 
521
- def memcached_socket
522
- if socket_type == :unix
523
- Dalli::Socket::UNIX.open(hostname, self, options)
524
- else
525
- Dalli::Socket::TCP.open(hostname, port, self, options)
526
- end
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
527
375
  end
528
376
 
529
- def require_auth?
530
- !username.nil?
377
+ def reconnect_on_pipeline_complete!
378
+ @connection_manager.reconnect! 'pipelined get has completed' if pipeline_complete?
531
379
  end
532
380
 
533
- def username
534
- @options[:username] || ENV['MEMCACHE_USERNAME']
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.'
535
384
  end
536
385
 
537
- def password
538
- @options[:password] || ENV['MEMCACHE_PASSWORD']
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")
539
389
  end
540
390
 
541
391
  include SaslAuthentication