dalli 4.3.10 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -161
  3. data/Gemfile +1 -1
  4. data/README.md +4 -55
  5. data/lib/dalli/client.rb +39 -98
  6. data/lib/dalli/compressor.rb +5 -53
  7. data/lib/dalli/instrumentation.rb +1 -1
  8. data/lib/dalli/key_manager.rb +2 -28
  9. data/lib/dalli/options.rb +2 -2
  10. data/lib/dalli/pipelined_getter.rb +32 -46
  11. data/lib/dalli/pipelined_setter.rb +8 -17
  12. data/lib/dalli/protocol/base.rb +31 -160
  13. data/lib/dalli/protocol/connection_manager.rb +20 -137
  14. data/lib/dalli/protocol/meta.rb +15 -54
  15. data/lib/dalli/protocol/{meta/request_formatter.rb → request_formatter.rb} +8 -21
  16. data/lib/dalli/protocol/response_buffer.rb +12 -27
  17. data/lib/dalli/protocol/{meta/response_processor.rb → response_processor.rb} +32 -64
  18. data/lib/dalli/protocol/server_config_parser.rb +1 -1
  19. data/lib/dalli/protocol/value_compressor.rb +7 -36
  20. data/lib/dalli/protocol/value_serializer.rb +1 -1
  21. data/lib/dalli/protocol.rb +9 -7
  22. data/lib/dalli/ring.rb +2 -2
  23. data/lib/dalli/servers_arg_normalizer.rb +1 -1
  24. data/lib/dalli/socket.rb +0 -29
  25. data/lib/dalli/version.rb +2 -2
  26. data/lib/dalli.rb +0 -3
  27. data/lib/rack/session/dalli.rb +2 -1
  28. metadata +7 -14
  29. data/lib/dalli/json_serializer.rb +0 -28
  30. data/lib/dalli/protocol/binary/request_formatter.rb +0 -116
  31. data/lib/dalli/protocol/binary/response_header.rb +0 -36
  32. data/lib/dalli/protocol/binary/response_processor.rb +0 -245
  33. data/lib/dalli/protocol/binary/sasl_authentication.rb +0 -60
  34. data/lib/dalli/protocol/binary.rb +0 -202
  35. data/lib/dalli/protocol_deprecations.rb +0 -45
  36. /data/lib/dalli/protocol/{meta/key_regularizer.rb → key_regularizer.rb} +0 -0
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'forwardable'
4
- require 'set'
5
4
  require 'socket'
6
5
  require 'timeout'
7
6
 
@@ -23,9 +22,9 @@ module Dalli
23
22
 
24
23
  def initialize(attribs, client_options = {})
25
24
  hostname, port, socket_type, @weight, user_creds = ServerConfigParser.parse(attribs)
25
+ warn_uri_credentials(user_creds)
26
26
  @options = client_options.merge(user_creds)
27
27
  @raw_mode = client_options[:raw]
28
- @verify_reply_keys = client_options[:verify_reply_keys] ? true : false
29
28
  @value_marshaller = @raw_mode ? StringMarshaller.new(@options) : ValueMarshaller.new(@options)
30
29
  @connection_manager = ConnectionManager.new(hostname, port, socket_type, @options)
31
30
  end
@@ -36,36 +35,20 @@ module Dalli
36
35
  @raw_mode
37
36
  end
38
37
 
39
- # True when values should come back as stored: the client is in raw mode,
40
- # or this request passed raw: true. A stored value is then never
41
- # deserialized or decompressed for a caller who asked for raw bytes.
42
- def raw_request?(options)
43
- raw_mode? || (options.is_a?(Hash) && options[:raw]) ? true : false
44
- end
45
-
46
38
  # Chokepoint method for error handling and ensuring liveness
47
39
  def request(opkey, *args)
48
40
  verify_state(opkey)
49
41
 
50
42
  begin
51
43
  @connection_manager.start_request!
52
- drain_pending_quiet_responses(opkey)
53
44
  response = send(opkey, *args)
54
- # Quiet requests can leave replies on the socket; remember that so a
55
- # later non-quiet request knows to read them first
56
- @connection_manager.mark_quiet_responses_pending! if quiet? && opkey != :noop
57
45
 
58
46
  # pipelined_get/pipelined_get_interleaved emit query but don't read the response(s)
59
47
  @connection_manager.finish_request! unless %i[pipelined_get pipelined_get_interleaved].include?(opkey)
60
48
 
61
49
  response
62
- rescue Dalli::MarshalError, Dalli::ValueOverMaxSize => e
63
- log_marshal_err(args.first, e) if e.is_a?(Dalli::MarshalError)
64
- # Raised only while the connection is clean: a value is marshalled
65
- # before any of its request is written. Leaving the request in
66
- # progress would close the connection on the next request, which
67
- # discards earlier quiet writes still waiting in the write buffer.
68
- @connection_manager.abort_request!
50
+ rescue Dalli::MarshalError => e
51
+ log_marshal_err(args.first, e)
69
52
  raise
70
53
  rescue Dalli::DalliError
71
54
  raise
@@ -100,7 +83,7 @@ module Dalli
100
83
  verify_pipelined_state(:getkq)
101
84
  write_noop
102
85
  # Use ensure_ready instead of reset to preserve any data already buffered
103
- # while this get_multi's interleaved requests were sent
86
+ # during interleaved pipelined get draining
104
87
  response_buffer.ensure_ready
105
88
  end
106
89
 
@@ -109,25 +92,12 @@ module Dalli
109
92
  # repeatedly whenever this server's socket is readable until
110
93
  # #pipeline_complete?.
111
94
  #
112
- # When a block is given, yields (key, value, cas) for each response,
113
- # avoiding intermediate Hash allocation. Returns nil.
114
- # Without a block, returns a Hash of { key => [value, cas] }.
115
- # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
116
- def pipeline_next_responses(&block)
117
- return abandon_pipeline if @pipeline_out_of_step
118
-
95
+ # Returns a Hash of kv pairs received.
96
+ def pipeline_next_responses
119
97
  reconnect_on_pipeline_complete!
120
- values = nil
98
+ values = {}
121
99
 
122
- # Only the read is socket I/O. The caller's block runs below, outside
123
- # this rescue, so an Errno or Timeout::Error it raises reaches the
124
- # caller instead of being mistaken for a network failure (which closed
125
- # the connection and retried the whole get_multi, yielding keys twice).
126
- begin
127
- response_buffer.read
128
- rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, EOFError => e
129
- @connection_manager.error_on_request!(e)
130
- end
100
+ response_buffer.read
131
101
 
132
102
  status, cas, key, value = response_buffer.process_single_getk_response
133
103
  # status is not nil only if we have a full response to parse
@@ -137,55 +107,33 @@ module Dalli
137
107
  # to the noop at the end of the pipeline
138
108
  finish_pipeline && break if status && key.nil?
139
109
 
140
- # A key that wasn't requested means the replies are out of step
141
- # (see expect_reply_keys): keep what was read so far, drop the rest
142
- if unexpected_reply_key?(key)
143
- abandon_pipeline
144
- break
145
- end
146
-
147
110
  # If the status is ok and the key is not nil, then this is a
148
111
  # getkq response with a value that we want to set in the response hash
149
- unless key.nil?
150
- if block
151
- yield key, value, cas
152
- else
153
- values ||= {}
154
- values[key] = [value, cas]
155
- end
156
- end
157
-
158
- # A request to this server from inside the block closes the
159
- # connection, and with it the rest of this get_multi's replies
160
- raise_pipeline_closed unless response_buffer.in_progress?
112
+ values[key] = [value, cas] unless key.nil?
161
113
 
162
114
  # Get the next response from the buffer
163
115
  status, cas, key, value = response_buffer.process_single_getk_response
164
116
  end
165
117
 
166
- values || {}
118
+ values
119
+ rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, EOFError => e
120
+ @connection_manager.error_on_request!(e)
167
121
  end
168
122
 
169
123
  # Abort current pipelined get. Generally used to signal an external
170
- # timeout during pipelined get, or that the get_multi stopped before
171
- # reading all its replies (timed_out: false). The underlying socket is
124
+ # timeout during pipelined get. The underlying socket is
172
125
  # disconnected, and the exception is swallowed.
173
126
  #
174
127
  # Returns nothing.
175
- def pipeline_abort(timed_out: true)
128
+ def pipeline_abort
176
129
  response_buffer.clear
177
- @pipeline_out_of_step = false
178
- @expected_reply_keys = nil
179
130
  @connection_manager.abort_request!
180
131
  return true unless connected?
181
132
 
182
- # Closes the connection, which ensures that our connection is in a
183
- # clean state for future requests. The get_multi ran out of time
184
- # overall, or stopped early, rather than hitting a socket error, so it
185
- # doesn't count toward socket_max_failures: two slow get_multis in a
186
- # row would otherwise mark a healthy server down.
187
- log_pipeline_abort(timed_out)
188
- @connection_manager.close
133
+ # Closes the connection, which ensures that our connection
134
+ # is in a clean state for future requests
135
+ @connection_manager.error_on_request!('External timeout')
136
+ rescue NetworkError
189
137
  true
190
138
  end
191
139
 
@@ -194,18 +142,6 @@ module Dalli
194
142
  !response_buffer.in_progress?
195
143
  end
196
144
 
197
- def username
198
- @options[:username] || ENV.fetch('MEMCACHE_USERNAME', nil)
199
- end
200
-
201
- def password
202
- @options[:password] || ENV.fetch('MEMCACHE_PASSWORD', nil)
203
- end
204
-
205
- def require_auth?
206
- !username.nil?
207
- end
208
-
209
145
  def quiet?
210
146
  Thread.current[::Dalli::QUIET]
211
147
  end
@@ -215,21 +151,19 @@ module Dalli
215
151
 
216
152
  private
217
153
 
218
- ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
219
- private_constant :ALLOWED_QUIET_OPS
154
+ URI_CREDENTIAL_WARNING = 'Dalli 5.0 removed SASL authentication. ' \
155
+ 'Credentials in memcached:// URIs are ignored.'
156
+ private_constant :URI_CREDENTIAL_WARNING
220
157
 
221
- # Reads the replies still pending from quiet requests before any
222
- # non-quiet request, which reads a reply of its own. A quiet block reads
223
- # them at its end, but the connection is shared: another thread or fiber
224
- # (quiet? is fiber-local) can make a request while the block is still
225
- # open, and would otherwise read the block's replies as its own. The
226
- # noop also sends any quiet requests still buffered.
227
- def drain_pending_quiet_responses(opkey)
228
- return unless @connection_manager.quiet_responses_pending? && opkey != :noop && !quiet?
158
+ def warn_uri_credentials(user_creds)
159
+ return if user_creds[:username].nil? && user_creds[:password].nil?
229
160
 
230
- noop
161
+ Dalli.logger.warn(URI_CREDENTIAL_WARNING)
231
162
  end
232
163
 
164
+ ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
165
+ private_constant :ALLOWED_QUIET_OPS
166
+
233
167
  def verify_allowed_quiet!(opkey)
234
168
  return if ALLOWED_QUIET_OPS.include?(opkey)
235
169
 
@@ -281,8 +215,7 @@ module Dalli
281
215
 
282
216
  def connect
283
217
  @connection_manager.establish_connection
284
- authenticate_connection if require_auth?
285
- @version = version # Connect socket if not authed
218
+ @version = version
286
219
  up!
287
220
  end
288
221
 
@@ -291,7 +224,6 @@ module Dalli
291
224
  # Use clear (not reset) to keep pipeline_complete? = true, which is
292
225
  # the expected state before pipeline_response_setup is called.
293
226
  response_buffer.clear
294
- expect_reply_keys(keys)
295
227
 
296
228
  req = +''
297
229
  keys.each do |key|
@@ -305,10 +237,8 @@ module Dalli
305
237
  # This prevents socket buffer deadlock when sending many keys.
306
238
  # Populates the provided results hash with any responses drained during send.
307
239
  def pipelined_get_interleaved(keys, chunk_size, results)
308
- # Start with an empty buffer for draining during the send phase. Bytes
309
- # left by an earlier get_multi that stopped early belong to its replies.
310
- response_buffer.reset
311
- expect_reply_keys(keys)
240
+ # Initialize the response buffer for draining during send phase
241
+ response_buffer.ensure_ready
312
242
 
313
243
  keys.each_slice(chunk_size) do |chunk|
314
244
  # Build and write this chunk of requests
@@ -341,12 +271,6 @@ module Dalli
341
271
  status, cas, key, value = response_buffer.process_single_getk_response
342
272
  break if status.nil? # No complete response available
343
273
 
344
- # Handled by the next pipeline_next_responses, once sending is done
345
- if unexpected_reply_key?(key)
346
- @pipeline_out_of_step = true
347
- break
348
- end
349
-
350
274
  results[key] = [value, cas] unless key.nil?
351
275
  end
352
276
  rescue SystemCallError, Dalli::NetworkError
@@ -354,48 +278,8 @@ module Dalli
354
278
  nil
355
279
  end
356
280
 
357
- # Registered with the connection manager, which clears it whenever the
358
- # connection closes, so a new connection never parses an old one's bytes
359
281
  def response_buffer
360
- @response_buffer ||= ResponseBuffer.new(@connection_manager, response_processor).tap do |buffer|
361
- @connection_manager.response_buffer = buffer
362
- end
363
- end
364
-
365
- # A multi-get only accepts replies for the keys it asked for. memcached
366
- # names an item created by auto-create (ma or mg with N) for a
367
- # base64-encoded key by its raw bytes, without the b flag; such a key
368
- # can contain spaces or CR/LF, which split the reply header and make the
369
- # replies after it parse as different ones. A key that wasn't requested
370
- # is how that shows up.
371
- # Only with the verify_reply_keys option, since building the set costs a
372
- # multi-get about 15%. Dalli itself no longer creates such items (see
373
- # Meta#decr_incr); the check guards against ones that older versions or
374
- # other clients created, on memcached versions with the behavior.
375
- def expect_reply_keys(keys)
376
- @pipeline_out_of_step = false
377
- @expected_reply_keys = (keys.to_set { |k| reply_key_form(k) } if @verify_reply_keys)
378
- end
379
-
380
- def unexpected_reply_key?(key)
381
- !key.nil? && @expected_reply_keys && !@expected_reply_keys.include?(reply_key_form(key))
382
- end
383
-
384
- # Keys compared as bytes; ASCII keys compare equal in any encoding as is
385
- def reply_key_form(key)
386
- key.ascii_only? ? key : key.b
387
- end
388
-
389
- # Keeps what was read, drops the rest of this server's replies, and
390
- # closes the connection so nothing unread is left on it. Not a socket
391
- # failure, so it doesn't count toward socket_max_failures; the keys not
392
- # read are simply missing from the result.
393
- def abandon_pipeline
394
- Dalli.logger.warn { "#{name} replied to a get_multi with a key that wasn't requested; closing the connection" }
395
- @pipeline_out_of_step = false
396
- response_buffer.clear
397
- @connection_manager.close
398
- {}
282
+ @response_buffer ||= ResponseBuffer.new(@connection_manager, response_processor)
399
283
  end
400
284
 
401
285
  # Called after the noop response is received at the end of a set
@@ -411,19 +295,6 @@ module Dalli
411
295
  @connection_manager.reconnect! 'pipelined get has completed' if pipeline_complete?
412
296
  end
413
297
 
414
- def raise_pipeline_closed
415
- raise Dalli::DalliError, "The connection to #{name} closed while a get_multi was reading its replies " \
416
- '(a request made from inside the get_multi block?)'
417
- end
418
-
419
- def log_pipeline_abort(timed_out)
420
- if timed_out
421
- Dalli.logger.warn { "#{name} didn't finish a get_multi within socket_timeout; closing the connection" }
422
- else
423
- Dalli.logger.debug { "#{name} get_multi stopped before reading all replies; closing the connection" }
424
- end
425
- end
426
-
427
298
  def log_marshal_err(key, err)
428
299
  Dalli.logger.error "Marshalling error for key '#{key}': #{err.message}"
429
300
  Dalli.logger.error 'You are trying to cache a Ruby object which cannot be serialized to memcached.'
@@ -28,9 +28,6 @@ module Dalli
28
28
 
29
29
  attr_accessor :hostname, :port, :socket_type, :options
30
30
  attr_reader :sock
31
- # The buffer replies are read into (see Base#response_buffer). It's
32
- # cleared with the write buffer whenever the connection closes.
33
- attr_writer :response_buffer
34
31
 
35
32
  def initialize(hostname, port, socket_type, client_options)
36
33
  @hostname = hostname
@@ -38,14 +35,9 @@ module Dalli
38
35
  @socket_type = socket_type
39
36
  @options = DEFAULTS.merge(client_options)
40
37
  @request_in_progress = false
41
- @quiet_responses_pending = false
42
38
  @sock = nil
43
39
  @pid = nil
44
- @write_buffer = []
45
- @write_buffer_bytes = 0
46
- @response_buffer = nil
47
40
 
48
- @fail_count = 0
49
41
  reset_down_info
50
42
  end
51
43
 
@@ -61,13 +53,11 @@ module Dalli
61
53
  Dalli.logger.debug { "Dalli::Server#connect #{name}" }
62
54
 
63
55
  @sock = memcached_socket
64
- # Writes are buffered in @write_buffer instead; see WRITE_BUFFER_FLUSH_BYTES
65
- @sock.sync = true
66
- @pid = Process.pid
56
+ @sock.sync = false # Enable buffered I/O for better performance
57
+ @pid = PIDCache.pid
67
58
  @request_in_progress = false
68
- @quiet_responses_pending = false
69
- rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, EOFError, SocketError => e
70
- # SocketError = DNS resolution failure; SSL_ERRORS = failed TLS handshake
59
+ rescue SystemCallError, *TIMEOUT_ERRORS, EOFError, SocketError => e
60
+ # SocketError = DNS resolution failure
71
61
  error_on_request!(e)
72
62
  end
73
63
 
@@ -95,8 +85,6 @@ module Dalli
95
85
  def down!
96
86
  close
97
87
  log_down_detected
98
- # Once down_retry_delay passes, the server gets a full set of attempts
99
- @fail_count = 0
100
88
 
101
89
  @error = $ERROR_INFO&.class&.name
102
90
  @msg ||= $ERROR_INFO&.message
@@ -126,44 +114,19 @@ module Dalli
126
114
  return unless @sock
127
115
 
128
116
  begin
129
- close_socket
117
+ @sock.close
130
118
  rescue StandardError
131
119
  nil
132
120
  end
133
121
  @sock = nil
134
122
  @pid = nil
135
- @quiet_responses_pending = false
136
- discard_write_buffer
137
- @response_buffer&.clear
138
123
  abort_request!
139
124
  end
140
125
 
141
- # A forked child shares the parent's connection, so it closes only its
142
- # own file descriptor. Closing the TLS socket itself would send
143
- # close_notify and end the parent's TLS session.
144
- def close_socket
145
- fork_detected? ? @sock.to_io.close : @sock.close
146
- end
147
-
148
126
  def connected?
149
127
  !@sock.nil?
150
128
  end
151
129
 
152
- # True when a quiet request was written on this connection and its
153
- # replies (memcached only suppresses some of them) haven't been
154
- # consumed by a noop yet.
155
- def quiet_responses_pending?
156
- @quiet_responses_pending
157
- end
158
-
159
- def mark_quiet_responses_pending!
160
- @quiet_responses_pending = true
161
- end
162
-
163
- def clear_quiet_responses_pending!
164
- @quiet_responses_pending = false
165
- end
166
-
167
130
  def request_in_progress?
168
131
  @request_in_progress
169
132
  end
@@ -178,88 +141,47 @@ module Dalli
178
141
  raise '[Dalli] No request in progress. This may be a bug in Dalli.' unless @request_in_progress
179
142
 
180
143
  @request_in_progress = false
181
- # A completed request is what proves the server healthy again, so the
182
- # failure count resets here rather than on reconnect: a server that
183
- # accepts connections but never answers would otherwise reset it on
184
- # every retry and never reach socket_max_failures.
185
- @fail_count = 0
186
144
  end
187
145
 
188
146
  def abort_request!
189
147
  @request_in_progress = false
190
148
  end
191
149
 
192
- # The longest reply line Dalli reads, in bytes, CRLF included. A meta
193
- # reply header is at most about 500 bytes: "VA <size>" plus its flags,
194
- # the largest being a 250-byte key base64-encoded to 336 bytes, a
195
- # 20-digit CAS, a 32-byte opaque and a few numeric fields. The longest
196
- # stats line (stats settings) carries a file path such as ext_path, up
197
- # to PATH_MAX (4096). 8 KiB leaves room for both, while a server that
198
- # never sends CRLF can't grow the read buffer without bound.
199
- MAX_LINE_BYTES = 8 * 1024
200
-
201
150
  def read_line
202
- flush_write_buffer
203
- data = @sock.gets("\r\n", MAX_LINE_BYTES)
151
+ data = @sock.gets("\r\n")
204
152
  error_on_request!('EOF in read_line') if data.nil?
205
- # Without its CRLF the line either ran past MAX_LINE_BYTES or was cut
206
- # short by EOF; either way the connection is out of step
207
- error_on_request!("Reply line without CRLF in the first #{MAX_LINE_BYTES} bytes") unless data.end_with?("\r\n")
208
153
  data
209
154
  rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, EOFError => e
210
155
  error_on_request!(e)
211
156
  end
212
157
 
213
- # memcached can't store an item larger than 1 GiB (its -I maximum), so a
214
- # reply claiming more (or a negative size) is malformed or hostile.
215
- # Reading allocates the full count up front, so check before reading.
216
- MAX_READ_BYTES = (1024 * 1024 * 1024) + 2 # plus the value's trailing "\r\n"
217
-
218
- def check_read_size!(count)
219
- return if count.between?(0, MAX_READ_BYTES)
220
-
221
- raise Dalli::DalliError, "Reply size #{count} from #{name} is out of range"
222
- end
223
-
224
158
  def read(count)
225
- check_read_size!(count)
226
- flush_write_buffer
227
- @sock.readfull(count)
159
+ # JRuby doesn't support IO#timeout=, so use custom readfull implementation
160
+ # CRuby 3.3+ has IO#timeout= which makes IO#read work with timeouts
161
+ if RUBY_ENGINE == 'jruby'
162
+ @sock.readfull(count)
163
+ else
164
+ @sock.read(count)
165
+ end
228
166
  rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, EOFError => e
229
167
  error_on_request!(e)
230
168
  end
231
169
 
232
- # Requests are buffered here rather than in the socket's own IO buffer
233
- # (the socket is sync). Ruby flushes an IO's write buffer when any process
234
- # holding it closes or finalizes the IO, so after a fork a child could
235
- # send bytes the parent had buffered but not sent yet: a quiet write would
236
- # run twice, or the parent's replies would shift onto the wrong requests.
237
- # A forked child discards this buffer instead. Like IO's own buffer, it
238
- # is sent once it grows past this size, or when a reply is read.
239
- WRITE_BUFFER_FLUSH_BYTES = 64 * 1024
240
-
241
170
  def write(bytes)
242
- # A copy: the caller may change its string before the buffer is sent
243
- # (the meta protocol writes a value string as its own part), as Ruby's
244
- # IO buffer copied on write
245
- @write_buffer << (bytes.frozen? ? bytes : bytes.dup)
246
- @write_buffer_bytes += bytes.bytesize
247
- flush_write_buffer if @write_buffer_bytes >= WRITE_BUFFER_FLUSH_BYTES
248
- bytes.bytesize
249
- rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, IOError => e
171
+ @sock.write(bytes)
172
+ rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS => e
250
173
  error_on_request!(e)
251
174
  end
252
175
 
253
176
  def flush
254
- flush_write_buffer
255
- rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, IOError => e
177
+ @sock.flush
178
+ rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS => e
256
179
  error_on_request!(e)
257
180
  end
258
181
 
259
182
  # Non-blocking read. Here to support the operation
260
183
  # of the get_multi operation
261
184
  def read_nonblock
262
- flush_write_buffer
263
185
  @sock.read_available
264
186
  end
265
187
 
@@ -286,9 +208,8 @@ module Dalli
286
208
  raise Dalli::RetryableNetworkError, message
287
209
  end
288
210
 
289
- # Called on connect. Deliberately leaves @fail_count alone; see
290
- # finish_request!.
291
211
  def reset_down_info
212
+ @fail_count = 0
292
213
  @down_at = nil
293
214
  @last_down_at = nil
294
215
  @msg = nil
@@ -313,18 +234,13 @@ module Dalli
313
234
  def reconnect_on_fork
314
235
  message = 'Fork detected, re-connecting child process...'
315
236
  Dalli.logger.info { message }
316
- # Drops anything the parent had buffered, closes the inherited socket
317
- # without touching the parent's connection, and reconnects immediately
237
+ # Close socket on a fork and reconnect immediately
318
238
  close
319
239
  establish_connection
320
240
  end
321
241
 
322
242
  def fork_detected?
323
- # Process.pid rather than PIDCache: PIDCache is refreshed by a
324
- # Process._fork hook, and another library's fork hook (such as
325
- # connection_pool closing its connections) can run in the child before
326
- # it, which would close the parent's TLS session as if it were ours.
327
- @pid && @pid != Process.pid
243
+ @pid && @pid != PIDCache.pid
328
244
  end
329
245
 
330
246
  def log_down_detected
@@ -345,39 +261,6 @@ module Dalli
345
261
  time = Time.now - @down_at
346
262
  Dalli.logger.warn { format('%<name>s is back (downtime was %<time>.3f seconds)', name: name, time: time) }
347
263
  end
348
-
349
- private
350
-
351
- # One write per flush. A TLS socket sends each argument of
352
- # write(*parts) as its own record and system call, so the parts are
353
- # joined first.
354
- def flush_write_buffer
355
- return if @write_buffer.empty?
356
-
357
- @sock.write(@write_buffer.size == 1 ? @write_buffer.first : joined_write_buffer)
358
- discard_write_buffer
359
- end
360
-
361
- # Joined as bytes: requests can mix UTF-8 and binary strings, which
362
- # String#<< refuses to combine when both hold non-ASCII bytes
363
- if String.method_defined?(:append_as_bytes)
364
- def joined_write_buffer
365
- out = String.new(capacity: @write_buffer_bytes, encoding: Encoding::BINARY)
366
- @write_buffer.each { |part| out.append_as_bytes(part) }
367
- out
368
- end
369
- else
370
- def joined_write_buffer
371
- out = String.new(capacity: @write_buffer_bytes, encoding: Encoding::BINARY)
372
- @write_buffer.each { |part| out << part.b }
373
- out
374
- end
375
- end
376
-
377
- def discard_write_buffer
378
- @write_buffer.clear
379
- @write_buffer_bytes = 0
380
- end
381
264
  end
382
265
  end
383
266
  end